3

In Spring Boot 2 application I have configured Log4j2 with JsonLayout like below

    ....

    <Appenders>
        <Console name="ConsoleJSONAppender" target="SYSTEM_OUT">
            <JsonLayout complete="false" compact="false">
            </JsonLayout>
        </Console>
    </Appenders> 
    <Logger name="CONSOLE_JSON_APPENDER" level="INFO" additivity="false">
        <AppenderRef ref="ConsoleJSONAppender" />
    </Logger>

    .....

and I got output like below

    {
            "timeMillis" : 1496306649058,
            "thread" : "main",
            "level" : "INFO",
            "loggerName" : "ConsoleJSONAppender",
            "message" : "Json Message",
            "endOfBatch" : false,
            "loggerFqcn" : "org.apache.logging.log4j.spi.AbstractLogger",
            "threadId" : 1,
            "threadPriority" : 5
    }

Output is fine but I don't want attributes like "endofBatch", "threadPriority" and others but it is getting displayed in logs, how to avoid unwanted (default) attributes in JsonLayout based logs.

Han
  • 263
  • 1
  • 8
Ravikumar
  • 413
  • 1
  • 7
  • 18
  • the same question: https://stackoverflow.com/questions/24652128/how-to-modify-log4j-jsonlayout-field-names/68148523#68148523 – HungNM2 Jun 27 '21 at 06:18

2 Answers2

3

If you want to log only level and loggerName than customize like below in your configuration file.

...
<PatternLayout>
    <pattern>{"level":"%p","loggerName":"%c"}</pattern>
</PatternLayout>
...

The parameter are described at here. Find Patterns at Pattern Layout.

Han
  • 263
  • 1
  • 8
  • 1
    thanks, about solution gets **JSON** structure with _**PatternLayout**_ but is there any possibility to define required attribute or remove unwanted attribute with _**JsonLayout**_ ? – Ravikumar May 06 '20 at 06:33
  • @Ravikumar You can define `define here` I attached link at my answer. **The parameter are described at here. Find Patterns at Pattern Layout.** – Han May 06 '20 at 06:42
  • **your the solution is perfectly okay**, which brings me logs in JSON Format. But here we are using _pattern_ inside _**PatterLayout**_ to bring JSON format in logs its totally fine. But Slf4j2 supports JSON format by default in _**JsonLayout**_ with default attributes like _timeMillis_, _endOfBatch_ and so on. so here how can we limit / remove default attributes in _**JsonLayout**_. So is that possible with _**JsonLayout**_ ?. – Ravikumar May 06 '20 at 07:29
  • @Ravikumar In my case, I only use `...` and print log well. `{"level":"%p","loggerName":"%c"}` print `{"level":"INFO","loggerName":"com.test.LogTest"}` – Han May 06 '20 at 10:03
  • @Ravikumar Good luck! – Han May 06 '20 at 23:11
0

For this purpose, I slightly changed the JSONLayout class and created the new JSONLayoutV2 class. You can use the JSONLayoutV2 class to customize these attributes until the JSONLayout class is modified.

<JSONLayoutV2 objectMessageAsJsonObject="true" thread="true" threadId="true" eventEol="true" complete="true" compact="false"  properties="true" />

JSONLayoutV2 class:

package org.apache.logging.log4j.core.layout;

import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.DefaultConfiguration;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
import org.apache.logging.log4j.core.util.KeyValuePair;

import java.io.IOException;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;


@Plugin(
        name = "JsonLayoutV2",
        category = "Core",
        elementType = "layout",
        printObject = true
)
public final class JsonLayoutV2 extends AbstractJacksonLayout {
    private static final String DEFAULT_FOOTER = "]";
    private static final String DEFAULT_HEADER = "[";
    static final String CONTENT_TYPE = "application/json";

    /** @deprecated */
    @Deprecated
    protected JsonLayoutV2(final Configuration config, final boolean locationInfo, final boolean properties, final boolean encodeThreadContextAsList, final boolean complete, final boolean compact, final boolean eventEol, final String endOfLine, final String headerPattern, final String footerPattern, final Charset charset, final boolean includeStacktrace) {
        super(config, (new JacksonFactory.JSON(encodeThreadContextAsList, includeStacktrace, false, false)).newWriter(locationInfo, properties, compact), charset, compact, complete, eventEol, endOfLine, PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(headerPattern).setDefaultPattern("[").build(), PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(footerPattern).setDefaultPattern("]").build(), false, (KeyValuePair[])null);
    }

