You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by ma...@apache.org on 2022/01/17 20:04:09 UTC

[logging-log4j2] 01/02: [LOG4J2-3343] Add excludePattern to JTL maps

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

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

commit c7906fb9b4ac3f518d2b4256bff3e11422b21fc6
Author: Matt Sicker <ma...@apache.org>
AuthorDate: Mon Jan 17 14:03:15 2022 -0600

    [LOG4J2-3343] Add excludePattern to JTL maps
    
    This adds a new option to exclude keys from map-based resolvers using a regular expression.
    
    Signed-off-by: Matt Sicker <ma...@apache.org>
---
 .../json/resolver/ReadOnlyStringMapResolver.java   | 47 +++++++++++++++-------
 .../resolver/ReadOnlyStringMapResolverTest.java    | 26 ++++++++++++
 .../asciidoc/manual/json-template-layout.adoc.vm   | 26 +++++++-----
 3 files changed, 74 insertions(+), 25 deletions(-)

diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java
index 788fa5d..65abfb0 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java
@@ -34,18 +34,19 @@ import java.util.regex.Pattern;
  * <h3>Configuration</h3>
  *
  * <pre>
- * config        = singleAccess | multiAccess
+ * config         = singleAccess | multiAccess
  *
- * singleAccess  = key , [ stringified ]
- * key           = "key" -> string
- * stringified   = "stringified" -> boolean
+ * singleAccess   = key , [ stringified ]
+ * key            = "key" -> string
+ * stringified    = "stringified" -> boolean
  *
- * multiAccess   = [ pattern ] , [ replacement ] , [ flatten ] , [ stringified ]
- * pattern       = "pattern" -> string
- * replacement   = "replacement" -> string
- * flatten       = "flatten" -> ( boolean | flattenConfig )
- * flattenConfig = [ flattenPrefix ]
- * flattenPrefix = "prefix" -> string
+ * multiAccess    = [ pattern ] , [ replacement ] , [ excludePattern ] , [ flatten ] , [ stringified ]
+ * pattern        = "pattern" -> string
+ * excludePattern = "excludePattern" -> string
+ * replacement    = "replacement" -> string
+ * flatten        = "flatten" -> ( boolean | flattenConfig )
+ * flattenConfig  = [ flattenPrefix ]
+ * flattenPrefix  = "prefix" -> string
  * </pre>
  *
  * Note that <tt>singleAccess</tt> resolves a single field, whilst
@@ -61,14 +62,17 @@ import java.util.regex.Pattern;
  * These two are effectively equivalent to
  * <tt>Pattern.compile(pattern).matcher(key).matches()</tt> and
  * <tt>Pattern.compile(pattern).matcher(key).replaceAll(replacement)</tt> calls.
+ * Regex provided in the <tt>excludePattern</tt> is used to exclude matching keys.
+ * This exclusion pattern is not used with the previously mentioned
+ * <tt>replacement</tt> option.
  *
  * <h3>Garbage Footprint</h3>
  *
  * <tt>stringified</tt> allocates a new <tt>String</tt> for values that are not
  * of type <tt>String</tt>.
  * <p>
- * <tt>pattern</tt> and <tt>replacement</tt> incur pattern matcher allocation
- * costs.
+ * <tt>pattern</tt>, <tt>excludePattern</tt> and <tt>replacement</tt> incur pattern
+ * matcher allocation costs.
  * <p>
  * Writing certain non-primitive values (e.g., <tt>BigDecimal</tt>,
  * <tt>Set</tt>, etc.) to JSON generates garbage, though most (e.g.,
@@ -208,6 +212,11 @@ class ReadOnlyStringMapResolver implements EventResolver {
             throw new IllegalArgumentException(
                     "replacement cannot be provided without a pattern: " + config);
         }
+        final String excludePattern = config.getString("excludePattern");
+        if (excludePattern != null && key != null) {
+            throw new IllegalArgumentException(
+                    "excludePattern and key options cannot be combined: " + config);
+        }
         final boolean stringified = config.getBoolean("stringified", false);
         if (key != null) {
             return createKeyResolver(key, stringified, mapAccessor);
@@ -219,7 +228,7 @@ class ReadOnlyStringMapResolver implements EventResolver {
                     prefix,
                     pattern,
                     replacement,
-                    stringified,
+                    excludePattern, stringified,
                     mapAccessor);
         }
     }
@@ -257,6 +266,7 @@ class ReadOnlyStringMapResolver implements EventResolver {
             final String prefix,
             final String pattern,
             final String replacement,
+            final String excludePattern,
             final boolean stringified,
             final Function<LogEvent, ReadOnlyStringMap> mapAccessor) {
 
@@ -265,6 +275,8 @@ class ReadOnlyStringMapResolver implements EventResolver {
                 pattern == null
                         ? null
                         : Pattern.compile(pattern);
+        final Pattern exclusionPattern =
+                excludePattern == null ? null : Pattern.compile(excludePattern);
 
         // Create the recycler for the loop context.
         final Recycler<LoopContext> loopContextRecycler =
@@ -276,6 +288,7 @@ class ReadOnlyStringMapResolver implements EventResolver {
                     }
                     loopContext.pattern = compiledPattern;
                     loopContext.replacement = replacement;
+                    loopContext.exclusionPattern = exclusionPattern;
                     loopContext.stringified = stringified;
                     return loopContext;
                 });
