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ı <volkan.yaz...@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