You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by rg...@apache.org on 2021/03/20 22:32:01 UTC

[logging-log4j2] branch master updated: LOG4J2-3048 - Add improved MapMessge support to GelfLayout.

This is an automated email from the ASF dual-hosted git repository.

rgoers pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


The following commit(s) were added to refs/heads/master by this push:
     new 84a9772  LOG4J2-3048 - Add improved MapMessge support to GelfLayout.
84a9772 is described below

commit 84a9772091eb6fc57699a249f2558c6843074e48
Author: Ralph Goers <rg...@apache.org>
AuthorDate: Sat Mar 20 15:31:02 2021 -0700

    LOG4J2-3048 - Add improved MapMessge support to GelfLayout.
---
 .../logging/log4j/core/layout/GelfLayout.java      | 124 +++++++++++++++------
 .../logging/log4j/core/layout/GelfLayout3Test.java |  35 +++++-
 log4j-core/src/test/resources/GelfLayout3Test.xml  |   3 +-
 src/changes/changes.xml                            |   3 +
 src/site/asciidoc/manual/layouts.adoc              |  25 ++++-
 5 files changed, 150 insertions(+), 40 deletions(-)

diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java
index 38254f6..c035dcc 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java
@@ -29,6 +29,7 @@ import org.apache.logging.log4j.core.util.JsonUtils;
 import org.apache.logging.log4j.core.util.KeyValuePair;
 import org.apache.logging.log4j.core.util.NetUtils;
 import org.apache.logging.log4j.core.util.Patterns;
+import org.apache.logging.log4j.message.MapMessage;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.plugins.Node;
 import org.apache.logging.log4j.plugins.Plugin;
