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 2021/07/09 13:38:04 UTC

[logging-log4j2] branch master updated (7073607 -> 137a8e0)

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

vy pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git.


    from 7073607  Move test class to correct module
     new 37d865a  LOG4J2-3074 Add replacement parameter to ReadOnlyStringMapResolver.
     new 8ef3a37  Consolidate ReadOnlyStringMapResolver tests.
     new 4285d94  Consolidate MessageResolver tests.
     new 0d618fb  Add missing license preambles to log4j-layout-template-json sources.
     new 137a8e0  LOG4J2-3067 Add CounterResolver.

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../json/resolver/CaseConverterResolver.java       |  17 +-
 .../resolver/CaseConverterResolverFactory.java     |  16 +
 .../template/json/resolver/CounterResolver.java    | 247 ++++++++++
 ...verFactory.java => CounterResolverFactory.java} |  19 +-
 .../json/resolver/ReadOnlyStringMapResolver.java   |  87 +++-
 .../json/resolver/TemplateResolverFactories.java   |  16 +
 .../template/json/JsonTemplateLayoutTest.java      | 504 +--------------------
 .../log4j/layout/template/json/TestHelpers.java    |  16 +
 .../json/resolver/CaseConverterResolverTest.java   |  16 +
 .../json/resolver/CounterResolverTest.java         | 158 +++++++
 .../json/resolver/MessageResolverTest.java         | 158 +++++++
 .../resolver/ReadOnlyStringMapResolverTest.java    | 453 ++++++++++++++++++
 .../template/json/util/InstantFormatterTest.java   |  16 +
 src/changes/changes.xml                            |   6 +
 .../asciidoc/manual/json-template-layout.adoc.vm   | 115 ++++-
 15 files changed, 1305 insertions(+), 539 deletions(-)
 create mode 100644 log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java
 copy log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/{MessageResolverFactory.java => CounterResolverFactory.java} (71%)
 create mode 100644 log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolverTest.java
 create mode 100644 log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolverTest.java

[logging-log4j2] 04/05: Add missing license preambles to log4j-layout-template-json sources.

Posted by vy...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

vy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 0d618fb8d3e5634941588a42b482155b854ebf72
Author: Volkan Yazici <vo...@gmail.com>
AuthorDate: Wed Jul 7 11:01:23 2021 +0200

    Add missing license preambles to log4j-layout-template-json sources.
---
 .../template/json/resolver/CaseConverterResolver.java    | 16 ++++++++++++++++
 .../json/resolver/CaseConverterResolverFactory.java      | 16 ++++++++++++++++
 .../json/resolver/TemplateResolverFactories.java         | 16 ++++++++++++++++
 .../logging/log4j/layout/template/json/TestHelpers.java  | 16 ++++++++++++++++
 .../json/resolver/CaseConverterResolverTest.java         | 16 ++++++++++++++++
 .../layout/template/json/util/InstantFormatterTest.java  | 16 ++++++++++++++++
 6 files changed, 96 insertions(+)

diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CaseConverterResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CaseConverterResolver.java
index 5210384..559cba1 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CaseConverterResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CaseConverterResolver.java
@@ -1,3 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
 package org.apache.logging.log4j.layout.template.json.resolver;
 
 import org.apache.logging.log4j.core.LogEvent;
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CaseConverterResolverFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CaseConverterResolverFactory.java
index 27c8764..00ab138 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CaseConverterResolverFactory.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CaseConverterResolverFactory.java
@@ -1,3 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
 package org.apache.logging.log4j.layout.template.json.resolver;
 
 import org.apache.logging.log4j.plugins.Plugin;
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverFactories.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverFactories.java
index 31926ea..96ecd6e 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverFactories.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverFactories.java
@@ -1,3 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
 package org.apache.logging.log4j.layout.template.json.resolver;
 
 import org.apache.logging.log4j.Logger;
diff --git a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/TestHelpers.java b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/TestHelpers.java
index 78404b6..04ac179 100644
--- a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/TestHelpers.java
+++ b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/TestHelpers.java
@@ -1,3 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
 package org.apache.logging.log4j.layout.template.json;
 
 import org.apache.logging.log4j.core.Layout;
diff --git a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/CaseConverterResolverTest.java b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/CaseConverterResolverTest.java
index a52659c..fd51eda 100644
--- a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/CaseConverterResolverTest.java
+++ b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/CaseConverterResolverTest.java
@@ -1,3 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
 package org.apache.logging.log4j.layout.template.json.resolver;
 
 import org.apache.logging.log4j.core.LogEvent;
diff --git a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/util/InstantFormatterTest.java b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/util/InstantFormatterTest.java
index 762b3df..bc1fd42 100644
--- a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/util/InstantFormatterTest.java
+++ b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/util/InstantFormatterTest.java
@@ -1,3 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
 package org.apache.logging.log4j.layout.template.json.util;
 
 import org.apache.logging.log4j.core.time.MutableInstant;

[logging-log4j2] 02/05: Consolidate ReadOnlyStringMapResolver tests.

Posted by vy...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

vy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 8ef3a376542139564b1818a992201b63e4efbb22
Author: Volkan Yazici <vo...@gmail.com>
AuthorDate: Wed Jul 7 10:52:28 2021 +0200

    Consolidate ReadOnlyStringMapResolver tests.
---
 .../template/json/JsonTemplateLayoutTest.java      | 341 --------------------
 .../resolver/ReadOnlyStringMapResolverTest.java    | 348 ++++++++++++++++++++-
 2 files changed, 347 insertions(+), 342 deletions(-)

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 28ba22d..b2ae188 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
@@ -329,47 +329,6 @@ class JsonTemplateLayoutTest {
     }
 
     @Test
