You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by vy...@apache.org on 2020/11/24 15:07:04 UTC
[logging-log4j2] branch release-2.x updated: LOG4J2-2962 Enrich
"map" resolver by unifying its backend with "mdc" resolver.
This is an automated email from the ASF dual-hosted git repository.
vy 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 a71403e LOG4J2-2962 Enrich "map" resolver by unifying its backend with "mdc" resolver.
a71403e is described below
commit a71403ee2e5694a2699d68ddf09e8df6ccfc8b57
Author: Volkan Yazıcı <vo...@gmail.com>
AuthorDate: Tue Nov 17 22:54:19 2020 +0100
LOG4J2-2962 Enrich "map" resolver by unifying its backend with "mdc" resolver.
---
.../layout/template/json/resolver/MapResolver.java | 74 ++---
.../template/json/resolver/MapResolverFactory.java | 2 +-
...esolver.java => ReadOnlyStringMapResolver.java} | 121 +++++---
.../json/resolver/ThreadContextDataResolver.java | 325 +--------------------
.../template/json/JsonTemplateLayoutTest.java | 179 ++++++++++--
src/changes/changes.xml | 3 +
.../asciidoc/manual/json-template-layout.adoc.vm | 260 ++++++++---------
7 files changed, 375 insertions(+), 589 deletions(-)
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MapResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MapResolver.java
index f26bf86..5cc07eb 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MapResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MapResolver.java
@@ -17,75 +17,35 @@
package org.apache.logging.log4j.layout.template.json.resolver;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
import org.apache.logging.log4j.message.MapMessage;
import org.apache.logging.log4j.message.Message;
-import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
+import org.apache.logging.log4j.util.ReadOnlyStringMap;
/**
- * {@link MapMessage} field resolver.
+ * {@link MapMessage} resolver.
*
- * <h3>Configuration</h3>
- *
- * <pre>
- * config = key , [ stringified ]
- * key = "key" -> string
- * stringified = "stringified" -> boolean
- * </pre>
- *
- * <h3>Examples</h3>
- *
- * Resolve the <tt>userRole</tt> field of the message:
- *
- * <pre>
- * {
- * "$resolver": "map",
- * "key": "userRole"
- * }
- * </pre>
+ * @see ReadOnlyStringMapResolver
*/
-final class MapResolver implements EventResolver {
-
- private final String key;
+final class MapResolver extends ReadOnlyStringMapResolver {
- private final boolean stringified;
-
- static String getName() {
- return "map";
+ MapResolver(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ super(context, config, MapResolver::toMap);
}
- MapResolver(final TemplateResolverConfig config) {
- this.key = config.getString("key");
- this.stringified = config.getBoolean("stringified", false);
- if (key == null) {
- throw new IllegalArgumentException("missing key: " + config);
- }
- }
-
- @Override
- public boolean isResolvable(final LogEvent logEvent) {
- return logEvent.getMessage() instanceof MapMessage;
- }
-
- @Override
- public void resolve(
- final LogEvent logEvent,
- final JsonWriter jsonWriter) {
+ private static ReadOnlyStringMap toMap(final LogEvent logEvent) {
final Message message = logEvent.getMessage();
if (!(message instanceof MapMessage)) {
- jsonWriter.writeNull();
- } else {
- @SuppressWarnings("unchecked")
- MapMessage<?, Object> mapMessage = (MapMessage<?, Object>) message;
- final IndexedReadOnlyStringMap map = mapMessage.getIndexedReadOnlyStringMap();
- final Object value = map.getValue(key);
- if (stringified) {
- final String stringifiedValue = String.valueOf(value);
- jsonWriter.writeString(stringifiedValue);
- } else {
- jsonWriter.writeValue(value);
- }
+ return null;
}
+ @SuppressWarnings("unchecked")
+ final MapMessage<?, Object> mapMessage = (MapMessage<?, Object>) message;
+ return mapMessage.getIndexedReadOnlyStringMap();
+ }
+
+ static String getName() {
+ return "map";
}
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MapResolverFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MapResolverFactory.java
index a639719..53092c9 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MapResolverFactory.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MapResolverFactory.java
@@ -35,7 +35,7 @@ final class MapResolverFactory implements EventResolverFactory<MapResolver> {
public MapResolver create(
final EventResolverContext context,
final TemplateResolverConfig config) {
- return new MapResolver(config);
+ return new MapResolver(context, config);
}
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadContextDataResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java
similarity index 72%
copy from log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadContextDataResolver.java
copy to log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java
index dda41d3..3735017 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadContextDataResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java
@@ -24,10 +24,11 @@ import org.apache.logging.log4j.util.ReadOnlyStringMap;
import org.apache.logging.log4j.util.TriConsumer;
import java.util.Map;
+import java.util.function.Function;
import java.util.regex.Pattern;
/**
- * Mapped Diagnostic Context (MDC), aka. Thread Context Data, resolver.
+ * {@link ReadOnlyStringMap} resolver.
*
* <h3>Configuration</h3>
*
@@ -45,87 +46,111 @@ import java.util.regex.Pattern;
* flattenPrefix = "prefix" -> string
* </pre>
*
- * Note that <tt>singleAccess</tt> resolves the MDC value as is, whilst
- * <tt>multiAccess</tt> resolves a multitude of MDC values. If <tt>flatten</tt>
- * is provided, <tt>multiAccess</tt> merges the values with the parent,
+ * Note that <tt>singleAccess</tt> resolves a single field, whilst
+ * <tt>multiAccess</tt> resolves a multitude of fields. If <tt>flatten</tt>
+ * is provided, <tt>multiAccess</tt> merges the fields with the parent,
* otherwise creates a new JSON object containing the values.
+ * <p>
+ * Enabling <tt>stringified</tt> flag converts each value to its string
+ * representation.
+ * <p>
+ * Regex provided in the `pattern` is used to match against the keys.
+ *
+ * <h3>Garbage Footprint</h3>
+ *
+ * <tt>stringified</tt> allocates a new <tt>String</tt> for values that are not
+ * of type <tt>String</tt>.
+ * <p>
+ * Writing certain non-primitive values (e.g., <tt>BigDecimal</tt>,
+ * <tt>Set</tt>, etc.) to JSON generates garbage, though most (e.g.,
+ * <tt>int</tt>, <tt>long</tt>, <tt>String</tt>, <tt>List</tt>,
+ * <tt>boolean[]</tt>, etc.) don't.
*
* <h3>Examples</h3>
*
- * Resolve the <tt>userRole</tt> MDC value:
+ * <tt>"$resolver"</tt> is left out in the following examples, since it is to be
+ * defined by the actual resolver, e.g., {@link MapResolver},
+ * {@link ThreadContextDataResolver}.
+ * <p>
+ * Resolve the value of the field keyed with <tt>userRole</tt>:
*
* <pre>
* {
- * "$resolver": "mdc",
+ * "$resolver": "…",
* "key": "userRole"
* }
* </pre>
*
- * Resolve the string representation of the <tt>userRank</tt> MDC value:
+ * Resolve the string representation of the <tt>userRank</tt> field value:
*
* <pre>
* {
- * "$resolver": "mdc",
+ * "$resolver": "…",
* "key": "userRank",
* "stringified": true
* }
* </pre>
*
- * Resolve all MDC entries into an object:
+ * Resolve all fields into an object:
*
* <pre>
* {
- * "$resolver": "mdc"
+ * "$resolver": "…"
* }
* </pre>
*
- * Resolve all MDC entries into an object such that values are converted to
+ * Resolve all fields into an object such that values are converted to
* string:
*
* <pre>
* {
- * "$resolver": "mdc",
+ * "$resolver": "…",
* "stringified": true
* }
* </pre>
*
- * Merge all MDC entries whose keys are matching with the
+ * Merge all fields whose keys are matching with the
* <tt>user(Role|Rank)</tt> regex into the parent:
*
* <pre>
* {
- * "$resolver": "mdc",
+ * "$resolver": "…",
* "flatten": true,
* "pattern": "user(Role|Rank)"
* }
* </pre>
*
- * After converting the corresponding entries to string, merge all MDC entries
+ * After converting the corresponding field values to string, merge all fields
* to parent such that keys are prefixed with <tt>_</tt>:
*
* <pre>
* {
- * "$resolver": "mdc",
+ * "$resolver": "…",
* "stringified": true,
* "flatten": {
* "prefix": "_"
* }
* }
* </pre>
+ *
+ * @see MapResolver
+ * @see ThreadContextDataResolver
*/
-final class ThreadContextDataResolver implements EventResolver {
+class ReadOnlyStringMapResolver implements EventResolver {
private final EventResolver internalResolver;
- ThreadContextDataResolver(
+ ReadOnlyStringMapResolver(
final EventResolverContext context,
- final TemplateResolverConfig config) {
- this.internalResolver = createResolver(context, config);
+ final TemplateResolverConfig config,
+ final Function<LogEvent, ReadOnlyStringMap> mapAccessor) {
+ this.internalResolver = createResolver(context, config, mapAccessor);
}
private static EventResolver createResolver(
final EventResolverContext context,
- final TemplateResolverConfig config) {
+ final TemplateResolverConfig config,
+ final Function<LogEvent, ReadOnlyStringMap> mapAccessor) {
final Object flattenObject = config.getObject("flatten");
final boolean flatten;
if (flattenObject == null) {
@@ -146,28 +171,35 @@ final class ThreadContextDataResolver implements EventResolver {
throw new IllegalArgumentException(
"both key and flatten options cannot be supplied: " + config);
}
- return createKeyResolver(key, stringified);
+ return createKeyResolver(key, stringified, mapAccessor);
} else {
final RecyclerFactory recyclerFactory = context.getRecyclerFactory();
- return createResolver(recyclerFactory, flatten, prefix, pattern, stringified);
+ return createResolver(
+ recyclerFactory,
+ flatten,
+ prefix,
+ pattern,
+ stringified,
+ mapAccessor);
}
}
private static EventResolver createKeyResolver(
final String key,
- final boolean stringified) {
+ final boolean stringified,
+ final Function<LogEvent, ReadOnlyStringMap> mapAccessor) {
return new EventResolver() {
@Override
public boolean isResolvable(final LogEvent logEvent) {
- final ReadOnlyStringMap contextData = logEvent.getContextData();
- return contextData != null && contextData.containsKey(key);
+ final ReadOnlyStringMap map = mapAccessor.apply(logEvent);
+ return map != null && map.containsKey(key);
}
@Override
public void resolve(final LogEvent logEvent, final JsonWriter jsonWriter) {
- final ReadOnlyStringMap contextData = logEvent.getContextData();
- final Object value = contextData == null ? null : contextData.getValue(key);
+ final ReadOnlyStringMap map = mapAccessor.apply(logEvent);
+ final Object value = map == null ? null : map.getValue(key);
if (stringified) {
final String valueString = String.valueOf(value);
jsonWriter.writeString(valueString);
@@ -184,7 +216,8 @@ final class ThreadContextDataResolver implements EventResolver {
final boolean flatten,
final String prefix,
final String pattern,
- final boolean stringified) {
+ final boolean stringified,
+ final Function<LogEvent, ReadOnlyStringMap> mapAccessor) {
// Compile the pattern.
final Pattern compiledPattern =
@@ -206,13 +239,14 @@ final class ThreadContextDataResolver implements EventResolver {
});
// Create the resolver.
- return createResolver(flatten, loopContextRecycler);
+ return createResolver(flatten, loopContextRecycler, mapAccessor);
}
private static EventResolver createResolver(
final boolean flatten,
- final Recycler<LoopContext> loopContextRecycler) {
+ final Recycler<LoopContext> loopContextRecycler,
+ final Function<LogEvent, ReadOnlyStringMap> mapAccessor) {
return new EventResolver() {
@Override
@@ -222,8 +256,8 @@ final class ThreadContextDataResolver implements EventResolver {
@Override
public boolean isResolvable(final LogEvent logEvent) {
- final ReadOnlyStringMap contextData = logEvent.getContextData();
- return contextData != null && !contextData.isEmpty();
+ final ReadOnlyStringMap map = mapAccessor.apply(logEvent);
+ return map != null && !map.isEmpty();
}
@Override
@@ -237,16 +271,16 @@ final class ThreadContextDataResolver implements EventResolver {
final JsonWriter jsonWriter,
final boolean succeedingEntry) {
- // Retrieve the context data.
- final ReadOnlyStringMap contextData = logEvent.getContextData();
- if (contextData == null || contextData.isEmpty()) {
+ // Retrieve the map.
+ final ReadOnlyStringMap map = mapAccessor.apply(logEvent);
+ if (map == null || map.isEmpty()) {
if (!flatten) {
jsonWriter.writeNull();
}
return;
}
- // Resolve the context data.
+ // Resolve the map.
if (!flatten) {
jsonWriter.writeObjectStart();
}
@@ -255,7 +289,7 @@ final class ThreadContextDataResolver implements EventResolver {
loopContext.initJsonWriterStringBuilderLength = jsonWriter.getStringBuilder().length();
loopContext.succeedingEntry = flatten && succeedingEntry;
try {
- contextData.forEach(LoopMethod.INSTANCE, loopContext);
+ map.forEach(LoopMethod.INSTANCE, loopContext);
} finally {
loopContextRecycler.release(loopContext);
}
@@ -286,9 +320,9 @@ final class ThreadContextDataResolver implements EventResolver {
}
- private static final class LoopMethod implements TriConsumer<String, Object, LoopContext> {
+ private enum LoopMethod implements TriConsumer<String, Object, LoopContext> {
- private static final LoopMethod INSTANCE = new LoopMethod();
+ INSTANCE;
@Override
public void accept(
@@ -324,10 +358,6 @@ final class ThreadContextDataResolver implements EventResolver {
}
- static String getName() {
- return "mdc";
- }
-
@Override
public boolean isFlattening() {
return internalResolver.isFlattening();
@@ -335,8 +365,7 @@ final class ThreadContextDataResolver implements EventResolver {
@Override
public boolean isResolvable(final LogEvent logEvent) {
- final ReadOnlyStringMap contextData = logEvent.getContextData();
- return contextData != null && !contextData.isEmpty();
+ return internalResolver.isResolvable(logEvent);
}
@Override
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadContextDataResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadContextDataResolver.java
index dda41d3..95e3677 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadContextDataResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ThreadContextDataResolver.java
@@ -17,341 +17,22 @@
package org.apache.logging.log4j.layout.template.json.resolver;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
-import org.apache.logging.log4j.layout.template.json.util.Recycler;
-import org.apache.logging.log4j.layout.template.json.util.RecyclerFactory;
-import org.apache.logging.log4j.util.ReadOnlyStringMap;
-import org.apache.logging.log4j.util.TriConsumer;
-
-import java.util.Map;
-import java.util.regex.Pattern;
/**
* Mapped Diagnostic Context (MDC), aka. Thread Context Data, resolver.
*
- * <h3>Configuration</h3>
- *
- * <pre>
- * config = singleAccess | multiAccess
- *
- * singleAccess = key , [ stringified ]
- * key = "key" -> string
- * stringified = "stringified" -> boolean
- *
- * multiAccess = [ pattern ] , [ flatten ] , [ stringified ]
- * pattern = "pattern" -> string
- * flatten = "flatten" -> ( boolean | flattenConfig )
- * flattenConfig = [ flattenPrefix ]
- * flattenPrefix = "prefix" -> string
- * </pre>
- *
- * Note that <tt>singleAccess</tt> resolves the MDC value as is, whilst
- * <tt>multiAccess</tt> resolves a multitude of MDC values. If <tt>flatten</tt>
- * is provided, <tt>multiAccess</tt> merges the values with the parent,
- * otherwise creates a new JSON object containing the values.
- *
- * <h3>Examples</h3>
- *
- * Resolve the <tt>userRole</tt> MDC value:
- *
- * <pre>
- * {
- * "$resolver": "mdc",
- * "key": "userRole"
- * }
- * </pre>
- *
- * Resolve the string representation of the <tt>userRank</tt> MDC value:
- *
- * <pre>
- * {
- * "$resolver": "mdc",
- * "key": "userRank",
- * "stringified": true
- * }
- * </pre>
- *
- * Resolve all MDC entries into an object:
- *
- * <pre>
- * {
- * "$resolver": "mdc"
- * }
- * </pre>
- *
- * Resolve all MDC entries into an object such that values are converted to
- * string:
- *
- * <pre>
- * {
- * "$resolver": "mdc",
- * "stringified": true
- * }
- * </pre>
- *
- * Merge all MDC entries whose keys are matching with the
- * <tt>user(Role|Rank)</tt> regex into the parent:
- *
- * <pre>
- * {
- * "$resolver": "mdc",
- * "flatten": true,
- * "pattern": "user(Role|Rank)"
- * }
- * </pre>
- *
- * After converting the corresponding entries to string, merge all MDC entries
- * to parent such that keys are prefixed with <tt>_</tt>:
- *
- * <pre>
- * {
- * "$resolver": "mdc",
- * "stringified": true,
- * "flatten": {
- * "prefix": "_"
- * }
- * }
- * </pre>
+ * @see ReadOnlyStringMapResolver
*/
-final class ThreadContextDataResolver implements EventResolver {
-
- private final EventResolver internalResolver;
+final class ThreadContextDataResolver extends ReadOnlyStringMapResolver {
ThreadContextDataResolver(
final EventResolverContext context,
final TemplateResolverConfig config) {
- this.internalResolver = createResolver(context, config);
- }
-
- private static EventResolver createResolver(
- final EventResolverContext context,
- final TemplateResolverConfig config) {
- final Object flattenObject = config.getObject("flatten");
- final boolean flatten;
- if (flattenObject == null) {
- flatten = false;
- } else if (flattenObject instanceof Boolean) {
- flatten = (boolean) flattenObject;
- } else if (flattenObject instanceof Map) {
- flatten = true;
- } else {
- throw new IllegalArgumentException("invalid flatten option: " + config);
- }
- final String key = config.getString("key");
- final String prefix = config.getString(new String[] {"flatten", "prefix"});
- final String pattern = config.getString("pattern");
- final boolean stringified = config.getBoolean("stringified", false);
- if (key != null) {
- if (flatten) {
- throw new IllegalArgumentException(
- "both key and flatten options cannot be supplied: " + config);
- }
- return createKeyResolver(key, stringified);
- } else {
- final RecyclerFactory recyclerFactory = context.getRecyclerFactory();
- return createResolver(recyclerFactory, flatten, prefix, pattern, stringified);
- }
- }
-
- private static EventResolver createKeyResolver(
- final String key,
- final boolean stringified) {
- return new EventResolver() {
-
- @Override
- public boolean isResolvable(final LogEvent logEvent) {
- final ReadOnlyStringMap contextData = logEvent.getContextData();
- return contextData != null && contextData.containsKey(key);
- }
-
- @Override
- public void resolve(final LogEvent logEvent, final JsonWriter jsonWriter) {
- final ReadOnlyStringMap contextData = logEvent.getContextData();
- final Object value = contextData == null ? null : contextData.getValue(key);
- if (stringified) {
- final String valueString = String.valueOf(value);
- jsonWriter.writeString(valueString);
- } else {
- jsonWriter.writeValue(value);
- }
- }
-
- };
- }
-
- private static EventResolver createResolver(
- final RecyclerFactory recyclerFactory,
- final boolean flatten,
- final String prefix,
- final String pattern,
- final boolean stringified) {
-
- // Compile the pattern.
- final Pattern compiledPattern =
- pattern == null
- ? null
- : Pattern.compile(pattern);
-
- // Create the recycler for the loop context.
- final Recycler<LoopContext> loopContextRecycler =
- recyclerFactory.create(() -> {
- final LoopContext loopContext = new LoopContext();
- if (prefix != null) {
- loopContext.prefix = prefix;
- loopContext.prefixedKey = new StringBuilder(prefix);
- }
- loopContext.pattern = compiledPattern;
- loopContext.stringified = stringified;
- return loopContext;
- });
-
- // Create the resolver.
- return createResolver(flatten, loopContextRecycler);
-
- }
-
- private static EventResolver createResolver(
- final boolean flatten,
- final Recycler<LoopContext> loopContextRecycler) {
- return new EventResolver() {
-
- @Override
- public boolean isFlattening() {
- return flatten;
- }
-
- @Override
- public boolean isResolvable(final LogEvent logEvent) {
- final ReadOnlyStringMap contextData = logEvent.getContextData();
- return contextData != null && !contextData.isEmpty();
- }
-
- @Override
- public void resolve(final LogEvent value, final JsonWriter jsonWriter) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void resolve(
- final LogEvent logEvent,
- final JsonWriter jsonWriter,
- final boolean succeedingEntry) {
-
- // Retrieve the context data.
- final ReadOnlyStringMap contextData = logEvent.getContextData();
- if (contextData == null || contextData.isEmpty()) {
- if (!flatten) {
- jsonWriter.writeNull();
- }
- return;
- }
-
- // Resolve the context data.
- if (!flatten) {
- jsonWriter.writeObjectStart();
- }
- final LoopContext loopContext = loopContextRecycler.acquire();
- loopContext.jsonWriter = jsonWriter;
- loopContext.initJsonWriterStringBuilderLength = jsonWriter.getStringBuilder().length();
- loopContext.succeedingEntry = flatten && succeedingEntry;
- try {
- contextData.forEach(LoopMethod.INSTANCE, loopContext);
- } finally {
- loopContextRecycler.release(loopContext);
- }
- if (!flatten) {
- jsonWriter.writeObjectEnd();
- }
-
- }
-
- };
- }
-
- private static final class LoopContext {
-
- private String prefix;
-
- private StringBuilder prefixedKey;
-
- private Pattern pattern;
-
- private boolean stringified;
-
- private JsonWriter jsonWriter;
-
- private int initJsonWriterStringBuilderLength;
-
- private boolean succeedingEntry;
-
- }
-
- private static final class LoopMethod implements TriConsumer<String, Object, LoopContext> {
-
- private static final LoopMethod INSTANCE = new LoopMethod();
-
- @Override
- public void accept(
- final String key,
- final Object value,
- final LoopContext loopContext) {
- final boolean keyMatched =
- loopContext.pattern == null ||
- loopContext.pattern.matcher(key).matches();
- if (keyMatched) {
- final boolean succeedingEntry =
- loopContext.succeedingEntry ||
- loopContext.initJsonWriterStringBuilderLength <
- loopContext.jsonWriter.getStringBuilder().length();
- if (succeedingEntry) {
- loopContext.jsonWriter.writeSeparator();
- }
- if (loopContext.prefix == null) {
- loopContext.jsonWriter.writeObjectKey(key);
- } else {
- loopContext.prefixedKey.setLength(loopContext.prefix.length());
- loopContext.prefixedKey.append(key);
- loopContext.jsonWriter.writeObjectKey(loopContext.prefixedKey);
- }
- if (loopContext.stringified && !(value instanceof String)) {
- final String valueString = String.valueOf(value);
- loopContext.jsonWriter.writeString(valueString);
- } else {
- loopContext.jsonWriter.writeValue(value);
- }
- }
- }
-
+ super(context, config, LogEvent::getContextData);
}
static String getName() {
return "mdc";
}
- @Override
- public boolean isFlattening() {
- return internalResolver.isFlattening();
- }
-
- @Override
- public boolean isResolvable(final LogEvent logEvent) {
- final ReadOnlyStringMap contextData = logEvent.getContextData();
- return contextData != null && !contextData.isEmpty();
- }
-
- @Override
- public void resolve(
- final LogEvent logEvent,
- final JsonWriter jsonWriter) {
- internalResolver.resolve(logEvent, jsonWriter);
- }
-
- @Override
- public void resolve(
- final LogEvent logEvent,
- final JsonWriter jsonWriter,
- final boolean succeedingEntry) {
- internalResolver.resolve(logEvent, jsonWriter, succeedingEntry);
- }
-
}
diff --git a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutTest.java b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutTest.java
index 075dfb5..0fdf369 100644
--- a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutTest.java
+++ b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutTest.java
@@ -708,9 +708,7 @@ public class JsonTemplateLayoutTest {
final String mdcDirectlyAccessedValue = "mdcValue1";
contextData.putValue(mdcDirectlyAccessedKey, mdcDirectlyAccessedValue);
final String mdcDirectlyAccessedNullPropertyKey = "mdcKey2";
- final String mdcDirectlyAccessedNullPropertyValue = null;
- // noinspection ConstantConditions
- contextData.putValue(mdcDirectlyAccessedNullPropertyKey, mdcDirectlyAccessedNullPropertyValue);
+ contextData.putValue(mdcDirectlyAccessedNullPropertyKey, null);
final LogEvent logEvent = Log4jLogEvent
.newBuilder()
.setLoggerName(LOGGER_NAME)
@@ -719,14 +717,57 @@ public class JsonTemplateLayoutTest {
.setContextData(contextData)
.build();
+ // Check the serialized event.
+ testReadOnlyStringMapKeyAccess(
+ mdcDirectlyAccessedKey,
+ mdcDirectlyAccessedValue,
+ mdcDirectlyAccessedNullPropertyKey,
+ logEvent,
+ "mdc");
+
+ }
+
+ @Test
+ public void test_map_key_access() {
+
+ // Create the log event.
+ final String directlyAccessedKey = "mapKey1";
+ final String directlyAccessedValue = "mapValue1";
+ final String directlyAccessedNullPropertyKey = "mapKey2";
+ final Message message = new StringMapMessage()
+ .with(directlyAccessedKey, directlyAccessedValue);
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(Level.INFO)
+ .setMessage(message)
+ .build();
+
+ // Check the serialized event.
+ testReadOnlyStringMapKeyAccess(
+ directlyAccessedKey,
+ directlyAccessedValue,
+ directlyAccessedNullPropertyKey,
+ logEvent,
+ "map");
+
+ }
+
+ private static void testReadOnlyStringMapKeyAccess(
+ final String directlyAccessedKey,
+ final String directlyAccessedValue,
+ final String directlyAccessedNullPropertyKey,
+ final LogEvent logEvent,
+ final String resolverName) {
+
// Create the event template.
String eventTemplate = writeJson(Map(
- mdcDirectlyAccessedKey, Map(
- "$resolver", "mdc",
- "key", mdcDirectlyAccessedKey),
- mdcDirectlyAccessedNullPropertyKey, Map(
- "$resolver", "mdc",
- "key", mdcDirectlyAccessedNullPropertyKey)));
+ directlyAccessedKey, Map(
+ "$resolver", resolverName,
+ "key", directlyAccessedKey),
+ directlyAccessedNullPropertyKey, Map(
+ "$resolver", resolverName,
+ "key", directlyAccessedNullPropertyKey)));
// Create the layout.
final JsonTemplateLayout layout = JsonTemplateLayout
@@ -738,8 +779,8 @@ public class JsonTemplateLayoutTest {
// Check the serialized event.
usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
- assertThat(accessor.getString(mdcDirectlyAccessedKey)).isEqualTo(mdcDirectlyAccessedValue);
- assertThat(accessor.getString(mdcDirectlyAccessedNullPropertyKey)).isNull();
+ assertThat(accessor.getString(directlyAccessedKey)).isEqualTo(directlyAccessedValue);
+ assertThat(accessor.getString(directlyAccessedNullPropertyKey)).isNull();
});
}
@@ -764,12 +805,57 @@ public class JsonTemplateLayoutTest {
.setContextData(contextData)
.build();
+ // Check the serialized event.
+ testReadOnlyStringMapPattern(
+ mdcPatternMatchedKey,
+ mdcPatternMatchedValue,
+ mdcPatternMismatchedKey,
+ logEvent,
+ "mdc");
+
+ }
+
+ @Test
+ public void test_map_pattern() {
+
+ // Create the log event.
+ final String patternMatchedKey = "mapKey1";
+ final String patternMatchedValue = "mapValue1";
+ final String patternMismatchedKey = "mapKey2";
+ final String patternMismatchedValue = "mapValue2";
+ final Message message = new StringMapMessage()
+ .with(patternMatchedKey, patternMatchedValue)
+ .with(patternMismatchedKey, patternMismatchedValue);
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(Level.INFO)
+ .setMessage(message)
+ .build();
+
+ // Check the serialized event.
+ testReadOnlyStringMapPattern(
+ patternMatchedKey,
+ patternMatchedValue,
+ patternMismatchedKey,
+ logEvent,
+ "map");
+
+ }
+
+ private static void testReadOnlyStringMapPattern(
+ final String patternMatchedKey,
+ final String patternMatchedValue,
+ final String patternMismatchedKey,
+ final LogEvent logEvent,
+ final String resolverName) {
+
// Create the event template.
- final String mdcFieldName = "mdc";
+ final String mapFieldName = "map";
final String eventTemplate = writeJson(Map(
- mdcFieldName, Map(
- "$resolver", "mdc",
- "pattern", mdcPatternMatchedKey)));
+ mapFieldName, Map(
+ "$resolver", resolverName,
+ "pattern", patternMatchedKey)));
// Create the layout.
final JsonTemplateLayout layout = JsonTemplateLayout
@@ -781,8 +867,8 @@ public class JsonTemplateLayoutTest {
// Check the serialized event.
usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
- assertThat(accessor.getString(new String[]{mdcFieldName, mdcPatternMatchedKey})).isEqualTo(mdcPatternMatchedValue);
- assertThat(accessor.exists(new String[]{mdcFieldName, mdcPatternMismatchedKey})).isFalse();
+ assertThat(accessor.getString(new String[]{mapFieldName, patternMatchedKey})).isEqualTo(patternMatchedValue);
+ assertThat(accessor.exists(new String[]{mapFieldName, patternMismatchedKey})).isFalse();
});
}
@@ -807,13 +893,58 @@ public class JsonTemplateLayoutTest {
.setContextData(contextData)
.build();
+ // Check the serialized event.
+ testReadOnlyStringMapFlatten(
+ mdcPatternMatchedKey,
+ mdcPatternMatchedValue,
+ mdcPatternMismatchedKey,
+ logEvent,
+ "mdc");
+
+ }
+
+ @Test
+ public void test_map_flatten() {
+
+ // Create the log event.
+ final String patternMatchedKey = "mapKey1";
+ final String patternMatchedValue = "mapValue1";
+ final String patternMismatchedKey = "mapKey2";
+ final String patternMismatchedValue = "mapValue2";
+ final Message message = new StringMapMessage()
+ .with(patternMatchedKey, patternMatchedValue)
+ .with(patternMismatchedKey, patternMismatchedValue);
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(Level.INFO)
+ .setMessage(message)
+ .build();
+
+ // Check the serialized event.
+ testReadOnlyStringMapFlatten(
+ patternMatchedKey,
+ patternMatchedValue,
+ patternMismatchedKey,
+ logEvent,
+ "map");
+
+ }
+
+ private static void testReadOnlyStringMapFlatten(
+ final String patternMatchedKey,
+ final String patternMatchedValue,
+ final String patternMismatchedKey,
+ final LogEvent logEvent,
+ final String resolverName) {
+
// Create the event template.
- final String mdcPrefix = "_mdc.";
+ final String prefix = "_map.";
final String eventTemplate = writeJson(Map(
"ignoredFieldName", Map(
- "$resolver", "mdc",
- "pattern", mdcPatternMatchedKey,
- "flatten", Map("prefix", mdcPrefix))));
+ "$resolver", resolverName,
+ "pattern", patternMatchedKey,
+ "flatten", Map("prefix", prefix))));
// Create the layout.
final JsonTemplateLayout layout = JsonTemplateLayout
@@ -825,8 +956,8 @@ public class JsonTemplateLayoutTest {
// Check the serialized event.
usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
- assertThat(accessor.getString(mdcPrefix + mdcPatternMatchedKey)).isEqualTo(mdcPatternMatchedValue);
- assertThat(accessor.exists(mdcPrefix + mdcPatternMismatchedKey)).isFalse();
+ assertThat(accessor.getString(prefix + patternMatchedKey)).isEqualTo(patternMatchedValue);
+ assertThat(accessor.exists(prefix + patternMismatchedKey)).isFalse();
});
}
@@ -1793,7 +1924,7 @@ public class JsonTemplateLayoutTest {
testMessageParameterResolver(ReusableMessageFactory.INSTANCE);
}
- private void testMessageParameterResolver(MessageFactory messageFactory) {
+ private static void testMessageParameterResolver(MessageFactory messageFactory) {
// Create the event template.
final String eventTemplate = writeJson(Map(
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 1a0ea85..100fba9 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -30,6 +30,9 @@
- "remove" - Removed
-->
<release version="2.14.1" date="2020-MM-DD" description="GA Release 2.14.1">
+ <action issue="LOG4J2-2962" dev="vy" type="add">
+ Enrich "map" resolver by unifying its backend with "mdc" resolver.
+ </action>
<action issue="LOG4J2-2961" dev="vy" type="fix">
Fix reading of JsonTemplateLayout event additional fields from config.
</action>
diff --git a/src/site/asciidoc/manual/json-template-layout.adoc.vm b/src/site/asciidoc/manual/json-template-layout.adoc.vm
index 2536e9b..c50bb0a 100644
--- a/src/site/asciidoc/manual/json-template-layout.adoc.vm
+++ b/src/site/asciidoc/manual/json-template-layout.adoc.vm
@@ -596,147 +596,18 @@ Resolve the argument coming right after `--userId`:
----
| map
-a|
-[source]
-----
-config = key , [ stringified ]
-key = "key" -> string
-stringified = "stringified" -> boolean
-----
-| resolves the given `key` of ``MapMessage``s
-| `stringified` flag translates to `String.valueOf(value)`, hence mind
- not-`String`-typed values.
-a|
-Resolve the `userRole` field of the message:
-
-[source,json]
-----
-{
- "$resolver": "map",
- "key": "userRole"
-}
-----
-
-| marker
-a|
-[source]
-----
-config = "field" -> "name"
-----
-| `logEvent.getMarker().getName()`
-| none
-a|
-Resolve the marker name:
-
-[source,json]
-----
-{
- "$resolver": "marker",
- "field": "name"
-}
-----
+| see link:#map-resolver-template[Map Resolver Template]
+| resolves ``MapMessage``s
+| see link:#map-resolver-template[Map Resolver Template]
+| see link:#map-resolver-template[Map Resolver Template]
| mdc
-a|
-[source]
-----
-config = singleAccess \| multiAccess
-
-singleAccess = key , [ stringified ]
-key = "key" -> string
-stringified = "stringified" -> boolean
-
-multi-access = [ pattern ] , [ flatten ] , [ stringified ]
-pattern = "pattern" -> string
-flatten = "flatten" -> ( boolean \| flattenConfig )
-flattenConfig = [ flattenPrefix ]
-flattenPrefix = "prefix" -> string
-----
-a| Mapped Diagnostic Context (MDC), aka. Thread Context Data, resolver.
-
-`singleAccess` resolves the MDC value as is, whilst `multiAccess` resolves a
-multitude of MDC values. If `flatten` is provided, `multiAccess` merges the
-values with the parent, otherwise creates a new JSON object containing the
-values.
-
-Enabling `stringified` flag converts each value to its string representation.
-
-Regex provided in the `pattern` is used to match against the keys.
-a|
-`log4j2.garbagefreeThreadContextMap` flag needs to be turned on to iterate
-the map without allocations.
-
-`stringified` allocates a new `String` for values that are not of type `String`.
-
-Writing certain non-primitive values (e.g., `BigDecimal`, `Set`, etc.) to JSON
-generates garbage, though most (e.g., `int`, `long`, `String`, `List`,
-`boolean[]`, etc.) don't.
-a|
-Resolve the `userRole` MDC value:
-
-[source,json]
-----
-{
- "$resolver": "mdc",
- "key": "userRole"
-}
-----
-
-Resolve the string representation of the `userRank` MDC value:
-
-[source,json]
-----
-{
- "$resolver": "mdc",
- "key": "userRank",
- "stringified": true
-}
-----
-
-Resolve all MDC entries into an object:
-
-[source,json]
-----
-{
- "$resolver": "mdc"
-}
-----
-
-Resolve all MDC entries into an object such that values are converted to string:
-
-[source,json]
-----
-{
- "$resolver": "mdc",
- "stringified": true
-}
-----
-
-Merge all MDC entries whose keys are matching with the `user(Role\|Rank)` regex
-into the parent:
-
-[source,json]
-----
-{
- "$resolver": "mdc",
- "flatten": true,
- "pattern": "user(Role\|Rank)"
-}
-----
-
-After converting the corresponding entries to string, merge all MDC entries to
-parent such that keys are prefixed with `_`:
-
-[source,json]
-----
-{
- "$resolver": "mdc",
- "stringified": true,
- "flatten": {
- "prefix": "_"
- }
-}
-----
+| see link:#map-resolver-template[Map Resolver Template]
+| resolves Mapped Diagnostic Context (MDC), aka. Thread Context Data
+| `log4j2.garbagefreeThreadContextMap` flag needs to be turned on to iterate
+ the map without allocations. See
+ link:#map-resolver-template[Map Resolver Template] for other details.
+| see link:#map-resolver-template[Map Resolver Template]
| message
a|
@@ -1109,6 +980,117 @@ a!
!===
|===
+[#map-resolver-template]
+==== Map Resolver Template
+
+`ReadOnlyStringMap` is Log4j's `Map<String, Object>` equivalent with
+garbage-free accessors and heavily employed throughout the code base. It is the
+data structure backing both Mapped Diagnostic Context (MDC), aka. Thread Context
+Data and `MapMessage` implementations. Hence template resolvers for both of
+these are provided by a single backend: `ReadOnlyStringMapResolver`. Put another
+way, both `mdc` and `map` resolvers support identical configuration, behaviour,
+and garbage footprint, which are detailed below.
+
+[#stringmap-template-resolver]
+.`ReadOnlyStringMap` template resolver
+[cols="3,2,2,4"]
+|===
+| Syntax
+| Description
+| Garbage Footprint
+| Examples
+
+a|
+[source]
+----
+config = singleAccess \| multiAccess
+
+singleAccess = key , [ stringified ]
+key = "key" -> string
+stringified = "stringified" -> boolean
+
+multiAccess = [ pattern ] , [ flatten ] , [ stringified ]
+pattern = "pattern" -> string
+flatten = "flatten" -> ( boolean \| flattenConfig )
+flattenConfig = [ flattenPrefix ]
+flattenPrefix = "prefix" -> string
+----
+| `singleAccess` resolves a single field, whilst `multiAccess` resolves a
+ multitude of fields. If `flatten` is provided, `multiAccess` merges the fields
+ with the parent, otherwise creates a new JSON object containing the values.
+| `stringified` flag translates to `String.valueOf(value)`, hence mind
+ not-`String`-typed values.
+a|
+`"${dollar}resolver"` is left out in the following examples, since it is to be
+defined by the actual resolver, e.g., `map`, `mdc`.
+
+Resolve the value of the field keyed with `userRole`:
+
+[source,json]
+----
+{
+ "$resolver": "…",
+ "key": "userRole"
+}
+----
+
+Resolve the string representation of the `userRank` field value:
+
+[source,json]
+----
+{
+ "$resolver": "…",
+ "key": "userRank",
+ "stringified": true
+}
+----
+
+Resolve all fields into an object:
+
+[source,json]
+----
+{
+ "$resolver": "…"
+}
+----
+
+Resolve all fields into an object such that values are converted to string:
+
+[source,json]
+----
+{
+ "$resolver": "…",
+ "stringified": true
+}
+----
+
+Merge all fields whose keys are matching with the `user(Role\|Rank)` regex into
+the parent:
+
+[source,json]
+----
+{
+ "$resolver": "…",
+ "flatten": true,
+ "pattern": "user(Role\|Rank)"
+}
+----
+
+After converting the corresponding field values to string, merge all fields to
+parent such that keys are prefixed with `_`:
+
+[source,json]
+----
+{
+ "$resolver": "…",
+ "stringified": true,
+ "flatten": {
+ "prefix": "_"
+ }
+}
+----
+|===
+
[#stack-trace-element-templates]
=== Stack Trace Element Templates