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/21 23:07:01 UTC

[logging-log4j2] branch release-2.x updated: LOG4J2-3050 - Allow GelfLayout to skip fields that are null or empty

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

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


The following commit(s) were added to refs/heads/release-2.x by this push:
     new cee0a45  LOG4J2-3050 - Allow GelfLayout to skip fields that are null or empty
     new f03a728  Merge remote-tracking branch 'origin/release-2.x' into release-2.x
cee0a45 is described below

commit cee0a4581c06e46516158d0c382e02c3ca1b7c00
Author: Ralph Goers <rg...@apache.org>
AuthorDate: Sun Mar 21 15:35:13 2021 -0700

    LOG4J2-3050 - Allow GelfLayout to skip fields that are null or empty
---
 .../logging/log4j/core/layout/GelfLayout.java      | 46 +++++++++++++---------
 .../logging/log4j/core/layout/GelfLayout3Test.java | 13 ++++--
 log4j-core/src/test/resources/GelfLayout3Test.xml  |  3 +-
 src/changes/changes.xml                            |  3 ++
 src/site/xdoc/manual/layouts.xml.vm                |  7 ++++
 5 files changed, 49 insertions(+), 23 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 156c5d5..9f3bb4a 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
@@ -34,15 +34,15 @@ import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.core.Layout;
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.layout.internal.ExcludeChecker;
-import org.apache.logging.log4j.core.layout.internal.IncludeChecker;
-import org.apache.logging.log4j.core.layout.internal.ListChecker;
 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.PluginAttribute;
 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.layout.internal.ExcludeChecker;
+import org.apache.logging.log4j.core.layout.internal.IncludeChecker;
+import org.apache.logging.log4j.core.layout.internal.ListChecker;
 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
 import org.apache.logging.log4j.core.net.Severity;
 import org.apache.logging.log4j.core.util.JsonUtils;
@@ -108,6 +108,7 @@ public final class GelfLayout extends AbstractStringLayout {
     private final boolean includeMapMessage;
     private final boolean includeNullDelimiter;
     private final boolean includeNewLineDelimiter;
+    private final boolean omitEmptyFields;
     private final PatternLayout layout;
     private final FieldWriter mdcWriter;
     private final FieldWriter mapWriter;
@@ -155,6 +156,9 @@ public final class GelfLayout extends AbstractStringLayout {
         private boolean includeMapMessage = true;
 
         @PluginBuilderAttribute
+        private boolean omitEmptyFields = false;
+
+        @PluginBuilderAttribute
         private String messagePattern = null;
 
         @PluginBuilderAttribute
@@ -194,7 +198,8 @@ public final class GelfLayout extends AbstractStringLayout {
             }
             return new GelfLayout(getConfiguration(), host, additionalFields, compressionType, compressionThreshold,
                     includeStacktrace, includeThreadContext, includeMapMessage, includeNullDelimiter,
-                    includeNewLineDelimiter, mdcChecker, mapChecker, patternLayout, threadContextPrefix, mapPrefix);
+                    includeNewLineDelimiter, omitEmptyFields, mdcChecker, mapChecker, patternLayout,
+                    threadContextPrefix, mapPrefix);
         }
 
         private ListChecker createChecker(String excludes, String includes) {
@@ -439,14 +444,15 @@ public final class GelfLayout extends AbstractStringLayout {
     public GelfLayout(final String host, final KeyValuePair[] additionalFields, final CompressionType compressionType,
                       final int compressionThreshold, final boolean includeStacktrace) {
         this(null, host, additionalFields, compressionType, compressionThreshold, includeStacktrace, true, true,
-                false, false, null, null, null, "", "");
+                false, false, false, null, null, null, "", "");
     }
 
     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 includeMapMessage, final boolean includeNullDelimiter,
-            final boolean includeNewLineDelimiter, final ListChecker mdcChecker, final ListChecker mapChecker,
-            final PatternLayout patternLayout, final String mdcPrefix, final String mapPrefix) {
+            final boolean includeNewLineDelimiter, final boolean omitEmptyFields, final ListChecker mdcChecker,
+            final ListChecker mapChecker, final PatternLayout patternLayout, final String mdcPrefix,
+            final String mapPrefix) {
         super(config, StandardCharsets.UTF_8, null, null);
         this.host = host != null ? host : NetUtils.getLocalHostname();
         this.additionalFields = additionalFields != null ? additionalFields : new KeyValuePair[0];
@@ -464,6 +470,7 @@ public final class GelfLayout extends AbstractStringLayout {
         this.includeMapMessage = includeMapMessage;
         this.includeNullDelimiter = includeNullDelimiter;
         this.includeNewLineDelimiter = includeNewLineDelimiter;
+        this.omitEmptyFields = omitEmptyFields;
         if (includeNullDelimiter && compressionType != CompressionType.OFF) {
             throw new IllegalArgumentException("null delimiter cannot be used with compression");
         }
@@ -512,7 +519,7 @@ public final class GelfLayout extends AbstractStringLayout {
                 defaultBoolean = true) final boolean includeStacktrace) {
             // @formatter:on
         return new GelfLayout(null, host, additionalFields, compressionType, compressionThreshold, includeStacktrace,
-                true, true, false, false, null, null, null, "", "");
+                true, true, false, false, false, null, null, null, "", "");
     }
 
     @PluginBuilderFactory