-    void test_MapMessage_keyed_access() {
-
-        // Create the event template.
-        final String key = "list";
-        final String eventTemplate = writeJson(asMap(
-                "typedValue", asMap(
-                        "$resolver", "map",
-                        "key", key),
-                "stringifiedValue", asMap(
-                        "$resolver", "map",
-                        "key", key,
-                        "stringified", true)));
-
-        // Create the layout.
-        final JsonTemplateLayout layout = JsonTemplateLayout
-                .newBuilder()
-                .setConfiguration(CONFIGURATION)
-                .setEventTemplate(eventTemplate)
-                .build();
-
-        // Create the log event with a MapMessage.
-        final List<Integer> value = Arrays.asList(1, 2);
-        final StringMapMessage mapMessage = new StringMapMessage()
-                .with(key, value);
-        final LogEvent logEvent = Log4jLogEvent
-                .newBuilder()
-                .setLoggerName(LOGGER_NAME)
-                .setLevel(Level.INFO)
-                .setMessage(mapMessage)
-                .setTimeMillis(System.currentTimeMillis())
-                .build();
-
-        // Check the serialized event.
-        usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
-            assertThat(accessor.getObject("typedValue")).isEqualTo(value);
-            assertThat(accessor.getString("stringifiedValue")).isEqualTo(String.valueOf(value));
-        });
-
-    }
-
-    @Test
     void test_message_fallbackKey() {
 
         // Create the event template.
@@ -707,306 +666,6 @@ class JsonTemplateLayoutTest {
     }
 
     @Test
-    void test_mdc_key_access() {
-
-        // Create the log event.
-        final SimpleMessage message = new SimpleMessage("Hello, World!");
-        final StringMap contextData = new SortedArrayStringMap();
-        final String mdcDirectlyAccessedKey = "mdcKey1";
-        final String mdcDirectlyAccessedValue = "mdcValue1";
-        contextData.putValue(mdcDirectlyAccessedKey, mdcDirectlyAccessedValue);
-        final String mdcDirectlyAccessedNullPropertyKey = "mdcKey2";
-        contextData.putValue(mdcDirectlyAccessedNullPropertyKey, null);
-        final LogEvent logEvent = Log4jLogEvent
-                .newBuilder()
-                .setLoggerName(LOGGER_NAME)
-                .setLevel(Level.INFO)
-                .setMessage(message)
-                .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(asMap(
-                directlyAccessedKey, asMap(
-                        "$resolver", resolverName,
-                        "key", directlyAccessedKey),
-                directlyAccessedNullPropertyKey, asMap(
-                        "$resolver", resolverName,
-                        "key", directlyAccessedNullPropertyKey)));
-
-        // Create the layout.
-        final JsonTemplateLayout layout = JsonTemplateLayout
-                .newBuilder()
-                .setConfiguration(CONFIGURATION)
-                .setStackTraceEnabled(true)
-                .setEventTemplate(eventTemplate)
-                .build();
-
-        // Check the serialized event.
-        usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
-            assertThat(accessor.getString(directlyAccessedKey)).isEqualTo(directlyAccessedValue);
-            assertThat(accessor.getString(directlyAccessedNullPropertyKey)).isNull();
-        });
-
-    }
-
-    @Test
-    void test_mdc_pattern() {
-
-        // Create the log event.
-        final SimpleMessage message = new SimpleMessage("Hello, World!");
-        final StringMap contextData = new SortedArrayStringMap();
-        final String mdcPatternMatchedKey = "mdcKey1";
-        final String mdcPatternMatchedValue = "mdcValue1";
-        contextData.putValue(mdcPatternMatchedKey, mdcPatternMatchedValue);
-        final String mdcPatternMismatchedKey = "mdcKey2";
-        final String mdcPatternMismatchedValue = "mdcValue2";
-        contextData.putValue(mdcPatternMismatchedKey, mdcPatternMismatchedValue);
-        final LogEvent logEvent = Log4jLogEvent
-                .newBuilder()
-                .setLoggerName(LOGGER_NAME)
-                .setLevel(Level.INFO)
-                .setMessage(message)
-                .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 mapFieldName = "map";
-        final String eventTemplate = writeJson(asMap(
-                mapFieldName, asMap(
-                        "$resolver", resolverName,
-                        "pattern", patternMatchedKey)));
-
-        // Create the layout.
-        final JsonTemplateLayout layout = JsonTemplateLayout
-                .newBuilder()
-                .setConfiguration(CONFIGURATION)
-                .setStackTraceEnabled(true)
-                .setEventTemplate(eventTemplate)
-                .build();
-
-        // Check the serialized event.
-        usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
-            assertThat(accessor.getString(new String[]{mapFieldName, patternMatchedKey})).isEqualTo(patternMatchedValue);
-            assertThat(accessor.exists(new String[]{mapFieldName, patternMismatchedKey})).isFalse();
-        });
-
-    }
-
-    @Test
-    void test_mdc_flatten() {
-
-        // Create the log event.
-        final SimpleMessage message = new SimpleMessage("Hello, World!");
-        final StringMap contextData = new SortedArrayStringMap();
-        final String mdcPatternMatchedKey = "mdcKey1";
-        final String mdcPatternMatchedValue = "mdcValue1";
-        contextData.putValue(mdcPatternMatchedKey, mdcPatternMatchedValue);
-        final String mdcPatternMismatchedKey = "mdcKey2";
-        final String mdcPatternMismatchedValue = "mdcValue2";
-        contextData.putValue(mdcPatternMismatchedKey, mdcPatternMismatchedValue);
-        final LogEvent logEvent = Log4jLogEvent
-                .newBuilder()
-                .setLoggerName(LOGGER_NAME)
-                .setLevel(Level.INFO)
-                .setMessage(message)
-                .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 prefix = "_map.";
-        final String eventTemplate = writeJson(asMap(
-                "ignoredFieldName", asMap(
-                        "$resolver", resolverName,
-                        "pattern", patternMatchedKey,
-                        "flatten", asMap("prefix", prefix))));
-
-        // Create the layout.
-        final JsonTemplateLayout layout = JsonTemplateLayout
-                .newBuilder()
-                .setConfiguration(CONFIGURATION)
-                .setStackTraceEnabled(true)
-                .setEventTemplate(eventTemplate)
-                .build();
-
-        // Check the serialized event.
-        usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
-            assertThat(accessor.getString(prefix + patternMatchedKey)).isEqualTo(patternMatchedValue);
-            assertThat(accessor.exists(prefix + patternMismatchedKey)).isFalse();
-        });
-
-    }
-
-    @Test
-    void test_MapResolver() {
-
-        // Create the log event.
-        final StringMapMessage message = new StringMapMessage().with("key1", "val1");
-        final LogEvent logEvent = Log4jLogEvent
-                .newBuilder()
-                .setLoggerName(LOGGER_NAME)
-                .setLevel(Level.INFO)
-                .setMessage(message)
-                .build();
-
-        // Create the event template node with map values.
-        final String eventTemplate = writeJson(asMap(
-                "mapValue1", asMap(
-                        "$resolver", "map",
-                        "key", "key1"),
-                "mapValue2", asMap(
-                        "$resolver", "map",
-                        "key", "key?")));
-
-        // Create the layout.
-        final JsonTemplateLayout layout = JsonTemplateLayout
-                .newBuilder()
-                .setConfiguration(CONFIGURATION)
-                .setEventTemplate(eventTemplate)
-                .build();
-
-        // Check serialized event.
-        usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
-            assertThat(accessor.getString("mapValue1")).isEqualTo("val1");
-            assertThat(accessor.getString("mapValue2")).isNull();
-        });
-
-    }
-
-    @Test
     void test_StringMapMessage() {
 
         // Create the log event.
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 baaedef..921e153 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
@@ -1,13 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
 package org.apache.logging.log4j.layout.template.json.resolver;
 
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.impl.Log4jLogEvent;
 import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.message.SimpleMessage;
+import org.apache.logging.log4j.message.StringMapMessage;
 import org.apache.logging.log4j.util.SortedArrayStringMap;
 import org.apache.logging.log4j.util.StringMap;
 import org.assertj.core.api.Assertions;
 import org.junit.jupiter.api.Test;
 
+import java.util.Arrays;
+import java.util.List;
 import java.util.regex.PatternSyntaxException;
 
 import static org.apache.logging.log4j.layout.template.json.TestHelpers.*;
@@ -57,7 +78,7 @@ class ReadOnlyStringMapResolverTest {
                 "replacement cannot be provided without a pattern");
     }
 
