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:07 UTC
[logging-log4j2] branch release-2.x 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 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 e9171dc LOG4J2-3048 - Add improved MapMessge support to GelfLayout.
e9171dc is described below
commit e9171dccbc161df7ae68a6ab0f5c918a5bc71d26
Author: Ralph Goers <rg...@apache.org>
AuthorDate: Sat Mar 20 15:31:39 2021 -0700
LOG4J2-3048 - Add improved MapMessge support to GelfLayout.
---
.../logging/log4j/core/layout/GelfLayout.java | 129 +++++++++++++++------
.../logging/log4j/core/layout/GelfLayout3Test.java | 33 ++++++
log4j-core/src/test/resources/GelfLayout3Test.xml | 3 +-
src/changes/changes.xml | 3 +
src/site/xdoc/manual/layouts.xml.vm | 28 ++++-
5 files changed, 156 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 43622e6..85fc942 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
@@ -49,6 +49,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.status.StatusLogger;
import org.apache.logging.log4j.util.StringBuilderFormattable;
@@ -104,10 +105,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.core.util.Builder<GelfLayout> {
@@ -143,6 +146,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,6 +370,36 @@ 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();
+ }
}
/**
@@ -360,14 +408,15 @@ public final class GelfLayout extends AbstractStringLayout {
@Deprecated
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, false, false,
- null, null);
+ this(null, host, additionalFields, compressionType, compressionThreshold, includeStacktrace, true, true,
+ 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 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];
@@ -382,12 +431,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.fieldWriter = new FieldWriter(listChecker);
+ this.mdcWriter = new FieldWriter(mdcChecker);
+ this.mapWriter = new FieldWriter(mapChecker);
this.layout = patternLayout;
}
@@ -401,10 +452,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("}");
}
@@ -427,7 +482,8 @@ public final class GelfLayout extends AbstractStringLayout {
defaultBoolean = true) final boolean includeStacktrace) {
// @formatter:on
return new GelfLayout(null, host, additionalFields, compressionType, compressionThreshold, includeStacktrace,
- true, false, false, null, null);
+ true, true, false, false,
+ null, null, null);
}
@PluginBuilderFactory
@@ -523,7 +579,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..d2452d4 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;
@@ -56,6 +57,38 @@ 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"));
+ }
+
+ @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 b2039cc..13ebeef 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=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/xdoc/manual/layouts.xml.vm b/src/site/xdoc/manual/layouts.xml.vm
index 0106562..a004d8d 100644
--- a/src/site/xdoc/manual/layouts.xml.vm
+++ b/src/site/xdoc/manual/layouts.xml.vm
@@ -238,6 +238,17 @@ logger.debug("one={}, two={}, three={}", 1, 2, 3);
<td>Compress if data is larger than this number of bytes (optional, defaults to 1024)</td>
</tr>
<tr>
+ <td>includeMapMessage</td>
+ <td>boolean</td>
+ <td>Whether to include fields from MapMessages as additional fields (optional, default to true).</td>
+ </tr>
+ <tr>
+ <td>includeNullDelimiter</td>
+ <td>boolean</td>
+ <td>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.</td>
+ </tr>
+ <tr>
<td>includeStacktrace</td>
<td>boolean</td>
<td>Whether to include full stacktrace of logged Throwables (optional, default to true).
@@ -250,10 +261,19 @@ logger.debug("one={}, two={}, three={}", 1, 2, 3);
<td>Whether to include thread context as additional fields (optional, default to true).</td>
</tr>
<tr>
- <td>includeNullDelimiter</td>
- <td>boolean</td>
- <td>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.</td>
+ <td>mapMessageExcludes</td>
+ <td>String</td>
+ <td>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.</td>
+ </tr>
+ <tr>
+ <td>mapMessageIncludes</td>
+ <td>String</td>
+ <td>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.</td>
</tr>
<tr>
<td>messagePattern</td>