@@ -597,14 +604,16 @@ public final class GelfLayout extends AbstractStringLayout {
         if (additionalFields.length > 0) {
             final StrSubstitutor strSubstitutor = getConfiguration().getStrSubstitutor();
             for (final KeyValuePair additionalField : additionalFields) {
-                builder.append(QU);
-                JsonUtils.quoteAsString(additionalField.getKey(), builder);
-                builder.append("\":\"");
                 final String value = valueNeedsLookup(additionalField.getValue())
-                    ? strSubstitutor.replace(event, additionalField.getValue())
-                    : additionalField.getValue();
-                JsonUtils.quoteAsString(toNullSafeString(value), builder);
-                builder.append(QC);
+                        ? strSubstitutor.replace(event, additionalField.getValue())
+                        : additionalField.getValue();
+                if (Strings.isNotEmpty(value) || !omitEmptyFields) {
+                    builder.append(QU);
+                    JsonUtils.quoteAsString(additionalField.getKey(), builder);
+                    builder.append("\":\"");
+                    JsonUtils.quoteAsString(toNullSafeString(value), builder);
+                    builder.append(QC);
+                }
             }
         }
         if (includeThreadContext) {
@@ -658,7 +667,7 @@ public final class GelfLayout extends AbstractStringLayout {
         return value != null && value.contains("${");
     }
 
-    private static class FieldWriter implements TriConsumer<String, Object, StringBuilder> {
+    private class FieldWriter implements TriConsumer<String, Object, StringBuilder> {
         private final ListChecker checker;
         private final String prefix;
 
@@ -669,11 +678,12 @@ public final class GelfLayout extends AbstractStringLayout {
 
         @Override
         public void accept(final String key, final Object value, final StringBuilder stringBuilder) {
-            if (checker.check(key)) {
+            String stringValue = String.valueOf(value);
+            if (checker.check(key) && (Strings.isNotEmpty(stringValue) || !omitEmptyFields)) {
                 stringBuilder.append(QU);
                 JsonUtils.quoteAsString(Strings.concat(prefix, key), stringBuilder);
                 stringBuilder.append("\":\"");
-                JsonUtils.quoteAsString(toNullSafeString(String.valueOf(value)), stringBuilder);
+                JsonUtils.quoteAsString(toNullSafeString(stringValue), stringBuilder);
                 stringBuilder.append(QC);
             }
         }
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 7bd367b..e7f1b1d 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
@@ -57,8 +57,10 @@ public class GelfLayout3Test {
         String message = json.get("full_message").asText();
         assertTrue(message.contains("loginId=rgoers"));
         assertTrue(message.contains("GelfLayout3Test"));
-        assertNull(json.get("arg1"));
-        assertNull(json.get("arg2"));
+        assertNull(json.get("_map.arg1"));
+        assertNull(json.get("_map.arg2"));
+        assertNull(json.get("_empty"));
+        assertEquals("FOO", json.get("_foo").asText());
     }
 
     @Test
@@ -69,13 +71,14 @@ public class GelfLayout3Test {
         ThreadContext.put("internalId", "12345");
         StringMapMessage message = new StringMapMessage();
         message.put("arg1", "test1");
+        message.put("arg2", "");
         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\"",
+        assertEquals("arg1=\"test1\" arg2=\"\" arg3=\"test3\" message=\"My Test Message\"",
                 json.get("short_message").asText());
         assertEquals("myhost", json.get("host").asText());
         assertNotNull(json.get("_mdc.loginId"));
@@ -86,9 +89,11 @@ public class GelfLayout3Test {
         assertTrue(msg.contains("loginId=rgoers"));
         assertTrue(msg.contains("GelfLayout3Test"));
         assertTrue(msg.contains("arg1=\"test1\""));
-        assertNull(json.get("map.arg2"));
+        assertNull(json.get("_map.arg2"));
         assertEquals("test1", json.get("_map.arg1").asText());
         assertEquals("test3", json.get("_map.arg3").asText());
+        assertNull(json.get("_empty"));
+        assertEquals("FOO", json.get("_foo").asText());
     }
 
 }
diff --git a/log4j-core/src/test/resources/GelfLayout3Test.xml b/log4j-core/src/test/resources/GelfLayout3Test.xml
index 6851ae1..d22d6e2 100644
--- a/log4j-core/src/test/resources/GelfLayout3Test.xml
+++ b/log4j-core/src/test/resources/GelfLayout3Test.xml
@@ -20,10 +20,11 @@
   <Appenders>
     <List name="list">
       <GelfLayout host="myhost" includeThreadContext="true" threadContextIncludes="requestId,loginId"
-                  mapMessageIncludes="arg1,arg2,arg3" threadContextPrefix="mdc." mapPrefix="map.">
+            mapMessageIncludes="arg1,arg2,arg3" threadContextPrefix="mdc." mapPrefix="map." omitEmptyFields="true">
         <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}"/>
+        <KeyValuePair key="empty" value="${test:-}"/>
       </GelfLayout>
     </List>
   </Appenders>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 0056253..088c844 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -30,6 +30,9 @@
          - "remove" - Removed
     -->
     <release version="2.15.0" date="2021-MM-DD" description="GA Release 2.15.0">
+      <action issue="LOG4J2-3050" dev="rgoers" type="add">
+        Allow AdditionalFields to be ignored if their value is null or a zero-length String.
+      </action>
       <action issue="LOG4J2-3049" dev="rgoers" type="add">
         Allow MapMessage and ThreadContext attributes to be prefixed.
       </action>
diff --git a/src/site/xdoc/manual/layouts.xml.vm b/src/site/xdoc/manual/layouts.xml.vm
index 87c381e..9e6916a 100644
--- a/src/site/xdoc/manual/layouts.xml.vm
+++ b/src/site/xdoc/manual/layouts.xml.vm
@@ -290,6 +290,13 @@ logger.debug("one={}, two={}, three={}", 1, 2, 3);
                 <a href="#PatternLayout">PatternLayout</a> for information on the pattern strings.</td>
             </tr>
             <tr>
+              <td>omitEmptyFields</td>
+              <td>boolean</td>
+              <td>If true fields which are null or are zero-length strings will not be included as a field in
+              the Gelf JSON. This setting will not affect whether those fields appear in the message fields. The
+              default value is false.</td>
+            </tr>
+            <tr>
               <td>patternSelector</td>
               <td>PatternSelector</td>
               <td>The PatternSelector to use to format the String. A messagePattern and patternSelector cannot both be