You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by vy...@apache.org on 2020/11/30 09:00:43 UTC

[logging-log4j2] branch master updated: LOG4J2-2967 Fix JTL parameter resolution for messages with too few parameters. (#443)

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


The following commit(s) were added to refs/heads/master by this push:
     new d728835  LOG4J2-2967 Fix JTL parameter resolution for messages with too few parameters. (#443)
d728835 is described below

commit d7288357f89e01d6d4877ba202e4153dcc31d723
Author: Carter Kozak <ck...@apache.org>
AuthorDate: Mon Nov 30 03:55:51 2020 -0500

    LOG4J2-2967 Fix JTL parameter resolution for messages with too few parameters. (#443)
---
 .../json/resolver/MessageParameterResolver.java    |  14 +-
 .../template/json/JsonTemplateLayoutTest.java      | 282 ++++++++++++---------
 src/changes/changes.xml                            |   3 +
 3 files changed, 181 insertions(+), 118 deletions(-)

diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java
index 059e5b1..866b3d1 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java
@@ -111,8 +111,13 @@ final class MessageParameterResolver implements EventResolver {
 
         // Short-circuit if there are no parameters.
         final Object[] parameters = message.getParameters();
-        if (parameters == null || parameters.length == 0) {
-            jsonWriter.writeNull();
+        if (parameters == null || parameters.length == 0 || index >= parameters.length) {
+            if (index < 0) {
+                jsonWriter.writeArrayStart();
+                jsonWriter.writeArrayEnd();
+            } else {
+                jsonWriter.writeNull();
+            }
             return;
         }
 
@@ -162,12 +167,17 @@ final class MessageParameterResolver implements EventResolver {
             if (arrayNeeded) {
                 jsonWriter.writeArrayStart();
             }
+            final StringBuilder buf = jsonWriter.getStringBuilder();
+            final int startIndex = buf.length();
             parameterConsumerState.resolver = this;
             parameterConsumerState.jsonWriter = jsonWriter;
             parameterVisitable.forEachParameter(
                     PARAMETER_CONSUMER, parameterConsumerState);
             if (arrayNeeded) {
                 jsonWriter.writeArrayEnd();
+            } else if (startIndex == buf.length()) {
+                // Handle the case in which index was not present in the event.
+                jsonWriter.writeNull();
             }
         } finally {
             parameterConsumerStateRecycler.release(parameterConsumerState);
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 a7503fc..f8d4990 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
@@ -225,10 +225,10 @@ class JsonTemplateLayoutTest {
         final String timestampFieldName = "@timestamp";
         final String staticFieldName = "staticFieldName";
         final String staticFieldValue = "staticFieldValue";
-        final String eventTemplate = writeJson(Map(
-                timestampFieldName, Map(
+        final String eventTemplate = writeJson(asMap(
+                timestampFieldName, asMap(
                         "$resolver", "timestamp",
-                        "pattern", Map("timeZone", "Europe/Amsterdam")),
+                        "pattern", asMap("timeZone", "Europe/Amsterdam")),
                 staticFieldName, staticFieldValue));
 
         // Create the layout.
@@ -250,8 +250,8 @@ class JsonTemplateLayoutTest {
     void test_log4j_deferred_runtime_resolver_for_MapMessage() {
 
         // Create the event template.
-        final String eventTemplate = writeJson(Map(
-                "mapValue3", Map("$resolver", "message"),
+        final String eventTemplate = writeJson(asMap(
+                "mapValue3", asMap("$resolver", "message"),
                 "mapValue1", "${map:key1}",
                 "mapValue2", "${map:key2}",
                 "nestedLookupEmptyValue", "${map:noExist:-${map:noExist2:-${map:noExist3:-}}}",
@@ -291,8 +291,8 @@ class JsonTemplateLayoutTest {
     void test_MapMessage_serialization() {
 
         // Create the event template.
-        final String eventTemplate = writeJson(Map(
-                "message", Map("$resolver", "message")));
+        final String eventTemplate = writeJson(asMap(
+                "message", asMap("$resolver", "message")));
 
         // Create the layout.
         final JsonTemplateLayout layout = JsonTemplateLayout
@@ -328,11 +328,11 @@ class JsonTemplateLayoutTest {
 
         // Create the event template.
         final String key = "list";
-        final String eventTemplate = writeJson(Map(
-                "typedValue", Map(
+        final String eventTemplate = writeJson(asMap(
+                "typedValue", asMap(
                         "$resolver", "map",
                         "key", key),
-                "stringifiedValue", Map(
+                "stringifiedValue", asMap(
                         "$resolver", "map",
                         "key", key,
                         "stringified", true)));
@@ -368,8 +368,8 @@ class JsonTemplateLayoutTest {
     void test_message_fallbackKey() {
 
         // Create the event template.
-        final String eventTemplate = writeJson(Map(
-                "message", Map(
+        final String eventTemplate = writeJson(asMap(
+                "message", asMap(
                         "$resolver", "message",
                         "fallbackKey", "formattedMessage")));
 
@@ -427,7 +427,7 @@ class JsonTemplateLayoutTest {
 
         // Create the event template with property.
         final String propertyName = "propertyName";
-        final String eventTemplate = writeJson(Map(
+        final String eventTemplate = writeJson(asMap(
                 propertyName, "${" + propertyName + "}"));
 
         // Create the layout with property.
@@ -463,24 +463,24 @@ class JsonTemplateLayoutTest {
                 .build();
 
         // Create the event template.
-        final String eventTemplate = writeJson(Map(
-                "ex_class", Map(
+        final String eventTemplate = writeJson(asMap(
+                "ex_class", asMap(
                         "$resolver", "exception",
                         "field", "className"),
-                "ex_message", Map(
+                "ex_message", asMap(
                         "$resolver", "exception",
                         "field", "message"),
-                "ex_stacktrace", Map(
+                "ex_stacktrace", asMap(
                         "$resolver", "exception",
                         "field", "stackTrace",
                         "stringified", true),
-                "root_ex_class", Map(
+                "root_ex_class", asMap(
                         "$resolver", "exceptionRootCause",
                         "field", "className"),
-                "root_ex_message", Map(
+                "root_ex_message", asMap(
                         "$resolver", "exceptionRootCause",
                         "field", "message"),
-                "root_ex_stacktrace", Map(
+                "root_ex_stacktrace", asMap(
                         "$resolver", "exceptionRootCause",
                         "field", "stackTrace",
                         "stringified", true)));
@@ -527,24 +527,24 @@ class JsonTemplateLayoutTest {
                 .build();
 
         // Create the event template.
-        final String eventTemplate = writeJson(Map(
-                "ex_class", Map(
+        final String eventTemplate = writeJson(asMap(
+                "ex_class", asMap(
                         "$resolver", "exception",
                         "field", "className"),
-                "ex_message", Map(
+                "ex_message", asMap(
                         "$resolver", "exception",
                         "field", "message"),
-                "ex_stacktrace", Map(
+                "ex_stacktrace", asMap(
                         "$resolver", "exception",
                         "field", "stackTrace",
                         "stringified", true),
-                "root_ex_class", Map(
+                "root_ex_class", asMap(
                         "$resolver", "exceptionRootCause",
                         "field", "className"),
-                "root_ex_message", Map(
+                "root_ex_message", asMap(
                         "$resolver", "exceptionRootCause",
                         "field", "message"),
-                "root_ex_stacktrace", Map(
+                "root_ex_stacktrace", asMap(
                         "$resolver", "exceptionRootCause",
                         "field", "stackTrace",
                         "stringified", true)));
@@ -593,9 +593,9 @@ class JsonTemplateLayoutTest {
         // Create the event template.
         final String messageKey = "message";
         final String markerNameKey = "marker";
-        final String eventTemplate = writeJson(Map(
-                "message", Map("$resolver", "message"),
-                "marker", Map(
+        final String eventTemplate = writeJson(asMap(
+                "message", asMap("$resolver", "message"),
+                "marker", asMap(
                         "$resolver", "marker",
                         "field", "name")));
 
@@ -671,14 +671,14 @@ class JsonTemplateLayoutTest {
             .build();
 
         // Create the template.
-        final String template = writeJson(Map(
-                "name", Map(
+        final String template = writeJson(asMap(
+                "name", asMap(
                         "$resolver", "main",
                         "key", kwKey),
-                "positionArg", Map(
+                "positionArg", asMap(
                         "$resolver", "main",
                         "index", 2),
-                "notFoundArg", Map(
+                "notFoundArg", asMap(
                         "$resolver", "main",
                         "key", missingKwKey)));
 
@@ -761,11 +761,11 @@ class JsonTemplateLayoutTest {
             final String resolverName) {
 
         // Create the event template.
-        String eventTemplate = writeJson(Map(
-                directlyAccessedKey, Map(
+        String eventTemplate = writeJson(asMap(
+                directlyAccessedKey, asMap(
                         "$resolver", resolverName,
                         "key", directlyAccessedKey),
-                directlyAccessedNullPropertyKey, Map(
+                directlyAccessedNullPropertyKey, asMap(
                         "$resolver", resolverName,
                         "key", directlyAccessedNullPropertyKey)));
 
@@ -852,8 +852,8 @@ class JsonTemplateLayoutTest {
 
         // Create the event template.
         final String mapFieldName = "map";
-        final String eventTemplate = writeJson(Map(
-                mapFieldName, Map(
+        final String eventTemplate = writeJson(asMap(
+                mapFieldName, asMap(
                         "$resolver", resolverName,
                         "pattern", patternMatchedKey)));
 
@@ -940,11 +940,11 @@ class JsonTemplateLayoutTest {
 
         // Create the event template.
         final String prefix = "_map.";
-        final String eventTemplate = writeJson(Map(
-                "ignoredFieldName", Map(
+        final String eventTemplate = writeJson(asMap(
+                "ignoredFieldName", asMap(
                         "$resolver", resolverName,
                         "pattern", patternMatchedKey,
-                        "flatten", Map("prefix", prefix))));
+                        "flatten", asMap("prefix", prefix))));
 
         // Create the layout.
         final JsonTemplateLayout layout = JsonTemplateLayout
@@ -975,11 +975,11 @@ class JsonTemplateLayoutTest {
                 .build();
 
         // Create the event template node with map values.
-        final String eventTemplate = writeJson(Map(
-                "mapValue1", Map(
+        final String eventTemplate = writeJson(asMap(
+                "mapValue1", asMap(
                         "$resolver", "map",
                         "key", "key1"),
-                "mapValue2", Map(
+                "mapValue2", asMap(
                         "$resolver", "map",
                         "key", "key?")));
 
@@ -1013,8 +1013,8 @@ class JsonTemplateLayoutTest {
                 .build();
 
         // Create the event template.
-        final String eventTemplate = writeJson(Map(
-                "message", Map("$resolver", "message")));
+        final String eventTemplate = writeJson(asMap(
+                "message", asMap("$resolver", "message")));
 
         // Create the layout.
         final JsonTemplateLayout layout = JsonTemplateLayout
@@ -1051,8 +1051,8 @@ class JsonTemplateLayoutTest {
                 .build();
 
         // Create the event template.
-        final String eventTemplate = writeJson(Map(
-                "message", Map("$resolver", "message")));
+        final String eventTemplate = writeJson(asMap(
+                "message", asMap("$resolver", "message")));
 
         // Create the layout.
         JsonTemplateLayout layout = JsonTemplateLayout
@@ -1078,24 +1078,24 @@ class JsonTemplateLayoutTest {
         final String methodNameFieldName = "methodName";
         final String fileNameFieldName = "fileName";
         final String lineNumberFieldName = "lineNumber";
-        final String stackTraceElementTemplate = writeJson(Map(
-                classNameFieldName, Map(
+        final String stackTraceElementTemplate = writeJson(asMap(
+                classNameFieldName, asMap(
                         "$resolver", "stackTraceElement",
                         "field", "className"),
-                methodNameFieldName, Map(
+                methodNameFieldName, asMap(
                         "$resolver", "stackTraceElement",
                         "field", "methodName"),
-                fileNameFieldName, Map(
+                fileNameFieldName, asMap(
                         "$resolver", "stackTraceElement",
                         "field", "fileName"),
-                lineNumberFieldName, Map(
+                lineNumberFieldName, asMap(
                         "$resolver", "stackTraceElement",
                         "field", "lineNumber")));
 
         // Create the event template.
         final String stackTraceFieldName = "stackTrace";
-        final String eventTemplate = writeJson(Map(
-                stackTraceFieldName, Map(
+        final String eventTemplate = writeJson(asMap(
+                stackTraceFieldName, asMap(
                         "$resolver", "exception",
                         "field", "stackTrace")));
 
@@ -1231,10 +1231,10 @@ class JsonTemplateLayoutTest {
         final String excessiveKey = Strings.repeat("k", maxStringLength) + 'K';
         final String excessiveValue = Strings.repeat("v", maxStringLength) + 'V';
         final String nullValueKey = "nullValueKey";
-        final String eventTemplate = writeJson(Map(
-                messageKey, Map("$resolver", "message"),
+        final String eventTemplate = writeJson(asMap(
+                messageKey, asMap("$resolver", "message"),
                 excessiveKey, excessiveValue,
-                nullValueKey, Map(
+                nullValueKey, asMap(
                         "$resolver", "exception",
                         "field", "message")));
 
@@ -1311,8 +1311,8 @@ class JsonTemplateLayoutTest {
                 .build();
 
         // Create the event template.
-        final String eventTemplate = writeJson(Map(
-                "ex_stacktrace", Map(
+        final String eventTemplate = writeJson(asMap(
+                "ex_stacktrace", asMap(
                         "$resolver", "exception",
                         "field", "stackTrace",
                         "stringified", true)));
@@ -1392,7 +1392,7 @@ class JsonTemplateLayoutTest {
     void test_timestamp_epoch_resolvers() {
 
         final List<Map<String, Object>> testCases = Arrays.asList(
-                Map(
+                asMap(
                         "epochSecs", new BigDecimal("1581082727.982123456"),
                         "epochSecsRounded", 1581082727,
                         "epochSecsNanos", 982123456,
@@ -1400,7 +1400,7 @@ class JsonTemplateLayoutTest {
                         "epochMillisRounded", 1581082727982L,
                         "epochMillisNanos", 123456,
                         "epochNanos", 1581082727982123456L),
-                Map(
+                asMap(
                         "epochSecs", new BigDecimal("1591177590.005000001"),
                         "epochSecsRounded", 1591177590,
                         "epochSecsNanos", 5000001,
@@ -1410,32 +1410,32 @@ class JsonTemplateLayoutTest {
                         "epochNanos", 1591177590005000001L));
 
         // Create the event template.
-        final String eventTemplate = writeJson(Map(
-                "epochSecs", Map(
+        final String eventTemplate = writeJson(asMap(
+                "epochSecs", asMap(
                         "$resolver", "timestamp",
-                        "epoch", Map("unit", "secs")),
-                "epochSecsRounded", Map(
+                        "epoch", asMap("unit", "secs")),
+                "epochSecsRounded", asMap(
                         "$resolver", "timestamp",
-                        "epoch", Map(
+                        "epoch", asMap(
                                 "unit", "secs",
                                 "rounded", true)),
-                "epochSecsNanos", Map(
+                "epochSecsNanos", asMap(
                         "$resolver", "timestamp",
-                        "epoch", Map("unit", "secs.nanos")),
-                "epochMillis", Map(
+                        "epoch", asMap("unit", "secs.nanos")),
+                "epochMillis", asMap(
                         "$resolver", "timestamp",
-                        "epoch", Map("unit", "millis")),
-                "epochMillisRounded", Map(
+                        "epoch", asMap("unit", "millis")),
+                "epochMillisRounded", asMap(
                         "$resolver", "timestamp",
-                        "epoch", Map(
+                        "epoch", asMap(
                                 "unit", "millis",
                                 "rounded", true)),
-                "epochMillisNanos", Map(
+                "epochMillisNanos", asMap(
                         "$resolver", "timestamp",
-                        "epoch", Map("unit", "millis.nanos")),
-                "epochNanos", Map(
+                        "epoch", asMap("unit", "millis.nanos")),
+                "epochNanos", asMap(
                         "$resolver", "timestamp",
-                        "epoch", Map("unit", "nanos"))));
+                        "epoch", asMap("unit", "nanos"))));
 
         // Create the layout.
         final JsonTemplateLayout layout = JsonTemplateLayout
@@ -1491,10 +1491,10 @@ class JsonTemplateLayoutTest {
         final LogEvent logEvent4 = createLogEventAtInstant(logEvent4FormattedInstant);
 
         // Create the event template.
-        final String eventTemplate = writeJson(Map(
-                "timestamp", Map(
+        final String eventTemplate = writeJson(asMap(
+                "timestamp", asMap(
                         "$resolver", "timestamp",
-                        "pattern", Map(
+                        "pattern", asMap(
                                 "format", "yyyy-MM-dd'T'HH:mm:ss'Z'",
                                 "timeZone", "UTC"))));
 
@@ -1544,15 +1544,15 @@ class JsonTemplateLayoutTest {
     void test_level_severity() {
 
         // Create the event template.
-        final String eventTemplate = writeJson(Map(
-                "severityKeyword", Map(
+        final String eventTemplate = writeJson(asMap(
+                "severityKeyword", asMap(
                         "$resolver", "level",
                         "field", "severity",
-                        "severity", Map("field", "keyword")),
-                "severityCode", Map(
+                        "severity", asMap("field", "keyword")),
+                "severityCode", asMap(
                         "$resolver", "level",
                         "field", "severity",
-                        "severity", Map("field", "code"))));
+                        "severity", asMap("field", "code"))));
 
         // Create the layout.
         final JsonTemplateLayout layout = JsonTemplateLayout
@@ -1597,18 +1597,18 @@ class JsonTemplateLayoutTest {
                 .build();
 
         // Create the event template.
-        final String eventTemplate = writeJson(Map(
-                "exStackTrace", Map(
+        final String eventTemplate = writeJson(asMap(
+                "exStackTrace", asMap(
                         "$resolver", "exception",
                         "field", "stackTrace"),
-                "exStackTraceString", Map(
+                "exStackTraceString", asMap(
                         "$resolver", "exception",
                         "field", "stackTrace",
                         "stringified", true),
-                "exRootCauseStackTrace", Map(
+                "exRootCauseStackTrace", asMap(
                         "$resolver", "exceptionRootCause",
                         "field", "stackTrace"),
-                "exRootCauseStackTraceString", Map(
+                "exRootCauseStackTraceString", asMap(
                         "$resolver", "exceptionRootCause",
                         "field", "stackTrace",
                         "stringified", true),
@@ -1637,8 +1637,8 @@ class JsonTemplateLayoutTest {
     void test_StackTraceTextResolver_with_maxStringLength() {
 
         // Create the event template.
-        final String eventTemplate = writeJson(Map(
-                "stackTrace", Map(
+        final String eventTemplate = writeJson(asMap(
+                "stackTrace", asMap(
                         "$resolver", "exception",
                         "field", "stackTrace",
                         "stringified", true)));
@@ -1675,7 +1675,7 @@ class JsonTemplateLayoutTest {
     void test_null_eventDelimiter() {
 
         // Create the event template.
-        final String eventTemplate = writeJson(Map("key", "val"));
+        final String eventTemplate = writeJson(asMap("key", "val"));
 
         // Create the layout.
         final JsonTemplateLayout layout = JsonTemplateLayout
@@ -1707,8 +1707,8 @@ class JsonTemplateLayoutTest {
         final List<LogEvent> logEvents = createNastyLogEvents();
 
         // Create the event template.
-        final String eventTemplate = writeJson(Map(
-                "message", Map("$resolver", "message")));
+        final String eventTemplate = writeJson(asMap(
+                "message", asMap("$resolver", "message")));
 
         // Create the layout.
         final JsonTemplateLayout layout = JsonTemplateLayout
@@ -1882,8 +1882,8 @@ class JsonTemplateLayoutTest {
     void test_PatternResolver() {
 
         // Create the event template.
-        final String eventTemplate = writeJson(Map(
-                "message", Map(
+        final String eventTemplate = writeJson(asMap(
+                "message", asMap(
                         "$resolver", "pattern",
                         "pattern", "%p:%m")));
 
@@ -1920,26 +1920,39 @@ class JsonTemplateLayoutTest {
     }
 
     @Test
+    void test_MessageParameterResolver_noParameters_with_ParameterizedMessageFactory() {
+        testMessageParameterResolverNoParameters(ParameterizedMessageFactory.INSTANCE);
+    }
+
+    @Test
     void test_MessageParameterResolver_with_ReusableMessageFactory() {
         testMessageParameterResolver(ReusableMessageFactory.INSTANCE);
     }
 
-    private static void testMessageParameterResolver(MessageFactory messageFactory) {
+    @Test
+    void test_MessageParameterResolver_noParameters_with_ReusableMessageFactory() {
+        testMessageParameterResolverNoParameters(ReusableMessageFactory.INSTANCE);
+    }
+
+    private static void testMessageParameterResolver(final MessageFactory messageFactory) {
 
         // Create the event template.
-        final String eventTemplate = writeJson(Map(
-                "po*", Map(
+        final String eventTemplate = writeJson(asMap(
+                "po*", asMap(
                         "$resolver", "messageParameter"),
-                "ps*", Map(
+                "ps*", asMap(
                         "$resolver", "messageParameter",
                         "stringified", true),
-                "po2", Map(
+                "po2", asMap(
                         "$resolver", "messageParameter",
                         "index", 2),
-                "ps2", Map(
+                "ps2", asMap(
                         "$resolver", "messageParameter",
                         "index", 2,
-                        "stringified", true)));
+                        "stringified", true),
+                "po3", asMap(
+                        "$resolver", "messageParameter",
+                        "index", 3)));
 
         // Create the layout.
         final JsonTemplateLayout layout = JsonTemplateLayout
@@ -1969,6 +1982,43 @@ class JsonTemplateLayoutTest {
             assertThat(accessor.getObject("ps*")).isEqualTo(stringifiedParameters);
             assertThat(accessor.getObject("po2")).isEqualTo(parameters[2]);
             assertThat(accessor.getString("ps2")).isEqualTo(stringifiedParameters.get(2));
+            assertThat(accessor.getString("ps3")).isNull();
+        });
+
+    }
+
+    private static void testMessageParameterResolverNoParameters(
+            final MessageFactory messageFactory) {
+
+        // Create the event template.
+        final String eventTemplate = writeJson(asMap(
+                "po*", asMap(
+                        "$resolver", "messageParameter"),
+                "ps*", asMap(
+                        "$resolver", "messageParameter",
+                        "stringified", true)));
+
+        // Create the layout.
+        final JsonTemplateLayout layout = JsonTemplateLayout
+                .newBuilder()
+                .setConfiguration(CONFIGURATION)
+                .setEventTemplate(eventTemplate)
+                .build();
+
+        // Create the log event.
+        final Message message = messageFactory.newMessage("foo", new Object[0]);
+        final Level level = Level.FATAL;
+        final LogEvent logEvent = Log4jLogEvent
+                .newBuilder()
+                .setLoggerName(LOGGER_NAME)
+                .setMessage(message)
+                .setLevel(level)
+                .build();
+
+        // Check the serialized event.
+        usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+            assertThat(accessor.getObject("po*")).isEqualTo(Collections.emptyList());
+            assertThat(accessor.getObject("ps*")).isEqualTo(Collections.emptyList());
         });
 
     }
@@ -1977,26 +2027,26 @@ class JsonTemplateLayoutTest {
     void test_unresolvable_nested_fields_are_skipped() {
 
         // Create the event template.
-        final String eventTemplate = writeJson(Map(
-                "exception", Map(
-                        "message", Map(
+        final String eventTemplate = writeJson(asMap(
+                "exception", asMap(
+                        "message", asMap(
                                 "$resolver", "exception",
                                 "field", "message"),
-                        "className", Map(
+                        "className", asMap(
                                 "$resolver", "exception",
                                 "field", "className")),
-                "exceptionRootCause", Map(
-                        "message", Map(
+                "exceptionRootCause", asMap(
+                        "message", asMap(
                                 "$resolver", "exceptionRootCause",
                                 "field", "message"),
-                        "className", Map(
+                        "className", asMap(
                                 "$resolver", "exceptionRootCause",
                                 "field", "className")),
-                "source", Map(
-                        "lineNumber", Map(
+                "source", asMap(
+                        "lineNumber", asMap(
                                 "$resolver", "source",
                                 "field", "lineNumber"),
-                        "fileName", Map(
+                        "fileName", asMap(
                                 "$resolver", "source",
                                 "field", "fileName")),
                 "emptyMap", Collections.emptyMap(),
@@ -2061,7 +2111,7 @@ class JsonTemplateLayoutTest {
         return JsonReader.read(json);
     }
 
-    private static Map<String, Object> Map(final Object... pairs) {
+    private static Map<String, Object> asMap(final Object... pairs) {
         final Map<String, Object> map = new LinkedHashMap<>();
         if (pairs.length % 2 != 0) {
             throw new IllegalArgumentException("odd number of arguments");
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 7099b53..40a192a 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -31,6 +31,9 @@
          - "remove" - Removed
     -->
     <release version="3.0.0" date="2019-xx-xx" description="GA Release 3.0.0">
+      <action issue="LOG4J2-2967" dev="ckozak" type="fix">
+        Fix JsonTemplateLayout index based parameter resolution when messages contain too few parameters.
+      </action>
       <action issue="LOG4J2-2962" dev="vy" type="add">
         Enrich "map" resolver by unifying its backend with "mdc" resolver.
       </action>