    private JsonLayoutV2(final Configuration config, final boolean locationInfo, final boolean properties, final boolean encodeThreadContextAsList, final boolean complete, final boolean compact, final boolean eventEol, final String endOfLine, final String headerPattern, final String footerPattern, final Charset charset, final boolean includeStacktrace, final boolean stacktraceAsString, final boolean includeNullDelimiter, final boolean includeTimeMillis, final KeyValuePair[] additionalFields, final boolean objectMessageAsJsonObject,
                         final boolean instant, final boolean loggerFqcn, final boolean endOfBatch, final boolean loggerName, final boolean thread, final boolean threadId, final boolean threadPriority) {
//        super(config, (new JacksonFactory.JSON(encodeThreadContextAsList, includeStacktrace, stacktraceAsString, objectMessageAsJsonObject)).newWriter(locationInfo, properties, compact, includeTimeMillis), charset, compact, complete, eventEol, endOfLine, PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(headerPattern).setDefaultPattern("[").build(), PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(footerPattern).setDefaultPattern("]").build(), includeNullDelimiter, additionalFields);
        super(config, newWriter((new JacksonFactory.JSON(encodeThreadContextAsList, includeStacktrace, stacktraceAsString, objectMessageAsJsonObject)), locationInfo, properties, compact, includeTimeMillis,
                instant,  loggerFqcn,  endOfBatch,  loggerName,  thread,  threadId,  threadPriority), charset, compact, complete, eventEol, endOfLine, PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(headerPattern).setDefaultPattern("[").build(), PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(footerPattern).setDefaultPattern("]").build(), includeNullDelimiter, additionalFields);
    }

    static ObjectWriter newWriter(JacksonFactory.JSON jacksonFactory, final boolean locationInfo, final boolean properties, final boolean compact, final boolean includeMillis,
                                  final boolean instant, final boolean loggerFqcn, final boolean endOfBatch, final boolean loggerName, final boolean thread, final boolean threadId, final boolean threadPriority) {
        SimpleFilterProvider filters = new SimpleFilterProvider();
        Set<String> except = new HashSet(3);
        if (!locationInfo) {
            except.add(jacksonFactory.getPropertNameForSource());
        }

        if (!properties) {
            except.add(jacksonFactory.getPropertNameForContextMap());
        }

        if (includeMillis) {
            except.add(jacksonFactory.getPropertyNameForInstant());
        } else {
            except.add(jacksonFactory.getPropertyNameForTimeMillis());
        }

        if (!instant)           except.add("instant");
        if (!loggerFqcn)        except.add("loggerFqcn");
        if (!endOfBatch)        except.add("endOfBatch");
        if (!loggerName)        except.add("loggerName");
        if (!thread)            except.add("thread");
        if (!threadId)          except.add("threadId");
        if (!threadPriority)    except.add("threadPriority");

        except.add(jacksonFactory.getPropertNameForNanoTime());
        filters.addFilter(Log4jLogEvent.class.getName(), SimpleBeanPropertyFilter.serializeAllExcept(except));
        ObjectWriter writer = jacksonFactory.newObjectMapper().writer(compact ? jacksonFactory.newCompactPrinter() : jacksonFactory.newPrettyPrinter());
        return writer.with(filters);
    }

    public byte[] getHeader() {
        if (!this.complete) {
            return null;
        } else {
            StringBuilder buf = new StringBuilder();
            String str = this.serializeToString(this.getHeaderSerializer());
            if (str != null) {
                buf.append(str);
            }

            buf.append(this.eol);
            return this.getBytes(buf.toString());
        }
    }

    public byte[] getFooter() {
        if (!this.complete) {
            return null;
        } else {
            StringBuilder buf = new StringBuilder();
            buf.append(this.eol);
            String str = this.serializeToString(this.getFooterSerializer());
            if (str != null) {
                buf.append(str);
            }

            buf.append(this.eol);
            return this.getBytes(buf.toString());
        }
    }