@@ -354,6 +367,8 @@ class ReadOnlyStringMapResolver implements EventResolver {
 
         private String replacement;
 
+        private Pattern exclusionPattern;
+
         private boolean stringified;
 
         private JsonWriter jsonWriter;
@@ -376,7 +391,11 @@ class ReadOnlyStringMapResolver implements EventResolver {
             final Matcher matcher = loopContext.pattern != null
                     ? loopContext.pattern.matcher(key)
                     : null;
-            final boolean keyMatched = matcher == null || matcher.matches();
+            final Matcher mismatcher = loopContext.exclusionPattern != null
+                    ? loopContext.exclusionPattern.matcher(key)
+                    : null;
+            final boolean keyMatched = (matcher == null || matcher.matches())
+                    && (mismatcher == null || !mismatcher.matches());
             if (keyMatched) {
                 final String replacedKey =
                         matcher != null && loopContext.replacement != null
diff --git a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolverTest.java b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolverTest.java
index 921e153..34fd944 100644
--- a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolverTest.java
+++ b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolverTest.java
@@ -29,6 +29,7 @@ import org.junit.jupiter.api.Test;
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.regex.PatternSyntaxException;
 
 import static org.apache.logging.log4j.layout.template.json.TestHelpers.*;
@@ -450,4 +451,29 @@ class ReadOnlyStringMapResolverTest {
 
     }
 
+    @Test
+    void test_map_exclude_pattern() {
+        final StringMapMessage message = new StringMapMessage()
+                .with("first", "alpha")
+                .with("second", "beta")
+                .with("third", "gamma")
+                .with("fourth", "delta");
+        final LogEvent logEvent = Log4jLogEvent.newBuilder()
+                .setMessage(message)
+                .build();
+
+        final String eventTemplate = writeJson(asMap(
+                "map", asMap(
+                        "$resolver", "map",
+                        "excludePattern", ".+d")));
+
+        final JsonTemplateLayout layout = JsonTemplateLayout.newBuilder()
+                .setConfiguration(CONFIGURATION)
+                .setEventTemplate(eventTemplate)
+                .build();
+
+        usingSerializedLogEventAccessor(layout, logEvent,
+                accessor -> assertThat(accessor.getObject("map")).isInstanceOfSatisfying(Map.class,
+                        map -> assertThat(map).containsOnlyKeys("first", "fourth")));
+    }
 }
diff --git a/src/site/asciidoc/manual/json-template-layout.adoc.vm b/src/site/asciidoc/manual/json-template-layout.adoc.vm
index 53410b2..a7b6a61 100644
--- a/src/site/asciidoc/manual/json-template-layout.adoc.vm
+++ b/src/site/asciidoc/manual/json-template-layout.adoc.vm
@@ -1346,18 +1346,19 @@ and garbage footprint, which are detailed below.
 
 [source]
 ----
-config        = singleAccess | multiAccess
+config         = singleAccess | multiAccess
 
-singleAccess  = key , [ stringified ]
-key           = "key" -> string
-stringified   = "stringified" -> boolean
+singleAccess   = key , [ stringified ]
+key            = "key" -> string
+stringified    = "stringified" -> boolean
 
-multiAccess   = [ pattern ] , [ replacement ] , [ flatten ] , [ stringified ]
-pattern       = "pattern" -> string
-replacement   = "replacement" -> string
-flatten       = "flatten" -> ( boolean | flattenConfig )
-flattenConfig = [ flattenPrefix ]
-flattenPrefix = "prefix" -> string
+multiAccess    = [ pattern ] , [ replacement ] , [ excludePattern ] , [ flatten ] , [ stringified ]
+pattern        = "pattern" -> string
+replacement    = "replacement" -> string
+excludePattern = "excludePattern" -> string
+flatten        = "flatten" -> ( boolean | flattenConfig )
+flattenConfig  = [ flattenPrefix ]
+flattenPrefix  = "prefix" -> string
 ----
 
 `singleAccess` resolves a single field, whilst `multiAccess` resolves a
@@ -1370,13 +1371,16 @@ Regex provided in the `pattern` is used to match against the keys. If provided,
 `replacement` will be used to replace the matched keys. These two are
 effectively equivalent to `Pattern.compile(pattern).matcher(key).matches()` and
 `Pattern.compile(pattern).matcher(key).replaceAll(replacement)` calls.
+Regex provided in the `excludePattern` is used to exclude matching keys.
+This exclusion pattern is not used with the previously mentioned `replacement`
+option.
 
 [WARNING]
 ====
 Regarding garbage footprint, `stringified` flag translates to
 `String.valueOf(value)`, hence mind not-`String`-typed values.
 
-`pattern` and `replacement` incur pattern matcher allocation costs.
+`pattern`, `excludePattern`, and `replacement` incur pattern matcher allocation costs.
 
 Writing certain non-primitive values (e.g., `BigDecimal`, `Set`, etc.) to JSON
 generates garbage, though most (e.g., `int`, `long`, `String`, `List`,