JsonLayout support for custom key-value pairs being inserted into JSON Changed AbstractJacksonLayout.convertMutableToLog4jEvent to protected Object wrapLogEvent(LogEvent event) Added Extras as KeyValuePair[] to JsonLayout When extras as specified, LogEvent is wrapped in new LogEventWithExtras class, which merges LogEvent serialized data with provided extras
Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/dad62657 Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/dad62657 Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/dad62657 Branch: refs/heads/master Commit: dad62657690ded5ab1f0e8524fed4efd5f418edb Parents: 7e83dfb Author: Michal Dvorak (cen38289) <[email protected]> Authored: Tue Sep 12 14:31:52 2017 +0200 Committer: Mikael Ståldal <[email protected]> Committed: Tue Sep 26 21:53:52 2017 +0200 ---------------------------------------------------------------------- .../core/layout/AbstractJacksonLayout.java | 8 +- .../logging/log4j/core/layout/JsonLayout.java | 89 ++++++++++++++++++-- 2 files changed, 87 insertions(+), 10 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/dad62657/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractJacksonLayout.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractJacksonLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractJacksonLayout.java index 6ae62cf..4a61d93 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractJacksonLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractJacksonLayout.java @@ -40,10 +40,10 @@ abstract class AbstractJacksonLayout extends AbstractStringLayout { @PluginBuilderAttribute private boolean eventEol; - + @PluginBuilderAttribute private boolean compact; - + @PluginBuilderAttribute private boolean complete; @@ -199,7 +199,7 @@ abstract class AbstractJacksonLayout extends AbstractStringLayout { } } - private static LogEvent convertMutableToLog4jEvent(final LogEvent event) { + protected Object wrapLogEvent(final LogEvent event) { // TODO Jackson-based layouts have certain filters set up for Log4jLogEvent. // TODO Need to set up the same filters for MutableLogEvent but don't know how... // This is a workaround. @@ -210,7 +210,7 @@ abstract class AbstractJacksonLayout extends AbstractStringLayout { public void toSerializable(final LogEvent event, final Writer writer) throws JsonGenerationException, JsonMappingException, IOException { - objectWriter.writeValue(writer, convertMutableToLog4jEvent(event)); + objectWriter.writeValue(writer, wrapLogEvent(event)); writer.write(eol); if (includeNullDelimiter) { writer.write('\0'); http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/dad62657/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JsonLayout.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JsonLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JsonLayout.java index 6fcc13d..4ce1aed 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JsonLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JsonLayout.java @@ -20,9 +20,13 @@ import java.io.IOException; import java.io.Writer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonUnwrapped; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; @@ -31,6 +35,8 @@ import org.apache.logging.log4j.core.config.Node; 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.util.KeyValuePair; /** * Appends a series of JSON events as strings serialized as bytes. @@ -56,6 +62,12 @@ import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; * appender uses end-of-line characters and indents lines to format the text. If {@code compact="true"}, then no * end-of-line or indentation is used. Message content may contain, of course, escaped end-of-lines. * </p> + * <h3>Extra Fields</h3> + * <p> + * This property allows addition of custom fields into generated JSON. + * {@code <JsonLayout><KeyValuePair key="foo" value="bar"/></JsonLayout>} inserts {@code "key":"bar"} directly + * into JSON output. Supports Lookup expressions. + * </p> */ @Plugin(name = "JsonLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) public final class JsonLayout extends AbstractJacksonLayout { @@ -71,7 +83,10 @@ public final class JsonLayout extends AbstractJacksonLayout { @PluginBuilderAttribute private boolean propertiesAsList; - + + @PluginElement("Extras") + private KeyValuePair[] extras; + public Builder() { super(); setCharset(StandardCharsets.UTF_8); @@ -84,7 +99,8 @@ public final class JsonLayout extends AbstractJacksonLayout { final String footerPattern = toStringOrNull(getFooter()); return new JsonLayout(getConfiguration(), isLocationInfo(), isProperties(), encodeThreadContextAsList, isComplete(), isCompact(), getEventEol(), headerPattern, footerPattern, getCharset(), - isIncludeStacktrace(), isStacktraceAsString(), isIncludeNullDelimiter()); + isIncludeStacktrace(), isStacktraceAsString(), isIncludeNullDelimiter(), + getExtras()); } public boolean isPropertiesAsList() { @@ -95,8 +111,19 @@ public final class JsonLayout extends AbstractJacksonLayout { this.propertiesAsList = propertiesAsList; return asBuilder(); } + + public KeyValuePair[] getExtras() { + return extras; + } + + public B setExtras(KeyValuePair[] extras) { + this.extras = extras; + return asBuilder(); + } } + protected final Map<String, Object> extras; + /** * @deprecated Use {@link #newBuilder()} instead */ @@ -111,6 +138,8 @@ public final class JsonLayout extends AbstractJacksonLayout { PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(headerPattern).setDefaultPattern(DEFAULT_HEADER).build(), PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(footerPattern).setDefaultPattern(DEFAULT_FOOTER).build(), false); + + extras = null; } private JsonLayout(final Configuration config, final boolean locationInfo, final boolean properties, @@ -118,13 +147,26 @@ public final class JsonLayout extends AbstractJacksonLayout { final boolean complete, final boolean compact, final boolean eventEol, final String headerPattern,final String footerPattern, final Charset charset, final boolean includeStacktrace,final boolean stacktraceAsString, - final boolean includeNullDelimiter) { + final boolean includeNullDelimiter, + final KeyValuePair[] extras) { super(config, new JacksonFactory.JSON(encodeThreadContextAsList, includeStacktrace, stacktraceAsString).newWriter( locationInfo, properties, compact), charset, compact, complete, eventEol, PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(headerPattern).setDefaultPattern(DEFAULT_HEADER).build(), PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(footerPattern).setDefaultPattern(DEFAULT_FOOTER).build(), includeNullDelimiter); + + if (extras != null && extras.length > 0) { + final Map<String, Object> extrasMap = new LinkedHashMap<>(); + + for (KeyValuePair pair : extras) { + extrasMap.put(pair.getKey(), pair.getValue()); + } + + this.extras = Collections.unmodifiableMap(extrasMap); + } else { + this.extras = null; + } } /** @@ -209,6 +251,8 @@ public final class JsonLayout extends AbstractJacksonLayout { * The character set to use, if {@code null}, uses "UTF-8". * @param includeStacktrace * If "true", includes the stacktrace of any Throwable in the generated JSON, defaults to "true". + * @param extras + * Set of custom fields that are appended to the generated JSON. * @return A JSON Layout. * * @deprecated Use {@link #newBuilder()} instead @@ -225,10 +269,11 @@ public final class JsonLayout extends AbstractJacksonLayout { final String headerPattern, final String footerPattern, final Charset charset, - final boolean includeStacktrace) { + final boolean includeStacktrace, + final KeyValuePair[] extras) { final boolean encodeThreadContextAsList = properties && propertiesAsList; return new JsonLayout(config, locationInfo, properties, encodeThreadContextAsList, complete, compact, eventEol, - headerPattern, footerPattern, charset, includeStacktrace, false, false); + headerPattern, footerPattern, charset, includeStacktrace, false, false, extras); } @PluginBuilderFactory @@ -243,7 +288,7 @@ public final class JsonLayout extends AbstractJacksonLayout { */ public static JsonLayout createDefaultLayout() { return new JsonLayout(new DefaultConfiguration(), false, false, false, false, false, false, - DEFAULT_HEADER, DEFAULT_FOOTER, StandardCharsets.UTF_8, true, false, false); + DEFAULT_HEADER, DEFAULT_FOOTER, StandardCharsets.UTF_8, true, false, false, null); } @Override @@ -253,4 +298,36 @@ public final class JsonLayout extends AbstractJacksonLayout { } super.toSerializable(event, writer); } + + @Override + protected Object wrapLogEvent(LogEvent event) { + Object result = super.wrapLogEvent(event); + + if (extras != null) { + return new LogEventWithExtras(result, extras); + } else { + return result; + } + } + + public static class LogEventWithExtras { + + private final Object logEvent; + private final Map<String, Object> extras; + + public LogEventWithExtras(Object logEvent, Map<String, Object> extras) { + this.logEvent = logEvent; + this.extras = extras; + } + + @JsonUnwrapped + public Object getLogEvent() { + return logEvent; + } + + @JsonAnyGetter + public Map<String, Object> getExtras() { + return extras; + } + } }
