LOG4J2-1694 Allow additional fields in JSON/XML/YAML layouts. (cherry picked from commit fc50939)
Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/c371568b Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/c371568b Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/c371568b Branch: refs/heads/master Commit: c371568bd0ff7f3d9464aadf3dbff4097056001c Parents: bf42c2f Author: Michal Dvorak <[email protected]> Authored: Sun Jul 9 15:29:02 2017 +0200 Committer: Mikael Ståldal <[email protected]> Committed: Tue Sep 26 21:53:52 2017 +0200 ---------------------------------------------------------------------- .../core/layout/AbstractJacksonLayout.java | 133 ++++++++++++++++++- .../logging/log4j/core/layout/JsonLayout.java | 92 +------------ .../logging/log4j/core/layout/XmlLayout.java | 16 ++- .../logging/log4j/core/layout/YamlLayout.java | 24 ++-- .../log4j/core/layout/XmlLayoutTest.java | 19 +++ .../log4j/core/layout/YamlLayoutTest.java | 19 +++ src/site/xdoc/manual/layouts.xml.vm | 30 +++++ 7 files changed, 226 insertions(+), 107 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/c371568b/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 4a61d93..e080704 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 @@ -19,17 +19,27 @@ package org.apache.logging.log4j.core.layout; import java.io.IOException; import java.io.Writer; import java.nio.charset.Charset; +import java.util.LinkedHashMap; +import java.util.Map; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginElement; import org.apache.logging.log4j.core.impl.MutableLogEvent; +import org.apache.logging.log4j.core.jackson.XmlConstants; +import org.apache.logging.log4j.core.lookup.StrSubstitutor; +import org.apache.logging.log4j.core.util.KeyValuePair; import org.apache.logging.log4j.core.util.StringBuilderWriter; import org.apache.logging.log4j.util.Strings; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.annotation.JsonUnwrapped; import com.fasterxml.jackson.core.JsonGenerationException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; abstract class AbstractJacksonLayout extends AbstractStringLayout { @@ -62,6 +72,9 @@ abstract class AbstractJacksonLayout extends AbstractStringLayout { @PluginBuilderAttribute private boolean includeNullDelimiter = false; + @PluginElement("AdditionalField") + private KeyValuePair[] additionalFields; + protected String toStringOrNull(final byte[] header) { return header == null ? null : new String(header, Charset.defaultCharset()); } @@ -100,6 +113,10 @@ abstract class AbstractJacksonLayout extends AbstractStringLayout { public boolean isIncludeNullDelimiter() { return includeNullDelimiter; } + public KeyValuePair[] getAdditionalFields() { + return additionalFields; + } + public B setEventEol(final boolean eventEol) { this.eventEol = eventEol; return asBuilder(); @@ -154,6 +171,16 @@ abstract class AbstractJacksonLayout extends AbstractStringLayout { this.includeNullDelimiter = includeNullDelimiter; return asBuilder(); } + + /** + * Additional fields to set on each log event. + * + * @return this builder + */ + public B setAdditionalFields(KeyValuePair[] additionalFields) { + this.additionalFields = additionalFields; + return asBuilder(); + } } protected final String eol; @@ -161,6 +188,7 @@ abstract class AbstractJacksonLayout extends AbstractStringLayout { protected final boolean compact; protected final boolean complete; protected final boolean includeNullDelimiter; + protected final ResolvableKeyValuePair[] additionalFields; @Deprecated protected AbstractJacksonLayout(final Configuration config, final ObjectWriter objectWriter, final Charset charset, @@ -169,15 +197,49 @@ abstract class AbstractJacksonLayout extends AbstractStringLayout { this(config, objectWriter, charset, compact, complete, eventEol, headerSerializer, footerSerializer, false); } + @Deprecated protected AbstractJacksonLayout(final Configuration config, final ObjectWriter objectWriter, final Charset charset, final boolean compact, final boolean complete, final boolean eventEol, final Serializer headerSerializer, final Serializer footerSerializer, final boolean includeNullDelimiter) { + this(config, objectWriter, charset, compact, complete, eventEol, headerSerializer, footerSerializer, includeNullDelimiter, null); + } + + protected AbstractJacksonLayout(final Configuration config, final ObjectWriter objectWriter, final Charset charset, + final boolean compact, final boolean complete, final boolean eventEol, final Serializer headerSerializer, + final Serializer footerSerializer, final boolean includeNullDelimiter, + final KeyValuePair[] additionalFields) { super(config, charset, headerSerializer, footerSerializer); this.objectWriter = objectWriter; this.compact = compact; this.complete = complete; this.eol = compact && !eventEol ? COMPACT_EOL : DEFAULT_EOL; this.includeNullDelimiter = includeNullDelimiter; + this.additionalFields = prepareAdditionalFields(config, additionalFields); + } + + protected static boolean valueNeedsLookup(final String value) { + return value != null && value.contains("${"); + } + + private static ResolvableKeyValuePair[] prepareAdditionalFields(final Configuration config, final KeyValuePair[] additionalFields) { + if (additionalFields == null || additionalFields.length == 0) { + // No fields set + return new ResolvableKeyValuePair[0]; + } + + // Convert to specific class which already determines whether values needs lookup during serialization + final ResolvableKeyValuePair[] resolvableFields = new ResolvableKeyValuePair[additionalFields.length]; + + for (int i = 0; i < additionalFields.length; i++) { + ResolvableKeyValuePair resolvable = resolvableFields[i] = new ResolvableKeyValuePair(additionalFields[i]); + + // Validate + if (config == null && resolvable.valueNeedsLookup) { + throw new IllegalArgumentException("configuration needs to be set when there are additional fields with variables"); + } + } + + return resolvableFields; } /** @@ -199,7 +261,7 @@ abstract class AbstractJacksonLayout extends AbstractStringLayout { } } - protected Object wrapLogEvent(final LogEvent event) { + private static LogEvent convertMutableToLog4jEvent(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. @@ -208,9 +270,40 @@ abstract class AbstractJacksonLayout extends AbstractStringLayout { : event; } + protected Object wrapLogEvent(final LogEvent event) { + if (additionalFields.length > 0) { + // Construct map for serialization - note that we are intentionally using original LogEvent + Map<String, String> additionalFieldsMap = resolveAdditionalFields(event); + // This class combines LogEvent with AdditionalFields during serialization + return new LogEventWithAdditionalFields(event, additionalFieldsMap); + } else { + // No additional fields, return original object + return event; + } + } + + private Map<String,String> resolveAdditionalFields(LogEvent logEvent) { + // Note: LinkedHashMap retains order + final Map<String,String> additionalFieldsMap = new LinkedHashMap<>(); + final StrSubstitutor strSubstitutor = configuration.getStrSubstitutor(); + + // Go over each field + for (ResolvableKeyValuePair pair : additionalFields) { + if (pair.valueNeedsLookup) { + // Resolve value + additionalFieldsMap.put(pair.key, strSubstitutor.replace(logEvent, pair.value)); + } else { + // Plain text value + additionalFieldsMap.put(pair.key, pair.value); + } + } + + return additionalFieldsMap; + } + public void toSerializable(final LogEvent event, final Writer writer) throws JsonGenerationException, JsonMappingException, IOException { - objectWriter.writeValue(writer, wrapLogEvent(event)); + objectWriter.writeValue(writer, wrapLogEvent(convertMutableToLog4jEvent(event))); writer.write(eol); if (includeNullDelimiter) { writer.write('\0'); @@ -218,4 +311,40 @@ abstract class AbstractJacksonLayout extends AbstractStringLayout { markEvent(); } + @JsonRootName(XmlConstants.ELT_EVENT) + @JacksonXmlRootElement(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_EVENT) + public static class LogEventWithAdditionalFields { + + private final Object logEvent; + private final Map<String, String> additionalFields; + + public LogEventWithAdditionalFields(Object logEvent, Map<String, String> additionalFields) { + this.logEvent = logEvent; + this.additionalFields = additionalFields; + } + + @JsonUnwrapped + public Object getLogEvent() { + return logEvent; + } + + @JsonAnyGetter + @SuppressWarnings("unused") + public Map<String, String> getAdditionalFields() { + return additionalFields; + } + } + + protected static class ResolvableKeyValuePair { + + final String key; + final String value; + final boolean valueNeedsLookup; + + ResolvableKeyValuePair(KeyValuePair pair) { + this.key = pair.getKey(); + this.value = pair.getValue(); + this.valueNeedsLookup = AbstractJacksonLayout.valueNeedsLookup(this.value); + } + } } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/c371568b/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 1e9d431..40cf62c 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 @@ -24,8 +24,6 @@ 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; @@ -35,7 +33,6 @@ 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.lookup.StrSubstitutor; import org.apache.logging.log4j.core.util.KeyValuePair; /** @@ -78,8 +75,6 @@ public final class JsonLayout extends AbstractJacksonLayout { static final String CONTENT_TYPE = "application/json"; - protected final ResolvableKeyValuePair[] additionalFields; - public static class Builder<B extends Builder<B>> extends AbstractJacksonLayout.Builder<B> implements org.apache.logging.log4j.core.util.Builder<JsonLayout> { @@ -137,9 +132,7 @@ public final class JsonLayout extends AbstractJacksonLayout { charset, compact, complete, eventEol, PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(headerPattern).setDefaultPattern(DEFAULT_HEADER).build(), PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(footerPattern).setDefaultPattern(DEFAULT_FOOTER).build(), - false); - - this.additionalFields = new ResolvableKeyValuePair[0]; + false, null); } private JsonLayout(final Configuration config, final boolean locationInfo, final boolean properties, @@ -154,21 +147,8 @@ public final class JsonLayout extends AbstractJacksonLayout { 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 (additionalFields != null && additionalFields.length > 0) { - // Convert to specific class which already determines whether values needs lookup during serialization - final ResolvableKeyValuePair[] resolvableFields = new ResolvableKeyValuePair[additionalFields.length]; - - for (int i = 0; i < additionalFields.length; i++) { - resolvableFields[i] = new ResolvableKeyValuePair(additionalFields[i]); - } - - this.additionalFields = resolvableFields; - } else { - // No fields set - this.additionalFields = new ResolvableKeyValuePair[0]; - } + includeNullDelimiter, + additionalFields); } /** @@ -297,70 +277,4 @@ public final class JsonLayout extends AbstractJacksonLayout { } super.toSerializable(event, writer); } - - @Override - protected Object wrapLogEvent(LogEvent event) { - Object result = super.wrapLogEvent(event); - - if (additionalFields.length > 0) { - // Construct map for serialization - note that we are intentionally using original LogEvent - Map<String, String> additionalFieldsMap = resolveAdditionalFields(event); - // This class combines LogEvent with AdditionalFields during serialization - return new LogEventWithAdditionalFields(result, additionalFieldsMap); - } else { - // No additional fields, return original object - return result; - } - } - - private Map<String,String> resolveAdditionalFields(LogEvent logEvent) { - final Map<String,String> additionalFieldsMap = new LinkedHashMap<>(); - final StrSubstitutor strSubstitutor = configuration.getStrSubstitutor(); - - for (ResolvableKeyValuePair pair : additionalFields) { - if (pair.valueNeedsLookup) { - // Resolve value - additionalFieldsMap.put(pair.key, strSubstitutor.replace(logEvent, pair.value)); - } else { - // Plain text value - additionalFieldsMap.put(pair.key, pair.value); - } - } - - return additionalFieldsMap; - } - - public static class LogEventWithAdditionalFields { - - private final Object logEvent; - private final Map<String, String> additionalFields; - - public LogEventWithAdditionalFields(Object logEvent, Map<String, String> additionalFields) { - this.logEvent = logEvent; - this.additionalFields = additionalFields; - } - - @JsonUnwrapped - public Object getLogEvent() { - return logEvent; - } - - @JsonAnyGetter - public Map<String, String> getAdditionalFields() { - return additionalFields; - } - } - - private static class ResolvableKeyValuePair { - - final String key; - final String value; - final boolean valueNeedsLookup; - - ResolvableKeyValuePair(KeyValuePair pair) { - this.key = pair.getKey(); - this.value = pair.getValue(); - this.valueNeedsLookup = this.value != null && this.value.contains("${"); - } - } } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/c371568b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/XmlLayout.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/XmlLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/XmlLayout.java index 1e42649..190758a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/XmlLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/XmlLayout.java @@ -27,6 +27,7 @@ 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.PluginBuilderFactory; import org.apache.logging.log4j.core.jackson.XmlConstants; +import org.apache.logging.log4j.core.util.KeyValuePair; /** * Appends a series of {@code event} elements as defined in the <a href="log4j.dtd">log4j.dtd</a>. @@ -69,7 +70,7 @@ public final class XmlLayout extends AbstractJacksonLayout { public XmlLayout build() { return new XmlLayout(getConfiguration(), isLocationInfo(), isProperties(), isComplete(), isCompact(), getCharset(), isIncludeStacktrace(), isStacktraceAsString(), - isIncludeNullDelimiter()); + isIncludeNullDelimiter(), getAdditionalFields()); } } @@ -79,15 +80,18 @@ public final class XmlLayout extends AbstractJacksonLayout { @Deprecated protected XmlLayout(final boolean locationInfo, final boolean properties, final boolean complete, final boolean compact, final Charset charset, final boolean includeStacktrace) { - this(null, locationInfo, properties, complete, compact, charset, includeStacktrace, false, false); + this(null, locationInfo, properties, complete, compact, charset, includeStacktrace, false, false, null); } private XmlLayout(final Configuration config, final boolean locationInfo, final boolean properties, final boolean complete, final boolean compact, final Charset charset, final boolean includeStacktrace, final boolean stacktraceAsString, - final boolean includeNullDelimiter) { + final boolean includeNullDelimiter, + final KeyValuePair[] additionalFields) { super(config, new JacksonFactory.XML(includeStacktrace, stacktraceAsString).newWriter( - locationInfo, properties, compact), charset, compact, complete, false, null, null, includeNullDelimiter); + locationInfo, properties, compact), + charset, compact, complete, false, null, null, includeNullDelimiter, + additionalFields); } /** @@ -179,7 +183,7 @@ public final class XmlLayout extends AbstractJacksonLayout { final Charset charset, final boolean includeStacktrace) { return new XmlLayout(null, locationInfo, properties, complete, compact, charset, includeStacktrace, false, - false); + false, null); } @PluginBuilderFactory @@ -193,6 +197,6 @@ public final class XmlLayout extends AbstractJacksonLayout { * @return an XML Layout. */ public static XmlLayout createDefaultLayout() { - return new XmlLayout(null, false, false, false, false, StandardCharsets.UTF_8, true, false, false); + return new XmlLayout(null, false, false, false, false, StandardCharsets.UTF_8, true, false, false, null); } } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/c371568b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/YamlLayout.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/YamlLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/YamlLayout.java index 221819d..e8d92ae 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/YamlLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/YamlLayout.java @@ -27,6 +27,7 @@ import org.apache.logging.log4j.core.config.DefaultConfiguration; 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.PluginBuilderFactory; +import org.apache.logging.log4j.core.util.KeyValuePair; import org.apache.logging.log4j.util.Strings; /** @@ -61,7 +62,8 @@ public final class YamlLayout extends AbstractJacksonLayout { final String footerPattern = toStringOrNull(getFooter()); return new YamlLayout(getConfiguration(), isLocationInfo(), isProperties(), isComplete(), isCompact(), getEventEol(), headerPattern, footerPattern, getCharset(), - isIncludeStacktrace(), isStacktraceAsString(), isIncludeNullDelimiter()); + isIncludeStacktrace(), isStacktraceAsString(), isIncludeNullDelimiter(), + getAdditionalFields()); } } @@ -72,23 +74,25 @@ public final class YamlLayout extends AbstractJacksonLayout { protected YamlLayout(final Configuration config, final boolean locationInfo, final boolean properties, final boolean complete, final boolean compact, final boolean eventEol, final String headerPattern, final String footerPattern, final Charset charset, final boolean includeStacktrace) { - super(config, new JacksonFactory.YAML(includeStacktrace, false).newWriter(locationInfo, properties, compact), charset, compact, - complete, eventEol, + super(config, new JacksonFactory.YAML(includeStacktrace, false).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(), - false); + false, null); } private YamlLayout(final Configuration config, final boolean locationInfo, final boolean properties, 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) { - super(config, new JacksonFactory.YAML(includeStacktrace, stacktraceAsString).newWriter(locationInfo, properties, compact), charset, compact, - complete, eventEol, + final boolean includeNullDelimiter, + final KeyValuePair[] additionalFields) { + super(config, new JacksonFactory.YAML(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); + includeNullDelimiter, + additionalFields); } /** @@ -176,7 +180,7 @@ public final class YamlLayout extends AbstractJacksonLayout { final Charset charset, final boolean includeStacktrace) { return new YamlLayout(config, locationInfo, properties, false, false, true, headerPattern, footerPattern, - charset, includeStacktrace, false, false); + charset, includeStacktrace, false, false, null); } @PluginBuilderFactory @@ -191,6 +195,6 @@ public final class YamlLayout extends AbstractJacksonLayout { */ public static AbstractJacksonLayout createDefaultLayout() { return new YamlLayout(new DefaultConfiguration(), false, false, false, false, false, DEFAULT_HEADER, - DEFAULT_FOOTER, StandardCharsets.UTF_8, true, false, false); + DEFAULT_FOOTER, StandardCharsets.UTF_8, true, false, false, null); } } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/c371568b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/XmlLayoutTest.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/XmlLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/XmlLayoutTest.java index 4701b17..95c9f6d 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/XmlLayoutTest.java +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/XmlLayoutTest.java @@ -33,6 +33,8 @@ import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.ConfigurationFactory; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.jackson.Log4jXmlObjectMapper; +import org.apache.logging.log4j.core.lookup.JavaLookup; +import org.apache.logging.log4j.core.util.KeyValuePair; import org.apache.logging.log4j.junit.ThreadContextRule; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.spi.AbstractLogger; @@ -305,6 +307,23 @@ public class XmlLayoutTest { } @Test + public void testAdditionalFields() throws Exception { + final AbstractJacksonLayout layout = XmlLayout.newBuilder() + .setLocationInfo(false) + .setProperties(false) + .setIncludeStacktrace(false) + .setAdditionalFields(new KeyValuePair[] { + new KeyValuePair("KEY1", "VALUE1"), + new KeyValuePair("KEY2", "${java:runtime}"), }) + .setCharset(StandardCharsets.UTF_8) + .setConfiguration(ctx.getConfiguration()) + .build(); + final String str = layout.toSerializable(LogEventFixtures.createLogEvent()); + assertTrue(str, str.contains("<KEY1>VALUE1</KEY1>")); + assertTrue(str, str.contains("<KEY2>" + new JavaLookup().getRuntime() + "</KEY2>")); + } + + @Test public void testLocationOffCompactOffMdcOff() throws Exception { this.testAllFeatures(false, false, false, true); } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/c371568b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/YamlLayoutTest.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/YamlLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/YamlLayoutTest.java index 3531237..de19606 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/YamlLayoutTest.java +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/YamlLayoutTest.java @@ -36,6 +36,8 @@ import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationFactory; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.jackson.Log4jYamlObjectMapper; +import org.apache.logging.log4j.core.lookup.JavaLookup; +import org.apache.logging.log4j.core.util.KeyValuePair; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.spi.AbstractLogger; import org.apache.logging.log4j.test.appender.ListAppender; @@ -300,6 +302,23 @@ public class YamlLayoutTest { } @Test + public void testAdditionalFields() throws Exception { + final AbstractJacksonLayout layout = YamlLayout.newBuilder() + .setLocationInfo(false) + .setProperties(false) + .setIncludeStacktrace(false) + .setAdditionalFields(new KeyValuePair[] { + new KeyValuePair("KEY1", "VALUE1"), + new KeyValuePair("KEY2", "${java:runtime}"), }) + .setCharset(StandardCharsets.UTF_8) + .setConfiguration(ctx.getConfiguration()) + .build(); + final String str = layout.toSerializable(LogEventFixtures.createLogEvent()); + assertTrue(str, str.contains("KEY1: \"VALUE1\"")); + assertTrue(str, str.contains("KEY2: \"" + new JavaLookup().getRuntime() + "\"")); + } + + @Test public void testLocationOffCompactOffMdcOff() throws Exception { this.testAllFeatures(false, false, false, false, false, true); } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/c371568b/src/site/xdoc/manual/layouts.xml.vm ---------------------------------------------------------------------- diff --git a/src/site/xdoc/manual/layouts.xml.vm b/src/site/xdoc/manual/layouts.xml.vm index bef42b5..83c6e47 100644 --- a/src/site/xdoc/manual/layouts.xml.vm +++ b/src/site/xdoc/manual/layouts.xml.vm @@ -463,6 +463,16 @@ logger.debug("one={}, two={}, three={}", 1, 2, 3); <caption align="top">JsonLayout Parameters</caption> </table> <p> + To include any custom field in the output, use following syntax: + <pre class="prettyprint linenums"> + <JsonLayout> + <KeyValuePair key="additionalField1" value="constant value"/> + <KeyValuePair key="additionalField2" value="${dollar}${dollar}{ctx:key}"/> + </JsonLayout> +</pre> + Custom fields are always last, in the order they are declared. + </p> + <p> Additional <a href="../runtime-dependencies.html">runtime dependencies</a> are required for using JsonLayout. </p> </subsection> @@ -2202,6 +2212,16 @@ at org.apache.logging.log4j.core.pattern.ExtendedThrowableTest.testException(Ext <caption align="top">XmlLayout Parameters</caption> </table> <p> + To include any custom field in the output, use following syntax: + <pre class="prettyprint linenums"> + <YamlLayout> + <KeyValuePair key="additionalField1" value="constant value"/> + <KeyValuePair key="additionalField2" value="${dollar}${dollar}{ctx:key}"/> + </YamlLayout> +</pre> + Custom fields are always last, in the order they are declared. + </p> + <p> Additional <a href="../runtime-dependencies.html">runtime dependencies</a> are required for using XmlLayout. </p> </subsection> @@ -2297,6 +2317,16 @@ source: <caption align="top">YamlLayout Parameters</caption> </table> <p> + To include any custom field in the output, use following syntax: + <pre class="prettyprint linenums"> + <YamlLayout> + <KeyValuePair key="additionalField1" value="constant value"/> + <KeyValuePair key="additionalField2" value="${dollar}${dollar}{ctx:key}"/> + </YamlLayout> +</pre> + Custom fields are always last, in the order they are declared. + </p> + <p> Additional <a href="../runtime-dependencies.html">runtime dependencies</a> are required for using YamlLayout. </p> </subsection>