-    private void verifyConfigFailure(
+    private static void verifyConfigFailure(
             final String eventTemplate,
             final Class<? extends Throwable> failureClass,
             final String failureMessage) {
@@ -104,4 +125,329 @@ class ReadOnlyStringMapResolverTest {
 
     }
 
+    @Test
+    void test_mdc_key_access() {
+
+        // Create the log event.
+        final SimpleMessage message = new SimpleMessage("Hello, World!");
+        final StringMap contextData = new SortedArrayStringMap();
+        final String mdcDirectlyAccessedKey = "mdcKey1";
+        final String mdcDirectlyAccessedValue = "mdcValue1";
+        contextData.putValue(mdcDirectlyAccessedKey, mdcDirectlyAccessedValue);
+        final String mdcDirectlyAccessedNullPropertyKey = "mdcKey2";
+        contextData.putValue(mdcDirectlyAccessedNullPropertyKey, null);
+        final LogEvent logEvent = Log4jLogEvent
+                .newBuilder()
+                .setMessage(message)
+                .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()
+                .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(asMap(
+                directlyAccessedKey, asMap(
+                        "$resolver", resolverName,
+                        "key", directlyAccessedKey),
+                directlyAccessedNullPropertyKey, asMap(
+                        "$resolver", resolverName,
+                        "key", directlyAccessedNullPropertyKey)));
+
+        // Create the layout.
+        final JsonTemplateLayout layout = JsonTemplateLayout
+                .newBuilder()
+                .setConfiguration(CONFIGURATION)
+                .setStackTraceEnabled(true)
+                .setEventTemplate(eventTemplate)
+                .build();
+
+        // Check the serialized event.
+        usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+            assertThat(accessor.getString(directlyAccessedKey)).isEqualTo(directlyAccessedValue);
+            assertThat(accessor.getString(directlyAccessedNullPropertyKey)).isNull();
+        });
+
+    }
+
+    @Test
+    void test_mdc_pattern() {
+
+        // Create the log event.
+        final SimpleMessage message = new SimpleMessage("Hello, World!");
+        final StringMap contextData = new SortedArrayStringMap();
+        final String mdcPatternMatchedKey = "mdcKey1";
+        final String mdcPatternMatchedValue = "mdcValue1";
+        contextData.putValue(mdcPatternMatchedKey, mdcPatternMatchedValue);
+        final String mdcPatternMismatchedKey = "mdcKey2";
+        final String mdcPatternMismatchedValue = "mdcValue2";
+        contextData.putValue(mdcPatternMismatchedKey, mdcPatternMismatchedValue);
+        final LogEvent logEvent = Log4jLogEvent
+                .newBuilder()
+                .setMessage(message)
+                .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()
+                .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 mapFieldName = "map";
+        final String eventTemplate = writeJson(asMap(
+                mapFieldName, asMap(
+                        "$resolver", resolverName,
+                        "pattern", patternMatchedKey)));
+
+        // Create the layout.
+        final JsonTemplateLayout layout = JsonTemplateLayout
+                .newBuilder()
+                .setConfiguration(CONFIGURATION)
+                .setStackTraceEnabled(true)
+                .setEventTemplate(eventTemplate)
+                .build();
+
+        // Check the serialized event.
+        usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+            assertThat(accessor.getString(new String[]{mapFieldName, patternMatchedKey})).isEqualTo(patternMatchedValue);
+            assertThat(accessor.exists(new String[]{mapFieldName, patternMismatchedKey})).isFalse();
+        });
+
+    }
+
+    @Test
+    void test_mdc_flatten() {
+
+        // Create the log event.
+        final SimpleMessage message = new SimpleMessage("Hello, World!");
+        final StringMap contextData = new SortedArrayStringMap();
+        final String mdcPatternMatchedKey = "mdcKey1";
+        final String mdcPatternMatchedValue = "mdcValue1";
+        contextData.putValue(mdcPatternMatchedKey, mdcPatternMatchedValue);
+        final String mdcPatternMismatchedKey = "mdcKey2";
+        final String mdcPatternMismatchedValue = "mdcValue2";
+        contextData.putValue(mdcPatternMismatchedKey, mdcPatternMismatchedValue);
+        final LogEvent logEvent = Log4jLogEvent
+                .newBuilder()
+                .setMessage(message)
+                .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()
+                .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 prefix = "_map.";
+        final String eventTemplate = writeJson(asMap(
+                "ignoredFieldName", asMap(
+                        "$resolver", resolverName,
+                        "pattern", patternMatchedKey,
+                        "flatten", asMap("prefix", prefix))));
+
+        // Create the layout.
+        final JsonTemplateLayout layout = JsonTemplateLayout
+                .newBuilder()
+                .setConfiguration(CONFIGURATION)
+                .setStackTraceEnabled(true)
+                .setEventTemplate(eventTemplate)
+                .build();
+
+        // Check the serialized event.
+        usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+            assertThat(accessor.getString(prefix + patternMatchedKey)).isEqualTo(patternMatchedValue);
+            assertThat(accessor.exists(prefix + patternMismatchedKey)).isFalse();
+        });
+
+    }
+
+    @Test
+    void test_MapResolver() {
+
+        // Create the log event.
+        final StringMapMessage message = new StringMapMessage().with("key1", "val1");
+        final LogEvent logEvent = Log4jLogEvent
+                .newBuilder()
+                .setMessage(message)
+                .build();
+
+        // Create the event template node with map values.
+        final String eventTemplate = writeJson(asMap(
+                "mapValue1", asMap(
+                        "$resolver", "map",
+                        "key", "key1"),
+                "mapValue2", asMap(
+                        "$resolver", "map",
+                        "key", "key?")));
+
+        // Create the layout.
+        final JsonTemplateLayout layout = JsonTemplateLayout
+                .newBuilder()
+                .setConfiguration(CONFIGURATION)
+                .setEventTemplate(eventTemplate)
+                .build();
+
+        // Check serialized event.
+        usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+            assertThat(accessor.getString("mapValue1")).isEqualTo("val1");
+            assertThat(accessor.getString("mapValue2")).isNull();
+        });
+
+    }
+
+    @Test
+    void test_MapMessage_keyed_access() {
+
+        // Create the event template.
+        final String key = "list";
+        final String eventTemplate = writeJson(asMap(
+                "typedValue", asMap(
+                        "$resolver", "map",
+                        "key", key),
+                "stringifiedValue", asMap(
+                        "$resolver", "map",
+                        "key", key,
+                        "stringified", true)));
+
+        // Create the layout.
+        final JsonTemplateLayout layout = JsonTemplateLayout
+                .newBuilder()
+                .setConfiguration(CONFIGURATION)
+                .setEventTemplate(eventTemplate)
+                .build();
+
+        // Create the log event with a MapMessage.
+        final List<Integer> value = Arrays.asList(1, 2);
+        final StringMapMessage mapMessage = new StringMapMessage()
+                .with(key, value);
+        final LogEvent logEvent = Log4jLogEvent
+                .newBuilder()
+                .setMessage(mapMessage)
+                .setTimeMillis(System.currentTimeMillis())
+                .build();
+
+        // Check the serialized event.
+        usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+            assertThat(accessor.getObject("typedValue")).isEqualTo(value);
+            assertThat(accessor.getString("stringifiedValue")).isEqualTo(String.valueOf(value));
+        });
+
+    }
+
 }

[logging-log4j2] 03/05: Consolidate MessageResolver tests.

Posted by vy...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

vy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 4285d94a5703db75e22737af5f9a81043237cb4a
Author: Volkan Yazici <vo...@gmail.com>
AuthorDate: Wed Jul 7 10:55:45 2021 +0200

    Consolidate MessageResolver tests.
---
 .../template/json/JsonTemplateLayoutTest.java      | 163 +--------------------
 .../json/resolver/MessageResolverTest.java         | 158 ++++++++++++++++++++
 2 files changed, 159 insertions(+), 162 deletions(-)

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 b2ae188..1a55237 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
@@ -72,7 +72,6 @@ import java.time.temporal.TemporalAccessor;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ArrayBlockingQueue;
@@ -82,10 +81,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
-import static org.apache.logging.log4j.layout.template.json.TestHelpers.CONFIGURATION;
-import static org.apache.logging.log4j.layout.template.json.TestHelpers.asMap;
-import static org.apache.logging.log4j.layout.template.json.TestHelpers.usingSerializedLogEventAccessor;
-import static org.apache.logging.log4j.layout.template.json.TestHelpers.writeJson;
+import static org.apache.logging.log4j.layout.template.json.TestHelpers.*;
 import static org.assertj.core.api.Assertions.assertThat;
 
 @SuppressWarnings("DoubleBraceInitialization")
@@ -293,91 +289,6 @@ class JsonTemplateLayoutTest {
     }
 
     @Test