    public Map<String, String> getContentFormat() {
        Map<String, String> result = new HashMap();
        result.put("version", "2.0");
        return result;
    }

    public String getContentType() {
        return "application/json; charset=" + this.getCharset();
    }

    /** @deprecated */
//    @Deprecated
//    public static JsonLayoutV2 createLayout(final Configuration config, final boolean locationInfo, final boolean properties, final boolean propertiesAsList, final boolean complete, final boolean compact, final boolean eventEol, final String headerPattern, final String footerPattern, final Charset charset, final boolean includeStacktrace) {
//        boolean encodeThreadContextAsList = properties && propertiesAsList;
//        return new JsonLayoutV2(config, locationInfo, properties, encodeThreadContextAsList, complete, compact, eventEol, (String)null, headerPattern, footerPattern, charset, includeStacktrace, false, false, false, (KeyValuePair[])null, false);
//    }

    @PluginBuilderFactory
    public static <B extends Builder<B>> B newBuilder() {
        return (B) new Builder();
    }

    public static JsonLayoutV2 createDefaultLayout() {
        return new JsonLayoutV2(new DefaultConfiguration(), false, false, false, false, false, false, (String)null, "[", "]", StandardCharsets.UTF_8, true, false, false, false, (KeyValuePair[])null, false,
                false,false,false,false,false,false,false);
    }

    public void toSerializable(final LogEvent event, final Writer writer) throws IOException {
        if (this.complete && this.eventCount > 0L) {
            writer.append(", ");
        }

        super.toSerializable(event, writer);
    }

    public static class Builder<B extends Builder<B>> extends AbstractJacksonLayout.Builder<B> implements org.apache.logging.log4j.core.util.Builder<JsonLayoutV2> {
        @PluginBuilderAttribute
        private boolean propertiesAsList;
        @PluginBuilderAttribute
        private boolean objectMessageAsJsonObject;
        @PluginElement("AdditionalField")
        private KeyValuePair[] additionalFields;

        @PluginBuilderAttribute
        private boolean instant;

        @PluginBuilderAttribute
        private boolean loggerFqcn;

        @PluginBuilderAttribute
        private boolean endOfBatch;

        @PluginBuilderAttribute
        private boolean loggerName;

        @PluginBuilderAttribute
        private boolean thread;

        @PluginBuilderAttribute
        private boolean threadId;

        @PluginBuilderAttribute
        private boolean threadPriority;

        public Builder() {
            this.setCharset(StandardCharsets.UTF_8);
        }

        public JsonLayoutV2 build() {
            boolean encodeThreadContextAsList = this.isProperties() && this.propertiesAsList;
            String headerPattern = this.toStringOrNull(this.getHeader());
            String footerPattern = this.toStringOrNull(this.getFooter());
            return new JsonLayoutV2(this.getConfiguration(), this.isLocationInfo(), this.isProperties(), encodeThreadContextAsList, this.isComplete(), this.isCompact(), this.getEventEol(), this.getEndOfLine(), headerPattern, footerPattern, this.getCharset(), this.isIncludeStacktrace(), this.isStacktraceAsString(), this.isIncludeNullDelimiter(), this.isIncludeTimeMillis(), this.getAdditionalFields(), this.getObjectMessageAsJsonObject(),
                    instant, loggerFqcn, endOfBatch, loggerName, thread, threadId, threadPriority);
        }

        public boolean isPropertiesAsList() {
            return this.propertiesAsList;
        }

        public B setPropertiesAsList(final boolean propertiesAsList) {
            this.propertiesAsList = propertiesAsList;
            return this.asBuilder();
        }

        public boolean getObjectMessageAsJsonObject() {
            return this.objectMessageAsJsonObject;
        }

        public B setObjectMessageAsJsonObject(final boolean objectMessageAsJsonObject) {
            this.objectMessageAsJsonObject = objectMessageAsJsonObject;
            return this.asBuilder();
        }

        public KeyValuePair[] getAdditionalFields() {
            return this.additionalFields;
        }

        public B setAdditionalFields(final KeyValuePair[] additionalFields) {
            this.additionalFields = additionalFields;
            return this.asBuilder();
        }
    }
}
Shapur
  • 498
  • 7
  • 17