[ 
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)

Reply via email to