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;
+        }
+    }
 }

Reply via email to