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">
+  &lt;JsonLayout&gt;
+    &lt;KeyValuePair key="additionalField1" value="constant value"/&gt;
+    &lt;KeyValuePair key="additionalField2" 
value="${dollar}${dollar}{ctx:key}"/&gt;
+  &lt;/JsonLayout&gt;
+</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">
+  &lt;YamlLayout&gt;
+    &lt;KeyValuePair key="additionalField1" value="constant value"/&gt;
+    &lt;KeyValuePair key="additionalField2" 
value="${dollar}${dollar}{ctx:key}"/&gt;
+  &lt;/YamlLayout&gt;
+</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">
+  &lt;YamlLayout&gt;
+    &lt;KeyValuePair key="additionalField1" value="constant value"/&gt;
+    &lt;KeyValuePair key="additionalField2" 
value="${dollar}${dollar}{ctx:key}"/&gt;
+  &lt;/YamlLayout&gt;
+</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>

Reply via email to