[
https://issues.apache.org/jira/browse/LOG4J2-2915?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17184146#comment-17184146
]
Richard Bair commented on LOG4J2-2915:
--------------------------------------
Here is a workaround I am using in my project. When used, the log statements
look like this:
{code:java}
LOG.info(() -> Messages.msg()
.with("msg", "New Infra Admin registered")
.with("infraAdmin", new RegistryItemMessage(saved))
.toMessage());
{code}
The builder class looks like this:
{code:java}
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.ObjectMessage;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
public final class Messages {
public static MessageBuilder msg() {
return new MessageBuilder();
}
public static class MessageBuilder {
private final Map<String, Object> map = new HashMap<>();
public MessageBuilder with(String name, String value) {
map.put(name, value);
return this;
}
public MessageBuilder with(String name, int value) {
map.put(name, value);
return this;
}
public MessageBuilder with(String name, long value) {
map.put(name, value);
return this;
}
public MessageBuilder with(String name, double value) {
map.put(name, value);
return this;
}
public MessageBuilder with(String name, boolean value) {
map.put(name, value);
return this;
}
public MessageBuilder with(String name, BigInteger value) {
map.put(name, value);
return this;
}
public MessageBuilder with(String name, BigDecimal value) {
map.put(name, value);
return this;
}
public MessageBuilder with(String name, MessageBuilder msg) {
map.put(name, msg.map);
return this;
}
public MessageBuilder with(String name, Message msg) {
map.put(name, msg);
return this;
}
public ObjectMessage toMessage() {
return new ObjectMessage(toMap());
}
public Map<String, Object> toMap() {
return new HashMap<>(map);
}
}
}
{code}
And the "RegistryItemMessage" is a special ObjectMessage subclass that looks
like this:
{code:java}
public class RegistryItemMessage extends ObjectMessage {
/**
* Creates the RegistryItemMessage
*
* @param item The RegistryItem to log.
*/
public RegistryItemMessage(RegistryItem item) {
super(Messages.msg()
.with("region", item.getRegion())
.with("serviceType", item.getServiceType())
.with("baseUrl", item.getUrl())
.with("active", item.isActive())
.with("idcsHost", item.getIdcsHost())
.with("idcsAppId", item.getIdcsAppId())
.toMap());
}
}
{code}
The builder has "with" methods much like the MapMessage does, and lets me use a
fluent style for constructing the log contents and looks consistent with
existing Log4j2 API. The "build" method is required for two reasons: first, so
that the ObjectMessage could be created; second, so that a copy of the Map can
be made in case the builder is reused.
The RegistryItemMessage is an example of a dynamic object message. The "toMap"
call on the builder will return a Map which serializes as a JSON object by
Jackson.
> JSONLayout should support JSONObjects
> -------------------------------------
>
> Key: LOG4J2-2915
> URL: https://issues.apache.org/jira/browse/LOG4J2-2915
> Project: Log4j 2
> Issue Type: Improvement
> Components: Layouts
> Affects Versions: 2.13.0
> Reporter: Richard Bair
> Priority: Major
>
> In our project, we're attempting to use Log4J2 for structured logging. Our
> production logs are sent to an ElasticSearch where developers can
> subsequently search the logs using Kibana.
> One of the key value propositions to using structured logging for us is that
> a developer can change the log statement to write new JSON content such that
> the ES will automatically index it and it will automatically be made
> available to Kibana - the ops team doesn't need to get involved, and doesn't
> need to modify any FileBeat or other components to change the parsing logic
> and create new JSON fields on the ES side.
> To realize this value, we need to have an API that allows the user to easily
> determine what to log. Right now, Log4j2 *only* supports ObjectMessages with
> the JSONLayout if you want the message to be an actual JSON object when
> written out. This means that every message to be logged must be a concrete
> Java bean object to be passed to ObjectMessage. I have to create a lot of
> Java classes just to represent these objects which is overly verbose.
> A nicer solution would be to have special handling if the object wrapped by
> ObjectMessage is a javax.json.JsonObject, or if the message is a MapMessage.
> In both cases, I expected that the "message" of the JSON object produced by
> the JsonLayout would be a JSON object (since I have objectMessageAsJsonObject
> set to true).
> For example, I would expect the following to produce a JSON object as the
> "message" produced by the JsonLayout:
> {{LOG.debug(() -> new MapMessage<>()}}{{.with("iaId", iaId)}}{{.with("item",
> item));}}
> I would also expect the following to produce a JSON object as the "message":
> {{LOG.info(() -> new ObjectMessage(Json.createObjectBuilder()}}
> {{ .add("iaId", iaId)}}
> {{ .add("item", item)));}}
>
>
--
This message was sent by Atlassian Jira
(v8.3.4#803005)