@@ -103,10 +104,12 @@ public final class GelfLayout extends AbstractStringLayout {
     private final String host;
     private final boolean includeStacktrace;
     private final boolean includeThreadContext;
+    private final boolean includeMapMessage;
     private final boolean includeNullDelimiter;
     private final boolean includeNewLineDelimiter;
     private final PatternLayout layout;
-    private final FieldWriter fieldWriter;
+    private final FieldWriter mdcWriter;
+    private final FieldWriter mapWriter;
 
     public static class Builder<B extends Builder<B>> extends AbstractStringLayout.Builder<B>
         implements org.apache.logging.log4j.plugins.util.Builder<GelfLayout> {
@@ -142,6 +145,15 @@ public final class GelfLayout extends AbstractStringLayout {
         private String threadContextExcludes = null;
 
         @PluginBuilderAttribute
+        private String mapMessageIncludes = null;
+
+        @PluginBuilderAttribute
+        private String mapMessageExcludes = null;
+
+        @PluginBuilderAttribute
+        private boolean includeMapMessage = true;
+
+        @PluginBuilderAttribute
         private String messagePattern = null;
 
         @PluginElement("PatternSelector")
@@ -154,30 +166,8 @@ public final class GelfLayout extends AbstractStringLayout {
 
         @Override
         public GelfLayout build() {
-            ListChecker checker = null;
-            if (threadContextExcludes != null) {
-                final String[] array = threadContextExcludes.split(Patterns.COMMA_SEPARATOR);
-                if (array.length > 0) {
-                    List<String> excludes = new ArrayList<>(array.length);
-                    for (final String str : array) {
-                        excludes.add(str.trim());
-                    }
-                    checker = new ExcludeChecker(excludes);
-                }
-            }
-            if (threadContextIncludes != null) {
-                final String[] array = threadContextIncludes.split(Patterns.COMMA_SEPARATOR);
-                if (array.length > 0) {
-                    List<String> includes = new ArrayList<>(array.length);
-                    for (final String str : array) {
-                        includes.add(str.trim());
-                    }
-                    checker = new IncludeChecker(includes);
-                }
-            }
-            if (checker == null) {
-                checker = ListChecker.NOOP_CHECKER;
-            }
+            ListChecker mdcChecker = createChecker(threadContextExcludes, threadContextIncludes);
+            ListChecker mapChecker = createChecker(mapMessageExcludes, mapMessageIncludes);
             PatternLayout patternLayout = null;
             if (messagePattern != null && patternSelector != null) {
                 LOGGER.error("A message pattern and PatternSelector cannot both be specified on GelfLayout, "
@@ -197,8 +187,36 @@ public final class GelfLayout extends AbstractStringLayout {
                         .build();
             }
             return new GelfLayout(getConfiguration(), host, additionalFields, compressionType, compressionThreshold,
-                    includeStacktrace, includeThreadContext, includeNullDelimiter, includeNewLineDelimiter, checker,
-                    patternLayout);
+                    includeStacktrace, includeThreadContext, includeMapMessage, includeNullDelimiter,
+                    includeNewLineDelimiter, mdcChecker, mapChecker, patternLayout);
+        }
+
+        private ListChecker createChecker(String excludes, String includes) {
+            ListChecker checker = null;
+            if (excludes != null) {
+                final String[] array = excludes.split(Patterns.COMMA_SEPARATOR);
+                if (array.length > 0) {
+                    List<String> excludeList = new ArrayList<>(array.length);
+                    for (final String str : array) {
+                        excludeList.add(str.trim());
+                    }
+                    checker = new ExcludeChecker(excludeList);
+                }
+            }
+            if (includes != null) {
+                final String[] array = includes.split(Patterns.COMMA_SEPARATOR);
+                if (array.length > 0) {
+                    List<String> includeList = new ArrayList<>(array.length);
+                    for (final String str : array) {
+                        includeList.add(str.trim());
+                    }
+                    checker = new IncludeChecker(includeList);
+                }
+            }
+            if (checker == null) {
+                checker = ListChecker.NOOP_CHECKER;
+            }
+            return checker;
         }
 
         public String getHost() {
@@ -352,12 +370,43 @@ public final class GelfLayout extends AbstractStringLayout {
             this.threadContextExcludes = mdcExcludes;
             return asBuilder();
         }
+
+        /**
+         * Whether to include MapMessage fields as additional fields (optional, default to true).
+         *
+         * @return this builder
+         */
+        public B setIncludeMapMessage(final boolean includeMapMessage) {
+            this.includeMapMessage = includeMapMessage;
+            return asBuilder();
+        }
+
+        /**
+         * A comma separated list of thread context keys to include;
+         * @param mapMessageIncludes the list of keys.
+         * @return this builder
+         */
+        public B setMapMessageIncludes(final String mapMessageIncludes) {
+            this.mapMessageIncludes = mapMessageIncludes;
+            return asBuilder();
+        }
+
+        /**
+         * A comma separated list of MapMessage keys to exclude;
+         * @param mapMessageExcludes the list of keys.
+         * @return this builder
+         */
+        public B setMapMessageExcludes(final String mapMessageExcludes) {
+            this.mapMessageExcludes = mapMessageExcludes;
+            return asBuilder();
+        }
     }
 
     private GelfLayout(final Configuration config, final String host, final KeyValuePair[] additionalFields,
             final CompressionType compressionType, final int compressionThreshold, final boolean includeStacktrace,
-            final boolean includeThreadContext, final boolean includeNullDelimiter, final boolean includeNewLineDelimiter,
-            final ListChecker listChecker, final PatternLayout patternLayout) {
+            final boolean includeThreadContext, final boolean includeMapMessage, final boolean includeNullDelimiter,
+            final boolean includeNewLineDelimiter, final ListChecker mdcChecker, final ListChecker mapChecker,
+            final PatternLayout patternLayout) {
         super(config, StandardCharsets.UTF_8, null, null);
         this.host = host != null ? host : NetUtils.getLocalHostname();
         this.additionalFields = additionalFields != null ? additionalFields : new KeyValuePair[0];
@@ -372,12 +421,14 @@ public final class GelfLayout extends AbstractStringLayout {
         this.compressionThreshold = compressionThreshold;
         this.includeStacktrace = includeStacktrace;
         this.includeThreadContext = includeThreadContext;
+        this.includeMapMessage = includeMapMessage;
         this.includeNullDelimiter = includeNullDelimiter;
+        this.includeNewLineDelimiter = includeNewLineDelimiter;
         if (includeNullDelimiter && compressionType != CompressionType.OFF) {
             throw new IllegalArgumentException("null delimiter cannot be used with compression");
         }
-        this.includeNewLineDelimiter = includeNewLineDelimiter;
-        this.fieldWriter = new FieldWriter(listChecker);
+        this.mdcWriter = new FieldWriter(mdcChecker);
+        this.mapWriter = new FieldWriter(mapChecker);
         this.layout = patternLayout;
     }
 
@@ -391,10 +442,14 @@ public final class GelfLayout extends AbstractStringLayout {
         sb.append(", includeThreadContext=").append(includeThreadContext);
         sb.append(", includeNullDelimiter=").append(includeNullDelimiter);
         sb.append(", includeNewLineDelimiter=").append(includeNewLineDelimiter);
-        String threadVars = fieldWriter.getChecker().toString();
+        String threadVars = mdcWriter.getChecker().toString();
         if (threadVars.length() > 0) {
             sb.append(", ").append(threadVars);
         }
+        String mapVars = mapWriter.getChecker().toString();
+        if (mapVars.length() > 0) {
+            sb.append(", ").append(mapVars);
+        }
         if (layout != null) {
             sb.append(", PatternLayout{").append(layout.toString()).append("}");
         }
@@ -494,7 +549,10 @@ public final class GelfLayout extends AbstractStringLayout {
             }
         }
         if (includeThreadContext) {
-            event.getContextData().forEach(fieldWriter, builder);
+            event.getContextData().forEach(mdcWriter, builder);
+        }
+        if (includeMapMessage && event.getMessage() instanceof MapMessage) {
+            ((MapMessage<?, Object>) event.getMessage()).forEach((key, value) -> mapWriter.accept(key, value, builder));
         }
 
         if (event.getThrown() != null || layout != null) {
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayout3Test.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayout3Test.java
index 6ebd27c..f2f15f8 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayout3Test.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayout3Test.java
@@ -24,6 +24,7 @@ import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.junit.LoggerContextSource;
 import org.apache.logging.log4j.junit.Named;
 import org.apache.logging.log4j.junit.UsingAnyThreadContext;
+import org.apache.logging.log4j.message.StringMapMessage;
 import org.apache.logging.log4j.test.appender.ListAppender;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
@@ -54,8 +55,38 @@ public class GelfLayout3Test {
         assertNull(json.get("_internalId"));
         assertNull(json.get("_requestId"));
         String message = json.get("full_message").asText();
-        assertTrue(message.contains("loginId=rgoers"));
-        assertTrue(message.contains("GelfLayout3Test"));
+        assertNull(json.get("arg1"));
+        assertNull(json.get("arg2"));
+    }
+
+    @Test
+    public void mapMessage(final LoggerContext context, @Named final ListAppender list) throws IOException {
+        list.clear();
+        final Logger logger = context.getLogger(getClass());
+        ThreadContext.put("loginId", "rgoers");
+        ThreadContext.put("internalId", "12345");
+        StringMapMessage message = new StringMapMessage();
+        message.put("arg1", "test1");
+        message.put("arg3", "test3");
+        message.put("message", "My Test Message");
+        logger.info(message);
+        final String gelf = list.getMessages().get(0);
+        final ObjectMapper mapper = new ObjectMapper();
+        final JsonNode json = mapper.readTree(gelf);
+        assertEquals("arg1=\"test1\" arg3=\"test3\" message=\"My Test Message\"",
+                json.get("short_message").asText());
+        assertEquals("myhost", json.get("host").asText());
+        assertNotNull(json.get("_loginId"));
+        assertEquals("rgoers", json.get("_loginId").asText());
+        assertNull(json.get("_internalId"));
+        assertNull(json.get("_requestId"));
+        String msg = json.get("full_message").asText();
+        assertTrue(msg.contains("loginId=rgoers"));
+        assertTrue(msg.contains("GelfLayout3Test"));
+        assertTrue(msg.contains("arg1=\"test1\""));
+        assertNull(json.get("arg2"));
+        assertEquals("test1", json.get("_arg1").asText());
+        assertEquals("test3", json.get("_arg3").asText());
     }
 
 }
diff --git a/log4j-core/src/test/resources/GelfLayout3Test.xml b/log4j-core/src/test/resources/GelfLayout3Test.xml
index fcb23d3..e985737 100644
--- a/log4j-core/src/test/resources/GelfLayout3Test.xml
+++ b/log4j-core/src/test/resources/GelfLayout3Test.xml
@@ -19,7 +19,8 @@
 <Configuration status="WARN" name="GelfLayoutTest3">
   <Appenders>
     <List name="list">
-      <GelfLayout host="myhost" includeThreadContext="true" threadContextIncludes="requestId,loginId">
+      <GelfLayout host="myhost" includeThreadContext="true" threadContextIncludes="requestId,loginId"
+                  mapMessageIncludes="arg1,arg2,arg3">
         <MessagePattern>%d [%t] %-5p %X{requestId, loginId} %C{1.}.%M:%L - %m%n"</MessagePattern>
         <KeyValuePair key="foo" value="FOO"/>
         <KeyValuePair key="runtime" value="$${java:runtime}"/>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 631e34e..d6a03a3 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -169,6 +169,9 @@
       </action>
     </release>
     <release version="2.15.0" date="2021-MM-DD" description="GA Release 2.15.0">
+      <action issue="LOG4J2=3048" dev="rgoers" type="add">
+        Add improved MapMessge support to GelfLayout.
+      </action>
       <action issue="LOG4J2-3044" dev="rgoers" type="add">
         Add RepeatPatternConverter.
       </action>
diff --git a/src/site/asciidoc/manual/layouts.adoc b/src/site/asciidoc/manual/layouts.adoc
index efeacd6..03dc6c5 100644
--- a/src/site/asciidoc/manual/layouts.adoc
+++ b/src/site/asciidoc/manual/layouts.adoc
@@ -210,6 +210,15 @@ Configure as follows to send to a Graylog 2.x server with TCP:
 |int
 |Compress if data is larger than this number of bytes (optional, defaults to 1024)
 
+|includeMapMessage
+|boolean
+|Whether to include fields from MapMessages as additional fields (optional, default to true).
+
+|includeNullDelimiter
+|boolean
+|Whether to include NULL byte as delimiter after each event (optional, default to false).
+Useful for Graylog GELF TCP input. Cannot be used with compression.
+
 |includeStacktrace
 |boolean
 |Whether to include full stacktrace of logged Throwables (optional, default to true).
@@ -221,10 +230,18 @@ will be included.
 |boolean
 |Whether to include thread context as additional fields (optional, default to true).
 
-|includeNullDelimiter
-|boolean
-|Whether to include NULL byte as delimiter after each event (optional, default to false).
-Useful for Graylog GELF TCP input. Cannot be used with compression.
+|mapMessageExcludes
+|String
+|A comma separated list of attributes from the MapMessage to exclude when formatting the event. This
+attribute only applies when includeMapMessage="true" is specified. If mapMessageIncludes
+are also specified this attribute will be ignored.
+
+|mapMessageIncludes
+|String
+|A comma separated list of attributes from the MapMessageto include when formatting the event. This
+attribute only applies when includeMapMessage="true" is specified. If mapMessageExcludes
+are also specified this attribute will override them. MapMessage fields specified here that
+have no value will be omitted.
 
 |messagePattern
 |String