-    void test_MapMessage_serialization() {
-
-        // Create the event template.
-        final String eventTemplate = writeJson(asMap(
-                "message", asMap("$resolver", "message")));
-
-        // Create the layout.
-        final JsonTemplateLayout layout = JsonTemplateLayout
-                .newBuilder()
-                .setConfiguration(CONFIGURATION)
-                .setEventTemplate(eventTemplate)
-                .build();
-
-        // Create the log event with a MapMessage.
-        final StringMapMessage mapMessage = new StringMapMessage()
-                .with("key1", "val1")
-                .with("key2", 0xDEADBEEF)
-                .with("key3", Collections.singletonMap("key3.1", "val3.1"));
-        final LogEvent logEvent = Log4jLogEvent
-                .newBuilder()
-                .setLoggerName(LOGGER_NAME)
-                .setLevel(Level.INFO)
-                .setMessage(mapMessage)
-                .setTimeMillis(System.currentTimeMillis())
-                .build();
-
-        // Check the serialized event.
-        usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
-            assertThat(accessor.getString(new String[]{"message", "key1"})).isEqualTo("val1");
-            assertThat(accessor.getInteger(new String[]{"message", "key2"})).isEqualTo(0xDEADBEEF);
-            assertThat(accessor.getString(new String[]{"message", "key3", "key3.1"})).isEqualTo("val3.1");
-        });
-
-    }
-
-    @Test
-    void test_message_fallbackKey() {
-
-        // Create the event template.
-        final String eventTemplate = writeJson(asMap(
-                "message", asMap(
-                        "$resolver", "message",
-                        "fallbackKey", "formattedMessage")));
-
-        // Create the layout.
-        final JsonTemplateLayout layout = JsonTemplateLayout
-                .newBuilder()
-                .setConfiguration(CONFIGURATION)
-                .setEventTemplate(eventTemplate)
-                .build();
-
-        // Create a log event with a MapMessage.
-        final Message mapMessage = new StringMapMessage()
-                .with("key1", "val1");
-        final LogEvent mapMessageLogEvent = Log4jLogEvent
-                .newBuilder()
-                .setLoggerName(LOGGER_NAME)
-                .setLevel(Level.INFO)
-                .setMessage(mapMessage)
-                .setTimeMillis(System.currentTimeMillis())
-                .build();
-
-        // Check the serialized MapMessage.
-        usingSerializedLogEventAccessor(layout, mapMessageLogEvent, accessor ->
-                assertThat(accessor.getString(new String[]{"message", "key1"}))
-                        .isEqualTo("val1"));
-
-        // Create a log event with a SimpleMessage.
-        final Message simpleMessage = new SimpleMessage("simple");
-        final LogEvent simpleMessageLogEvent = Log4jLogEvent
-                .newBuilder()
-                .setLoggerName(LOGGER_NAME)
-                .setLevel(Level.INFO)
-                .setMessage(simpleMessage)
-                .setTimeMillis(System.currentTimeMillis())
-                .build();
-
-        // Check the serialized MapMessage.
-        usingSerializedLogEventAccessor(layout, simpleMessageLogEvent, accessor ->
-                assertThat(accessor.getString(new String[]{"message", "formattedMessage"}))
-                        .isEqualTo("simple"));
-
-    }
-
-    @Test
     void test_property_injection() {
 
         // Create the log event.
@@ -666,78 +577,6 @@ class JsonTemplateLayoutTest {
     }
 
     @Test
-    void test_StringMapMessage() {
-
-        // Create the log event.
-        final StringMapMessage message = new StringMapMessage();
-        message.put("message", "Hello, World!");
-        message.put("bottle", "Kickapoo Joy Juice");
-        final LogEvent logEvent = Log4jLogEvent
-                .newBuilder()
-                .setLoggerName(LOGGER_NAME)
-                .setLevel(Level.INFO)
-                .setMessage(message)
-                .build();
-
-        // Create the event template.
-        final String eventTemplate = writeJson(asMap(
-                "message", asMap("$resolver", "message")));
-
-        // Create the layout.
-        final JsonTemplateLayout layout = JsonTemplateLayout
-                .newBuilder()
-                .setConfiguration(CONFIGURATION)
-                .setStackTraceEnabled(true)
-                .setEventTemplate(eventTemplate)
-                .build();
-
-        // Check the serialized event.
-        usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
-            assertThat(accessor.getString(new String[]{"message", "message"})).isEqualTo("Hello, World!");
-            assertThat(accessor.getString(new String[]{"message", "bottle"})).isEqualTo("Kickapoo Joy Juice");
-        });
-
-    }
-
-    @Test
-    void test_ObjectMessage() {
-
-        // Create the log event.
-        final int id = 0xDEADBEEF;
-        final String name = "name-" + id;
-        final Object attachment = new LinkedHashMap<String, Object>() {{
-            put("id", id);
-            put("name", name);
-        }};
-        final ObjectMessage message = new ObjectMessage(attachment);
-        final LogEvent logEvent = Log4jLogEvent
-                .newBuilder()
-                .setLoggerName(LOGGER_NAME)
-                .setLevel(Level.INFO)
-                .setMessage(message)
-                .build();
-
-        // Create the event template.
-        final String eventTemplate = writeJson(asMap(
-                "message", asMap("$resolver", "message")));
-
-        // Create the layout.
-        JsonTemplateLayout layout = JsonTemplateLayout
-                .newBuilder()
-                .setConfiguration(CONFIGURATION)
-                .setStackTraceEnabled(true)
-                .setEventTemplate(eventTemplate)
-                .build();
-
-        // Check the serialized event.
-        usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
-            assertThat(accessor.getInteger(new String[]{"message", "id"})).isEqualTo(id);
-            assertThat(accessor.getString(new String[]{"message", "name"})).isEqualTo(name);
-        });
-
-    }
-
-    @Test
     void test_StackTraceElement_template() {
 
         // Create the stack trace element template.
diff --git a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/MessageResolverTest.java b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/MessageResolverTest.java
index 7aa8b24..34b2f77 100644
--- a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/MessageResolverTest.java
+++ b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/MessageResolverTest.java
@@ -16,19 +16,30 @@
  */
 package org.apache.logging.log4j.layout.template.json.resolver;
 
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.impl.Log4jLogEvent;
 import org.apache.logging.log4j.core.test.junit.LoggerContextSource;
 import org.apache.logging.log4j.core.test.junit.Named;
+import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout;
 import org.apache.logging.log4j.layout.template.json.util.JsonReader;
 import org.apache.logging.log4j.core.test.appender.ListAppender;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.message.ObjectMessage;
+import org.apache.logging.log4j.message.SimpleMessage;
+import org.apache.logging.log4j.message.StringMapMessage;
 import org.assertj.core.api.Assertions;
 import org.junit.jupiter.api.Test;
 
 import java.nio.charset.StandardCharsets;
 import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import static org.apache.logging.log4j.layout.template.json.TestHelpers.*;
+import static org.assertj.core.api.Assertions.assertThat;
+
 class MessageResolverTest {
 
     /**
@@ -74,4 +85,151 @@ class MessageResolverTest {
 
     }
 
+    @Test
+    void test_message_fallbackKey() {
+
+        // Create the event template.
+        final String eventTemplate = writeJson(asMap(
+                "message", asMap(
+                        "$resolver", "message",
+                        "fallbackKey", "formattedMessage")));
+
+        // Create the layout.
+        final JsonTemplateLayout layout = JsonTemplateLayout
+                .newBuilder()
+                .setConfiguration(CONFIGURATION)
+                .setEventTemplate(eventTemplate)
+                .build();
+
+        // Create a log event with a MapMessage.
+        final Message mapMessage = new StringMapMessage()
+                .with("key1", "val1");
+        final LogEvent mapMessageLogEvent = Log4jLogEvent
+                .newBuilder()
+                .setMessage(mapMessage)
+                .setTimeMillis(System.currentTimeMillis())
+                .build();
+
+        // Check the serialized MapMessage.
+        usingSerializedLogEventAccessor(layout, mapMessageLogEvent, accessor ->
+                assertThat(accessor.getString(new String[]{"message", "key1"}))
+                        .isEqualTo("val1"));
+
+        // Create a log event with a SimpleMessage.
+        final Message simpleMessage = new SimpleMessage("simple");
+        final LogEvent simpleMessageLogEvent = Log4jLogEvent
+                .newBuilder()
+                .setMessage(simpleMessage)
+                .setTimeMillis(System.currentTimeMillis())
+                .build();
+
+        // Check the serialized MapMessage.
+        usingSerializedLogEventAccessor(layout, simpleMessageLogEvent, accessor ->
+                assertThat(accessor.getString(new String[]{"message", "formattedMessage"}))
+                        .isEqualTo("simple"));
+
+    }
+
+    @Test
+    void test_StringMapMessage() {
+
+        // Create the log event.
+        final StringMapMessage message = new StringMapMessage();
+        message.put("message", "Hello, World!");
+        message.put("bottle", "Kickapoo Joy Juice");
+        final LogEvent logEvent = Log4jLogEvent
+                .newBuilder()
+                .setMessage(message)
+                .build();
+
+        // Create the event template.
+        final String eventTemplate = writeJson(asMap(
+                "message", asMap("$resolver", "message")));
+
+        // Create the layout.
+        final JsonTemplateLayout layout = JsonTemplateLayout
+                .newBuilder()
+                .setConfiguration(CONFIGURATION)
+                .setStackTraceEnabled(true)
+                .setEventTemplate(eventTemplate)
+                .build();
+
+        // Check the serialized event.
+        usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+            assertThat(accessor.getString(new String[]{"message", "message"})).isEqualTo("Hello, World!");
+            assertThat(accessor.getString(new String[]{"message", "bottle"})).isEqualTo("Kickapoo Joy Juice");
+        });
+
+    }
+
+    @Test
+    void test_ObjectMessage() {
+
+        // Create the log event.
+        final int id = 0xDEADBEEF;
+        final String name = "name-" + id;
+        final Object attachment = new LinkedHashMap<String, Object>() {{
+            put("id", id);
+            put("name", name);
+        }};
+        final ObjectMessage message = new ObjectMessage(attachment);
+        final LogEvent logEvent = Log4jLogEvent
+                .newBuilder()
+                .setMessage(message)
+                .build();
+
+        // Create the event template.
+        final String eventTemplate = writeJson(asMap(
+                "message", asMap("$resolver", "message")));
+
+        // Create the layout.
+        JsonTemplateLayout layout = JsonTemplateLayout
+                .newBuilder()
+                .setConfiguration(CONFIGURATION)
+                .setStackTraceEnabled(true)
+                .setEventTemplate(eventTemplate)
+                .build();
+
+        // Check the serialized event.
+        usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+            assertThat(accessor.getInteger(new String[]{"message", "id"})).isEqualTo(id);
+            assertThat(accessor.getString(new String[]{"message", "name"})).isEqualTo(name);
+        });
+
+    }
+
+    @Test
+    void test_MapMessage_serialization() {
+
+        // Create the event template.
+        final String eventTemplate = writeJson(asMap(
+                "message", asMap("$resolver", "message")));
+
+        // Create the layout.
+        final JsonTemplateLayout layout = JsonTemplateLayout
+                .newBuilder()
+                .setConfiguration(CONFIGURATION)
+                .setEventTemplate(eventTemplate)
+                .build();
+
+        // Create the log event with a MapMessage.
+        final StringMapMessage mapMessage = new StringMapMessage()
+                .with("key1", "val1")
+                .with("key2", 0xDEADBEEF)
+                .with("key3", Collections.singletonMap("key3.1", "val3.1"));
+        final LogEvent logEvent = Log4jLogEvent
+                .newBuilder()
+                .setMessage(mapMessage)
+                .setTimeMillis(System.currentTimeMillis())
+                .build();
+
+        // Check the serialized event.
+        usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+            assertThat(accessor.getString(new String[]{"message", "key1"})).isEqualTo("val1");
+            assertThat(accessor.getInteger(new String[]{"message", "key2"})).isEqualTo(0xDEADBEEF);
+            assertThat(accessor.getString(new String[]{"message", "key3", "key3.1"})).isEqualTo("val3.1");
+        });
+
+    }
+
 }

[logging-log4j2] 01/05: LOG4J2-3074 Add replacement parameter to ReadOnlyStringMapResolver.

Posted by vy...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

vy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 37d865a03eb538f43021b2fd1f190e58d8ad8012
Author: Volkan Yazici <vo...@gmail.com>
AuthorDate: Wed Jul 7 10:47:12 2021 +0200

    LOG4J2-3074 Add replacement parameter to ReadOnlyStringMapResolver.
---
 .../json/resolver/ReadOnlyStringMapResolver.java   |  87 +++++++++++++----
 .../resolver/ReadOnlyStringMapResolverTest.java    | 107 +++++++++++++++++++++
 src/changes/changes.xml                            |   3 +
 .../asciidoc/manual/json-template-layout.adoc.vm   |  51 ++++++++--
 4 files changed, 222 insertions(+), 26 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 3735017..788fa5d 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
@@ -25,6 +25,7 @@ import org.apache.logging.log4j.util.TriConsumer;
 
 import java.util.Map;
 import java.util.function.Function;
+import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 /**
@@ -39,8 +40,9 @@ import java.util.regex.Pattern;
  * key           = "key" -> string
  * stringified   = "stringified" -> boolean
  *
- * multiAccess   = [ pattern ] , [ flatten ] , [ stringified ]
+ * multiAccess   = [ pattern ] , [ replacement ] , [ flatten ] , [ stringified ]
  * pattern       = "pattern" -> string
+ * replacement   = "replacement" -> string
  * flatten       = "flatten" -> ( boolean | flattenConfig )
  * flattenConfig = [ flattenPrefix ]
  * flattenPrefix = "prefix" -> string
@@ -54,13 +56,20 @@ import java.util.regex.Pattern;
  * 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.
+ * Regex provided in the <tt>pattern</tt> is used to match against the keys.
+ * If provided, <tt>replacement</tt> will be used to replace the matched keys.
+ * 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.
  *
  * <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.
+ * <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>,
@@ -72,21 +81,21 @@ import java.util.regex.Pattern;
  * defined by the actual resolver, e.g., {@link MapResolver},
  * {@link ThreadContextDataResolver}.
  * <p>
- * Resolve the value of the field keyed with <tt>userRole</tt>:
+ * Resolve the value of the field keyed with <tt>user:role</tt>:
  *
  * <pre>
  * {
  *   "$resolver": "…",
- *   "key": "userRole"
+ *   "key": "user:role"
  * }
  * </pre>
  *
- * Resolve the string representation of the <tt>userRank</tt> field value:
+ * Resolve the string representation of the <tt>user:rank</tt> field value:
  *
  * <pre>
  * {
  *   "$resolver": "…",
- *   "key": "userRank",
+ *   "key": "user:rank",
  *   "stringified": true
  * }
  * </pre>
@@ -109,14 +118,35 @@ import java.util.regex.Pattern;
  * }
  * </pre>
  *
+ * Resolve all fields whose keys match with the <tt>user:(role|rank)</tt> regex
+ * into an object:
+ *
+ * <pre>
+ * {
+ *   "$resolver": "…",
+ *   "pattern": "user:(role|rank)"
+ * }
+ * </pre>
+ *
+ * Resolve all fields whose keys match with the <tt>user:(role|rank)</tt> regex
+ * into an object after removing the <tt>user:</tt> prefix in the key:
+ *
+ * <pre>
+ * {
+ *   "$resolver": "…",
+ *   "pattern": "user:(role|rank)",
+ *   "replacement": "$1"
+ * }
+ * </pre>
+ *
  * Merge all fields whose keys are matching with the
- * <tt>user(Role|Rank)</tt> regex into the parent:
+ * <tt>user:(role|rank)</tt> regex into the parent:
  *
  * <pre>
  * {
  *   "$resolver": "…",
  *   "flatten": true,
- *   "pattern": "user(Role|Rank)"
+ *   "pattern": "user:(role|rank)"
  * }
  * </pre>
  *
@@ -162,15 +192,24 @@ class ReadOnlyStringMapResolver implements EventResolver {
         } 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 key = config.getString("key");
+        if (key != null && flatten) {
+            throw new IllegalArgumentException(
+                    "key and flatten options cannot be combined: " + config);
+        }
         final String pattern = config.getString("pattern");
+        if (pattern != null && key != null) {
+            throw new IllegalArgumentException(
+                    "pattern and key options cannot be combined: " + config);
+        }
+        final String replacement = config.getString("replacement");
+        if (pattern == null && replacement != null) {
+            throw new IllegalArgumentException(
+                    "replacement cannot be provided without a pattern: " + config);
+        }
         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, mapAccessor);
         } else {
             final RecyclerFactory recyclerFactory = context.getRecyclerFactory();
@@ -179,6 +218,7 @@ class ReadOnlyStringMapResolver implements EventResolver {
                     flatten,
                     prefix,
                     pattern,
+                    replacement,
                     stringified,
                     mapAccessor);
         }
@@ -216,6 +256,7 @@ class ReadOnlyStringMapResolver implements EventResolver {
             final boolean flatten,
             final String prefix,
             final String pattern,
+            final String replacement,
             final boolean stringified,
             final Function<LogEvent, ReadOnlyStringMap> mapAccessor) {
 
@@ -234,6 +275,7 @@ class ReadOnlyStringMapResolver implements EventResolver {
                         loopContext.prefixedKey = new StringBuilder(prefix);
                     }
                     loopContext.pattern = compiledPattern;
+                    loopContext.replacement = replacement;
                     loopContext.stringified = stringified;
                     return loopContext;
                 });
@@ -262,7 +304,7 @@ class ReadOnlyStringMapResolver implements EventResolver {
 
             @Override
             public void resolve(final LogEvent value, final JsonWriter jsonWriter) {
-                throw new UnsupportedOperationException();
+                resolve(value, jsonWriter, false);
             }
 
             @Override
@@ -310,6 +352,8 @@ class ReadOnlyStringMapResolver implements EventResolver {
 
         private Pattern pattern;
 
+        private String replacement;
+
         private boolean stringified;
 
         private JsonWriter jsonWriter;
@@ -329,10 +373,15 @@ class ReadOnlyStringMapResolver implements EventResolver {
                 final String key,
                 final Object value,
                 final LoopContext loopContext) {
-            final boolean keyMatched =
-                    loopContext.pattern == null ||
-                            loopContext.pattern.matcher(key).matches();
+            final Matcher matcher = loopContext.pattern != null
+                    ? loopContext.pattern.matcher(key)
+                    : null;
+            final boolean keyMatched = matcher == null || matcher.matches();
             if (keyMatched) {
+                final String replacedKey =
+                        matcher != null && loopContext.replacement != null
+                                ? matcher.replaceAll(loopContext.replacement)
+                                : key;
                 final boolean succeedingEntry =
                         loopContext.succeedingEntry ||
                                 loopContext.initJsonWriterStringBuilderLength <
@@ -341,10 +390,10 @@ class ReadOnlyStringMapResolver implements EventResolver {
                     loopContext.jsonWriter.writeSeparator();
                 }
                 if (loopContext.prefix == null) {
-                    loopContext.jsonWriter.writeObjectKey(key);
+                    loopContext.jsonWriter.writeObjectKey(replacedKey);
                 } else {
                     loopContext.prefixedKey.setLength(loopContext.prefix.length());
-                    loopContext.prefixedKey.append(key);
+                    loopContext.prefixedKey.append(replacedKey);
                     loopContext.jsonWriter.writeObjectKey(loopContext.prefixedKey);
                 }
                 if (loopContext.stringified && !(value instanceof String)) {
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
new file mode 100644
index 0000000..baaedef
--- /dev/null
+++ b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolverTest.java
@@ -0,0 +1,107 @@
+package org.apache.logging.log4j.layout.template.json.resolver;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.impl.Log4jLogEvent;
+import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout;
+import org.apache.logging.log4j.util.SortedArrayStringMap;
+import org.apache.logging.log4j.util.StringMap;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.regex.PatternSyntaxException;
+
+import static org.apache.logging.log4j.layout.template.json.TestHelpers.*;
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ReadOnlyStringMapResolverTest {
+
+    @Test
+    void key_should_not_be_allowed_with_flatten() {
+        verifyConfigFailure(
+                writeJson(asMap(
+                        "$resolver", "mdc",
+                        "key", "foo",
+                        "flatten", true)),
+                IllegalArgumentException.class,
+                "key and flatten options cannot be combined");
+    }
+
+    @Test
+    void invalid_pattern_should_fail() {
+        verifyConfigFailure(
+                writeJson(asMap(
+                        "$resolver", "mdc",
+                        "pattern", "[1")),
+                PatternSyntaxException.class,
+                "Unclosed character");
+    }
+
+    @Test
+    void pattern_should_not_be_allowed_with_key() {
+        verifyConfigFailure(
+                writeJson(asMap(
+                        "$resolver", "mdc",
+                        "key", "foo",
+                        "pattern", "bar")),
+                IllegalArgumentException.class,
+                "pattern and key options cannot be combined");
+    }
+
+    @Test
+    void replacement_should_not_be_allowed_without_pattern() {
+        verifyConfigFailure(
+                writeJson(asMap(
+                        "$resolver", "mdc",
+                        "replacement", "$1")),
+                IllegalArgumentException.class,
+                "replacement cannot be provided without a pattern");
+    }
+
+    private void verifyConfigFailure(
+            final String eventTemplate,
+            final Class<? extends Throwable> failureClass,
+            final String failureMessage) {
+        Assertions
+                .assertThatThrownBy(() -> JsonTemplateLayout
+                        .newBuilder()
+                        .setConfiguration(CONFIGURATION)
+                        .setEventTemplate(eventTemplate)
+                        .build())
+                .isInstanceOf(failureClass)
+                .hasMessageContaining(failureMessage);
+    }
+
+    @Test
+    void pattern_replacement_should_work() {
+
+        // Create the event template.
+        final String eventTemplate = writeJson(asMap(
+                "$resolver", "mdc",
+                "pattern", "user:(role|rank)",
+                "replacement", "$1"));
+
+        // Create the layout.
+        final JsonTemplateLayout layout = JsonTemplateLayout
+                .newBuilder()
+                .setConfiguration(CONFIGURATION)
+                .setEventTemplate(eventTemplate)
+                .build();
+
+        // Create the log event.
+        final StringMap contextData = new SortedArrayStringMap();
+        contextData.putValue("user:role", "engineer");
+        contextData.putValue("user:rank", "senior");
+        final LogEvent logEvent = Log4jLogEvent
+                .newBuilder()
+                .setContextData(contextData)
+                .build();
+
+        // Check the serialized event.
+        usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+            assertThat(accessor.getString("role")).isEqualTo("engineer");
+            assertThat(accessor.getString("rank")).isEqualTo("senior");
+        });
+
+    }
+
+}
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 0b7c3e0..3222b34 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -170,6 +170,9 @@
     </release>
     <release version="2.15.0" date="2021-MM-DD" description="GA Release 2.15.0">
       <!-- ADDS -->
+      <action issue="LOG4J2-3074" dev="vy" type="add">
+        Add replacement parameter to ReadOnlyStringMapResolver.
+      </action>
       <action issue="LOG4J2-3051" dev="vy" type="add">
         Add CaseConverterResolver to JsonTemplateLayout.
       </action>
diff --git a/src/site/asciidoc/manual/json-template-layout.adoc.vm b/src/site/asciidoc/manual/json-template-layout.adoc.vm
index 8fad470..d3204f6 100644
--- a/src/site/asciidoc/manual/json-template-layout.adoc.vm
+++ b/src/site/asciidoc/manual/json-template-layout.adoc.vm
@@ -1279,8 +1279,9 @@ singleAccess  = key , [ stringified ]
 key           = "key" -> string
 stringified   = "stringified" -> boolean
 
-multiAccess   = [ pattern ] , [ flatten ] , [ stringified ]
+multiAccess   = [ pattern ] , [ replacement ] , [ flatten ] , [ stringified ]
 pattern       = "pattern" -> string
+replacement   = "replacement" -> string
 flatten       = "flatten" -> ( boolean | flattenConfig )
 flattenConfig = [ flattenPrefix ]
 flattenPrefix = "prefix" -> string
@@ -1290,32 +1291,45 @@ flattenPrefix = "prefix" -> string
 multitude of fields. If `flatten` is provided, `multiAccess` merges the fields
 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. 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.
+
 [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.
+
+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.
 ====
 
 `"${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`:
+Resolve the value of the field keyed with `user:role`:
 
 [source,json]
 ----
 {
   "$resolver": "…",
-  "key": "userRole"
+  "key": "user:role"
 }
 ----
 
-Resolve the string representation of the `userRank` field value:
+Resolve the string representation of the `user:rank` field value:
 
 [source,json]
 ----
 {
   "$resolver": "…",
-  "key": "userRank",
+  "key": "user:rank",
   "stringified": true
 }
 ----
@@ -1339,7 +1353,30 @@ Resolve all fields into an object such that values are converted to string:
 }
 ----
 
-Merge all fields whose keys are matching with the `user(Role|Rank)` regex into
+Resolve all fields whose keys match with the `user:(role|rank)` regex into an
+object:
+
+[source,json]
+----
+{
+  "$resolver": "…",
+  "pattern": "user:(role|rank)"
+}
+----
+
+Resolve all fields whose keys match with the `user:(role|rank)` regex into an
+object after removing the `user:` prefix in the key:
+
+[source,json]
+----
+{
+  "$resolver": "…",
+  "pattern": "user:(role|rank)",
+  "replacement": "$1"
+}
+----
+
+Merge all fields whose keys are matching with the `user:(role|rank)` regex into
 the parent:
 
 [source,json]
@@ -1347,7 +1384,7 @@ the parent:
 {
   "$resolver": "…",
   "flatten": true,
-  "pattern": "user(Role|Rank)"
+  "pattern": "user:(role|rank)"
 }
 ----
 

[logging-log4j2] 05/05: LOG4J2-3067 Add CounterResolver.

Posted by vy...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

vy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 137a8e00b3fee80724af7f6a36b425a4c920f4c7
Author: Volkan Yazici <vo...@gmail.com>
AuthorDate: Wed Jul 7 15:04:10 2021 +0200

    LOG4J2-3067 Add CounterResolver.
---
 .../json/resolver/CaseConverterResolver.java       |   1 -
 .../template/json/resolver/CounterResolver.java    | 247 +++++++++++++++++++++
 .../json/resolver/CounterResolverFactory.java      |  50 +++++
 .../json/resolver/CounterResolverTest.java         | 158 +++++++++++++
 src/changes/changes.xml                            |   3 +
 .../asciidoc/manual/json-template-layout.adoc.vm   |  64 ++++++
 6 files changed, 522 insertions(+), 1 deletion(-)

diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CaseConverterResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CaseConverterResolver.java
index 559cba1..bbc4946 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CaseConverterResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CaseConverterResolver.java
@@ -44,7 +44,6 @@ import java.util.function.Function;
  *                           "replace"
  *                         )
  * replacement           = "replacement" -> JSON
- *
  * </pre>
  *
  * {@code input} can be any available template value; e.g., a JSON literal,
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java
new file mode 100644
index 0000000..aa4a139
--- /dev/null
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java
@@ -0,0 +1,247 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+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 java.math.BigInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.LockSupport;
+import java.util.function.Consumer;
+
+/**
+ * Resolves a number from an internal counter.
+ *
+ * <h3>Configuration</h3>
+ *
+ * <pre>
+ * config      = [ start ] , [ overflowing ] , [ stringified ]
+ * start       = "start" -> number
+ * overflowing = "overflowing" -> boolean
+ * stringified = "stringified" -> boolean
+ * </pre>
+ *
+ * Unless provided, <tt>start</tt> and <tt>overflowing</tt> are respectively
+ * set to zero and <tt>true</tt> by default.
+ * <p>
+ * When <tt>overflowing</tt> is set to <tt>true</tt>, the internal counter
+ * is created using a <tt>long</tt>, which is subject to overflow while
+ * incrementing, though garbage-free. Otherwise, a {@link BigInteger} is used,
+ * which does not overflow, but incurs allocation costs.
+ * <p>
+ * When <tt>stringified</tt> is enabled, which is set to <tt>false</tt> by
+ * default, the resolved number will be converted to a string.
+ *
+ * <h3>Examples</h3>
+ *
+ * Resolves a sequence of numbers starting from 0. Once {@link Long#MAX_VALUE}
+ * is reached, counter overflows to {@link Long#MIN_VALUE}.
+ *
+ * <pre>
+ * {
+ *   "$resolver": "counter"
+ * }
+ * </pre>
+ *
+ * Resolves a sequence of numbers starting from 1000. Once {@link Long#MAX_VALUE}
+ * is reached, counter overflows to {@link Long#MIN_VALUE}.
+ *
+ * <pre>
+ * {
+ *   "$resolver": "counter",
+ *   "start": 1000
+ * }
+ * </pre>
+ *
+ * Resolves a sequence of numbers starting from 0 and keeps on doing as long as
+ * JVM heap allows.
+ *
+ * <pre>
+ * {
+ *   "$resolver": "counter",
+ *   "overflowing": false
+ * }
+ * </pre>
+ */
+public class CounterResolver implements EventResolver {
+
+    private final Consumer<JsonWriter> delegate;
+
+    public CounterResolver(
+            final EventResolverContext context,
+            final TemplateResolverConfig config) {
+        this.delegate = createDelegate(context, config);
+    }
+
+    private static Consumer<JsonWriter> createDelegate(
+            final EventResolverContext context,
+            final TemplateResolverConfig config) {
+        final BigInteger start = readStart(config);
+        final boolean overflowing = config.getBoolean("overflowing", true);
+        final boolean stringified = config.getBoolean("stringified", false);
+        if (stringified) {
+            final Recycler<StringBuilder> stringBuilderRecycler =
+                    createStringBuilderRecycler(context);
+            return overflowing
+                    ? createStringifiedLongResolver(start, stringBuilderRecycler)
+                    : createStringifiedBigIntegerResolver(start, stringBuilderRecycler);
+        } else {
+            return overflowing
+                    ? createLongResolver(start)
+                    : createBigIntegerResolver(start);
+        }
+    }
+
+    private static BigInteger readStart(final TemplateResolverConfig config) {
+        final Object start = config.getObject("start", Object.class);
+        if (start == null) {
+            return BigInteger.ZERO;
+        } else if (start instanceof Short || start instanceof Integer || start instanceof Long) {
+            return BigInteger.valueOf(((Number) start).longValue());
+        } else if (start instanceof BigInteger) {
+            return (BigInteger) start;
+        } else {
+            final Class<?> clazz = start.getClass();
+            final String message = String.format(
+                    "could not read start of type %s: %s", clazz, config);
+            throw new IllegalArgumentException(message);
+        }
+    }
+
+    private static Consumer<JsonWriter> createLongResolver(final BigInteger start) {
+        final long effectiveStart = start.longValue();
+        final AtomicLong counter = new AtomicLong(effectiveStart);
+        return (jsonWriter) -> {
+            final long number = counter.getAndIncrement();
+            jsonWriter.writeNumber(number);
+        };
+    }
+
+    private static Consumer<JsonWriter> createBigIntegerResolver(final BigInteger start) {
+        final AtomicBigInteger counter = new AtomicBigInteger(start);
+        return jsonWriter -> {
+            final BigInteger number = counter.getAndIncrement();
+            jsonWriter.writeNumber(number);
+        };
+    }
+
+    private static Recycler<StringBuilder> createStringBuilderRecycler(
+            final EventResolverContext context) {
+        return context
+                .getRecyclerFactory()
+                .create(
+                        StringBuilder::new,
+                        stringBuilder -> {
+                            final int maxLength =
+                                    context.getJsonWriter().getMaxStringLength();
+                            trimStringBuilder(stringBuilder, maxLength);
+                        });
+    }
+
+    private static void trimStringBuilder(
+            final StringBuilder stringBuilder,
+            final int maxLength) {
+        if (stringBuilder.length() > maxLength) {
+            stringBuilder.setLength(maxLength);
+            stringBuilder.trimToSize();
+        }
+        stringBuilder.setLength(0);
+    }
+
+    private static Consumer<JsonWriter> createStringifiedLongResolver(
+            final BigInteger start,
+            final Recycler<StringBuilder> stringBuilderRecycler) {
+        final long effectiveStart = start.longValue();
+        final AtomicLong counter = new AtomicLong(effectiveStart);
+        return (jsonWriter) -> {
+            final long number = counter.getAndIncrement();
+            final StringBuilder stringBuilder = stringBuilderRecycler.acquire();
+            try {
+                stringBuilder.append(number);
+                jsonWriter.writeString(stringBuilder);
+            } finally {
+                stringBuilderRecycler.release(stringBuilder);
+            }
+        };
+    }
+
+    private static Consumer<JsonWriter> createStringifiedBigIntegerResolver(
+            final BigInteger start,
+            final Recycler<StringBuilder> stringBuilderRecycler) {
+        final AtomicBigInteger counter = new AtomicBigInteger(start);
+        return jsonWriter -> {
+            final BigInteger number = counter.getAndIncrement();
+            final StringBuilder stringBuilder = stringBuilderRecycler.acquire();
+            try {
+                stringBuilder.append(number);
+                jsonWriter.writeString(stringBuilder);
+            } finally {
+                stringBuilderRecycler.release(stringBuilder);
+            }
+        };
+    }
+
+    private static final class AtomicBigInteger {
+
+        private final AtomicReference<BigInteger> lastNumber;
+
+        private AtomicBigInteger(final BigInteger start) {
+            this.lastNumber = new AtomicReference<>(start);
+        }
+
+        private BigInteger getAndIncrement() {
+            BigInteger prevNumber;
+            BigInteger nextNumber;
+            do {
+                prevNumber = lastNumber.get();
+                nextNumber = prevNumber.add(BigInteger.ONE);
+            } while (!compareAndSetWithBackOff(prevNumber, nextNumber));
+            return prevNumber;
+        }
+
+        /**
+         * {@link AtomicReference#compareAndSet(Object, Object)} shortcut with a
+         * constant back off. This technique was originally described in
+         * <a href="https://arxiv.org/abs/1305.5800">Lightweight Contention
+         * Management for Efficient Compare-and-Swap Operations</a> and showed
+         * great results in benchmarks.
+         */
+        private boolean compareAndSetWithBackOff(
+                final BigInteger prevNumber,
+                final BigInteger nextNumber) {
+            if (lastNumber.compareAndSet(prevNumber, nextNumber)) {
+                return true;
+            }
+            LockSupport.parkNanos(1); // back-off
+            return false;
+        }
+
+    }
+
+    static String getName() {
+        return "counter";
+    }
+
+    @Override
+    public void resolve(final LogEvent ignored, final JsonWriter jsonWriter) {
+        delegate.accept(jsonWriter);
+    }
+
+}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolverFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolverFactory.java
new file mode 100644
index 0000000..60217ab
--- /dev/null
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolverFactory.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.layout.template.json.resolver;
+
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginFactory;
+
+/**
+ * {@link CounterResolver} factory.
+ */
+@Plugin(name = "CounterResolverFactory", category = TemplateResolverFactory.CATEGORY)
+public final class CounterResolverFactory implements EventResolverFactory {
+
+    private static final CounterResolverFactory INSTANCE =
+            new CounterResolverFactory();
+
+    private CounterResolverFactory() {}
+
+    @PluginFactory
+    public static CounterResolverFactory getInstance() {
+        return INSTANCE;
+    }
+
+    @Override
+    public String getName() {
+        return CounterResolver.getName();
+    }
+
+    @Override
+    public CounterResolver create(
+            final EventResolverContext context,
+            final TemplateResolverConfig config) {
+        return new CounterResolver(context, config);
+    }
+
+}
diff --git a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolverTest.java b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolverTest.java
new file mode 100644
index 0000000..d229fcf
--- /dev/null
+++ b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolverTest.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.layout.template.json.resolver;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.impl.Log4jLogEvent;
+import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout;
+import org.apache.logging.log4j.layout.template.json.util.JsonReader;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigInteger;
+
+import static org.apache.logging.log4j.layout.template.json.TestHelpers.*;
+import static org.assertj.core.api.Assertions.assertThat;
+
+class CounterResolverTest {
+
+    @Test
+    void no_arg_setup_should_start_from_zero() {
+        final String eventTemplate = writeJson(asMap("$resolver", "counter"));
+        verify(eventTemplate, 0, 1);
+    }
+
+    @Test
+    void positive_start_should_work() {
+        final String eventTemplate = writeJson(asMap(
+                "$resolver", "counter",
+                "start", 3));
+        verify(eventTemplate, 3, 4);
+    }
+
+    @Test
+    void positive_start_should_work_when_stringified() {
+        final String eventTemplate = writeJson(asMap(
+                "$resolver", "counter",
+                "start", 3,
+                "stringified", true));
+        verify(eventTemplate, "3", "4");
+    }
+
+    @Test
+    void negative_start_should_work() {
+        final String eventTemplate = writeJson(asMap(
+                "$resolver", "counter",
+                "start", -3));
+        verify(eventTemplate, -3, -2);
+    }
+
+    @Test
+    void negative_start_should_work_when_stringified() {
+        final String eventTemplate = writeJson(asMap(
+                "$resolver", "counter",
+                "start", -3,
+                "stringified", true));
+        verify(eventTemplate, "-3", "-2");
+    }
+
+    @Test
+    void min_long_should_work_when_overflow_enabled() {
+        final String eventTemplate = writeJson(asMap(
+                "$resolver", "counter",
+                "start", Long.MIN_VALUE));
+        verify(eventTemplate, Long.MIN_VALUE, Long.MIN_VALUE + 1L);
+    }
+
+    @Test
+    void min_long_should_work_when_overflow_enabled_and_stringified() {
+        final String eventTemplate = writeJson(asMap(
+                "$resolver", "counter",
+                "start", Long.MIN_VALUE,
+                "stringified", true));
+        verify(eventTemplate, "" + Long.MIN_VALUE, "" + (Long.MIN_VALUE + 1L));
+    }
+
+    @Test
+    void max_long_should_work_when_overflowing() {
+        final String eventTemplate = writeJson(asMap(
+                "$resolver", "counter",
+                "start", Long.MAX_VALUE));
+        verify(eventTemplate, Long.MAX_VALUE, Long.MIN_VALUE);
+    }
+
+    @Test
+    void max_long_should_work_when_overflowing_and_stringified() {
+        final String eventTemplate = writeJson(asMap(
+                "$resolver", "counter",
+                "start", Long.MAX_VALUE,
+                "stringified", true));
+        verify(eventTemplate, "" + Long.MAX_VALUE, "" + Long.MIN_VALUE);
+    }
+
+    @Test
+    void max_long_should_work_when_not_overflowing() {
+        final String eventTemplate = writeJson(asMap(
+                "$resolver", "counter",
+                "start", Long.MAX_VALUE,
+                "overflowing", false));
+        verify(
+                eventTemplate,
+                Long.MAX_VALUE,
+                BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE));
+    }
+
+    @Test
+    void max_long_should_work_when_not_overflowing_and_stringified() {
+        final String eventTemplate = writeJson(asMap(
+                "$resolver", "counter",
+                "start", Long.MAX_VALUE,
+                "overflowing", false,
+                "stringified", true));
+        verify(
+                eventTemplate,
+                "" + Long.MAX_VALUE,
+                "" + BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE));
+    }
+
+    private static void verify(
+            final String eventTemplate,
+            final Object expectedNumber1,
+            final Object expectedNumber2) {
+
+        // Create the layout.
+        final JsonTemplateLayout layout = JsonTemplateLayout
+                .newBuilder()
+                .setConfiguration(CONFIGURATION)
+                .setEventTemplate(eventTemplate)
+                .build();
+
+        // Create the log event.
+        final LogEvent logEvent = Log4jLogEvent.newBuilder().build();
+
+        // Check the 1st serialized event.
+        final String serializedLogEvent1 = layout.toSerializable(logEvent);
+        final Object deserializedLogEvent1 = JsonReader.read(serializedLogEvent1);
+        assertThat(deserializedLogEvent1).isEqualTo(expectedNumber1);
+
+        // Check the 2nd serialized event.
+        final String serializedLogEvent2 = layout.toSerializable(logEvent);
+        final Object deserializedLogEvent2 = JsonReader.read(serializedLogEvent2);
+        assertThat(deserializedLogEvent2).isEqualTo(expectedNumber2);
+
+    }
+
+}
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 3222b34..00f3a5e 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -170,6 +170,9 @@
     </release>
     <release version="2.15.0" date="2021-MM-DD" description="GA Release 2.15.0">
       <!-- ADDS -->
+      <action issue="LOG4J2-3067" dev="vy" type="add">
+        Add CounterResolver to JsonTemplateLayout.
+      </action>
       <action issue="LOG4J2-3074" dev="vy" type="add">
         Add replacement parameter to ReadOnlyStringMapResolver.
       </action>
diff --git a/src/site/asciidoc/manual/json-template-layout.adoc.vm b/src/site/asciidoc/manual/json-template-layout.adoc.vm
index d3204f6..80f706a 100644
--- a/src/site/asciidoc/manual/json-template-layout.adoc.vm
+++ b/src/site/asciidoc/manual/json-template-layout.adoc.vm
@@ -454,6 +454,67 @@ similar to the following:
 The complete list of available event template resolvers are provided below in
 detail.
 
+[#event-template-resolver-counter]
+===== `counter`
+
+[source]
+----
+config      = [ start ] , [ overflowing ] , [ stringified ]
+start       = "start" -> number
+overflowing = "overflowing" -> boolean
+stringified = "stringified" -> boolean
+----
+
+Resolves a number from an internal counter.
+
+Unless provided, `start` and `overflowing` are respectively set to zero and
+`true` by default.
+
+When `stringified` is enabled, which is set to `false by default, the resolved
+number will be converted to a string.
+
+[WARNING]
+====
+When `overflowing` is set to `true`, the internal counter is created using a
+`long`, which is subject to overflow while incrementing, though garbage-free.
+Otherwise, a `BigInteger` is used, which does not overflow, but incurs
+allocation costs.
+====
+
+====== Examples
+
+Resolves a sequence of numbers starting from 0. Once `Long.MAX_VALUE` is
+reached, counter overflows to `Long.MIN_VALUE`.
+
+[source,json]
+----
+{
+  "$resolver": "counter"
+}
+----
+
+Resolves a sequence of numbers starting from 1000. Once `Long.MAX_VALUE` is
+reached, counter overflows to `Long.MIN_VALUE`.
+
+[source,json]
+----
+{
+  "$resolver": "counter",
+  "start": 1000
+}
+----
+
+Resolves a sequence of numbers starting from 0 and keeps on doing as long as
+JVM heap allows.
+
+[source,json]
+----
+{
+  "$resolver": "counter",
+  "overflowing": false
+}
+----
+
 [#event-template-resolver-caseConverter]
 ===== `caseConverter`
 
@@ -501,8 +562,11 @@ is always expected to be of type string, using non-string ``replacement``s or
 `pass` in `errorHandlingStrategy` might result in type incompatibility issues at
 the storage level.
 
+[WARNING]
+====
 Unless the input value is ``pass``ed intact or ``replace``d, case conversion is
 not garbage-free.
+====
 
 ====== Examples