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/13 14:53:53 UTC

[logging-log4j2] branch release-2.x updated: LOG4J2-2961 Fix reading of JsonTemplateLayout event additional fields from config.

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

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


The following commit(s) were added to refs/heads/release-2.x by this push:
     new 06101a9  LOG4J2-2961 Fix reading of JsonTemplateLayout event additional fields from config.
06101a9 is described below

commit 06101a978a993a356da6dc413863734f72874948
Author: Volkan Yazici <vo...@gmail.com>
AuthorDate: Fri Nov 13 15:47:03 2020 +0100

    LOG4J2-2961 Fix reading of JsonTemplateLayout event additional fields from config.
---
 .../layout/template/json/JsonTemplateLayout.java   | 92 +++-------------------
 .../log4j/layout/template/json/EcsLayoutTest.java  | 29 +++----
 .../log4j/layout/template/json/GelfLayoutTest.java | 19 ++---
 .../JsonTemplateLayoutAdditionalFieldTest.java     | 73 +++++++++++++++++
 .../template/json/JsonTemplateLayoutTest.java      |  7 +-
 .../log4j/layout/template/json/LogstashIT.java     | 50 +++++-------
 ...onalFieldEnrichedJsonTemplateLayoutLogging.xml} | 35 ++++----
 .../resources/gcFreeJsonTemplateLayoutLogging.xml  | 39 +++++----
 ...nullEventDelimitedJsonTemplateLayoutLogging.xml | 39 +++++----
 .../json/JsonTemplateLayoutBenchmarkState.java     | 45 +++++------
 .../src/main/config-repo/log4j2.xml                | 34 ++++----
 src/changes/changes.xml                            |  3 +
 .../asciidoc/manual/json-template-layout.adoc.vm   |  9 +--
 src/site/markdown/manual/cloud.md                  | 75 +++++++++---------
 14 files changed, 257 insertions(+), 292 deletions(-)

diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java
index 17b9a20..33c73ef 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java
@@ -43,7 +43,6 @@ import org.apache.logging.log4j.layout.template.json.util.Uris;
 import org.apache.logging.log4j.util.Strings;
 
 import java.nio.charset.Charset;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.Map;
 import java.util.Objects;
@@ -140,6 +139,10 @@ public class JsonTemplateLayout implements StringLayout {
         final int maxStringByteCount =
                 Math.toIntExact(Math.round(
                         maxByteCountPerChar * builder.maxStringLength));
+        final EventTemplateAdditionalField[] eventTemplateAdditionalFields =
+                builder.eventTemplateAdditionalFields != null
+                        ? builder.eventTemplateAdditionalFields
+                        : new EventTemplateAdditionalField[0];
         final EventResolverContext resolverContext = EventResolverContext
                 .newBuilder()
                 .setConfiguration(configuration)
@@ -151,7 +154,7 @@ public class JsonTemplateLayout implements StringLayout {
                 .setLocationInfoEnabled(builder.locationInfoEnabled)
                 .setStackTraceEnabled(builder.stackTraceEnabled)
                 .setStackTraceElementObjectResolver(stackTraceElementObjectResolver)
-                .setEventTemplateAdditionalFields(builder.eventTemplateAdditionalFields.additionalFields)
+                .setEventTemplateAdditionalFields(eventTemplateAdditionalFields)
                 .build();
         return TemplateResolvers.ofTemplate(resolverContext, eventTemplate);
     }
@@ -316,9 +319,8 @@ public class JsonTemplateLayout implements StringLayout {
         private String eventTemplateUri =
                 JsonTemplateLayoutDefaults.getEventTemplateUri();
 
-        @PluginElement("EventTemplateAdditionalFields")
-        private EventTemplateAdditionalFields eventTemplateAdditionalFields
-                = EventTemplateAdditionalFields.EMPTY;
+        @PluginElement("EventTemplateAdditionalField")
+        private EventTemplateAdditionalField[] eventTemplateAdditionalFields;
 
         @PluginBuilderAttribute
         private String stackTraceElementTemplate =
@@ -404,12 +406,12 @@ public class JsonTemplateLayout implements StringLayout {
             return this;
         }
 
-        public EventTemplateAdditionalFields getEventTemplateAdditionalFields() {
+        public EventTemplateAdditionalField[] getEventTemplateAdditionalFields() {
             return eventTemplateAdditionalFields;
         }
 
         public Builder setEventTemplateAdditionalFields(
-                final EventTemplateAdditionalFields eventTemplateAdditionalFields) {
+                final EventTemplateAdditionalField[] eventTemplateAdditionalFields) {
             this.eventTemplateAdditionalFields = eventTemplateAdditionalFields;
             return this;
         }
@@ -492,7 +494,6 @@ public class JsonTemplateLayout implements StringLayout {
                     throw new IllegalArgumentException(
                             "both eventTemplate and eventTemplateUri are blank");
             }
-            Objects.requireNonNull(eventTemplateAdditionalFields, "eventTemplateAdditionalFields");
             if (stackTraceEnabled &&
                     Strings.isBlank(stackTraceElementTemplate)
                     && Strings.isBlank(stackTraceElementTemplateUri)) {
@@ -510,78 +511,6 @@ public class JsonTemplateLayout implements StringLayout {
 
     }
 
-    // We need this ugly model and its builder just to be able to allow
-    // key-value pairs in a dedicated element.
-    @SuppressWarnings({"unused", "WeakerAccess"})
-    @Plugin(name = "EventTemplateAdditionalFields",
-            category = Node.CATEGORY,
-            printObject = true)
-    public static final class EventTemplateAdditionalFields {
-
-        private static final EventTemplateAdditionalFields EMPTY = newBuilder().build();
-
-        private final EventTemplateAdditionalField[] additionalFields;
-
-        private EventTemplateAdditionalFields(final Builder builder) {
-            this.additionalFields = builder.additionalFields != null
-                    ? builder.additionalFields
-                    : new EventTemplateAdditionalField[0];
-        }
-
-        public EventTemplateAdditionalField[] getAdditionalFields() {
-            return additionalFields;
-        }
-
-        @Override
-        public boolean equals(Object object) {
-            if (this == object) return true;
-            if (object == null || getClass() != object.getClass()) return false;
-            EventTemplateAdditionalFields that = (EventTemplateAdditionalFields) object;
-            return Arrays.equals(additionalFields, that.additionalFields);
-        }
-
-        @Override
-        public int hashCode() {
-            return Arrays.hashCode(additionalFields);
-        }
-
-        @Override
-        public String toString() {
-            return Arrays.toString(additionalFields);
-        }
-
-        @PluginBuilderFactory
-        public static Builder newBuilder() {
-            return new Builder();
-        }
-
-        public static class Builder
-                implements org.apache.logging.log4j.core.util.Builder<EventTemplateAdditionalFields> {
-
-            @PluginElement("AdditionalField")
-            private EventTemplateAdditionalField[] additionalFields;
-
-            private Builder() {}
-
-            public EventTemplateAdditionalField[] getAdditionalFields() {
-                return additionalFields;
-            }
-
-            public Builder setAdditionalFields(
-                    final EventTemplateAdditionalField[] additionalFields) {
-                this.additionalFields = additionalFields;
-                return this;
-            }
-
-            @Override
-            public EventTemplateAdditionalFields build() {
-                return new EventTemplateAdditionalFields(this);
-            }
-
-        }
-
-    }
-
     @Plugin(name = "EventTemplateAdditionalField",
             category = Node.CATEGORY,
             printObject = true)
@@ -644,10 +573,13 @@ public class JsonTemplateLayout implements StringLayout {
         public static class Builder
                 implements org.apache.logging.log4j.core.util.Builder<EventTemplateAdditionalField> {
 
+            @PluginBuilderAttribute
             private String key;
 
+            @PluginBuilderAttribute
             private String value;
 
+            @PluginBuilderAttribute
             private Type type = Type.STRING;
 
             public Builder setKey(final String key) {
diff --git a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/EcsLayoutTest.java b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/EcsLayoutTest.java
index 4449041..4be0c7f 100644
--- a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/EcsLayoutTest.java
+++ b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/EcsLayoutTest.java
@@ -43,23 +43,18 @@ public class EcsLayoutTest {
             .setConfiguration(CONFIGURATION)
             .setEventTemplateUri("classpath:EcsLayout.json")
             .setEventTemplateAdditionalFields(
-                    JsonTemplateLayout
-                            .EventTemplateAdditionalFields
-                            .newBuilder()
-                            .setAdditionalFields(
-                                    new EventTemplateAdditionalField[]{
-                                            EventTemplateAdditionalField
-                                                    .newBuilder()
-                                                    .setKey("service.name")
-                                                    .setValue(SERVICE_NAME)
-                                                    .build(),
-                                            EventTemplateAdditionalField
-                                                    .newBuilder()
-                                                    .setKey("event.dataset")
-                                                    .setValue(EVENT_DATASET)
-                                                    .build()
-                                    })
-                            .build())
+                    new EventTemplateAdditionalField[]{
+                            EventTemplateAdditionalField
+                                    .newBuilder()
+                                    .setKey("service.name")
+                                    .setValue(SERVICE_NAME)
+                                    .build(),
+                            EventTemplateAdditionalField
+                                    .newBuilder()
+                                    .setKey("event.dataset")
+                                    .setValue(EVENT_DATASET)
+                                    .build()
+                    })
             .build();
 
     private static final EcsLayout ECS_LAYOUT = EcsLayout
diff --git a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/GelfLayoutTest.java b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/GelfLayoutTest.java
index fed0205..9d32cf3 100644
--- a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/GelfLayoutTest.java
+++ b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/GelfLayoutTest.java
@@ -43,18 +43,13 @@ public class GelfLayoutTest {
             .setConfiguration(CONFIGURATION)
             .setEventTemplateUri("classpath:GelfLayout.json")
             .setEventTemplateAdditionalFields(
-                    JsonTemplateLayout
-                            .EventTemplateAdditionalFields
-                            .newBuilder()
-                            .setAdditionalFields(
-                                    new EventTemplateAdditionalField[]{
-                                            EventTemplateAdditionalField
-                                                    .newBuilder()
-                                                    .setKey("host")
-                                                    .setValue(HOST_NAME)
-                                                    .build()
-                                    })
-                            .build())
+                    new EventTemplateAdditionalField[]{
+                            EventTemplateAdditionalField
+                                    .newBuilder()
+                                    .setKey("host")
+                                    .setValue(HOST_NAME)
+                                    .build()
+                    })
             .build();
 
     private static final GelfLayout GELF_LAYOUT = GelfLayout
diff --git a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutAdditionalFieldTest.java b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutAdditionalFieldTest.java
new file mode 100644
index 0000000..465bcc6
--- /dev/null
+++ b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutAdditionalFieldTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.Logger;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.junit.LoggerContextSource;
+import org.apache.logging.log4j.junit.Named;
+import org.apache.logging.log4j.layout.template.json.util.JsonReader;
+import org.apache.logging.log4j.test.appender.ListAppender;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+@LoggerContextSource("additionalFieldEnrichedJsonTemplateLayoutLogging.xml")
+class JsonTemplateLayoutAdditionalFieldTest {
+
+    @Test
+    void test_additional_fields_are_resolved(
+            final LoggerContext loggerContext,
+            @Named("List") final ListAppender appender) {
+
+        // Log an event.
+        final Logger logger =
+                loggerContext.getLogger(
+                        JsonTemplateLayoutAdditionalFieldTest.class);
+        logger.info("trigger");
+
+        // Verify that the appender has logged the event.
+        final List<byte[]> serializedEvents = appender.getData();
+        Assertions.assertThat(serializedEvents).hasSize(1);
+
+        // Deserialize the serialized event.
+        final byte[] serializedEvent = serializedEvents.get(0);
+        final String serializedEventJson =
+                new String(
+                        serializedEvent,
+                        JsonTemplateLayoutDefaults.getCharset());
+        final Object serializedEventObject = JsonReader.read(serializedEventJson);
+        Assertions.assertThat(serializedEventObject).isInstanceOf(Map.class);
+        @SuppressWarnings("unchecked")
+        final Map<String, Object> serializedEventMap =
+                (Map<String, Object>) serializedEventObject;
+
+        // Verify the serialized additional fields.
+        Assertions
+                .assertThat(serializedEventMap)
+                .containsEntry("stringField", "string")
+                .containsEntry("numberField", 1)
+                .containsEntry("objectField", Collections.singletonMap("numberField", 1))
+                .containsEntry("listField", Arrays.asList(1, "two"));
+
+    }
+
+}
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 9d2c3d9..ab76eb0 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
@@ -34,7 +34,6 @@ import org.apache.logging.log4j.core.lookup.MainMapLookup;
 import org.apache.logging.log4j.core.net.Severity;
 import org.apache.logging.log4j.core.time.MutableInstant;
 import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout.EventTemplateAdditionalField;
-import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout.EventTemplateAdditionalFields;
 import org.apache.logging.log4j.layout.template.json.util.JsonReader;
 import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
 import org.apache.logging.log4j.layout.template.json.util.MapAccessor;
@@ -1221,7 +1220,7 @@ public class JsonTemplateLayoutTest {
         final String eventTemplate = "{}";
 
         // Create the layout.
-        final EventTemplateAdditionalField[] additionalFieldPairs = {
+        final EventTemplateAdditionalField[] additionalFields = {
                 EventTemplateAdditionalField
                         .newBuilder()
                         .setKey("number")
@@ -1240,10 +1239,6 @@ public class JsonTemplateLayoutTest {
                         .setType(EventTemplateAdditionalField.Type.JSON)
                         .build()
         };
-        final EventTemplateAdditionalFields additionalFields = EventTemplateAdditionalFields
-                .newBuilder()
-                .setAdditionalFields(additionalFieldPairs)
-                .build();
         final JsonTemplateLayout layout = JsonTemplateLayout
                 .newBuilder()
                 .setConfiguration(CONFIGURATION)
diff --git a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/LogstashIT.java b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/LogstashIT.java
index 0ec0aa3..3bf6241 100644
--- a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/LogstashIT.java
+++ b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/LogstashIT.java
@@ -96,18 +96,14 @@ public class LogstashIT {
             .setCharset(CHARSET)
             .setEventTemplateUri("classpath:GelfLayout.json")
             .setEventDelimiter("\0")
-            .setEventTemplateAdditionalFields(JsonTemplateLayout
-                    .EventTemplateAdditionalFields
-                    .newBuilder()
-                    .setAdditionalFields(
-                            new EventTemplateAdditionalField[]{
-                                    EventTemplateAdditionalField
-                                            .newBuilder()
-                                            .setKey("host")
-                                            .setValue(HOST_NAME)
-                                            .build()
-                            })
-                    .build())
+            .setEventTemplateAdditionalFields(
+                    new EventTemplateAdditionalField[]{
+                            EventTemplateAdditionalField
+                                    .newBuilder()
+                                    .setKey("host")
+                                    .setValue(HOST_NAME)
+                                    .build()
+                    })
             .build();
 
     private static final EcsLayout ECS_LAYOUT = EcsLayout
@@ -124,23 +120,19 @@ public class LogstashIT {
             .setCharset(CHARSET)
             .setEventTemplateUri("classpath:EcsLayout.json")
             .setRecyclerFactory(ThreadLocalRecyclerFactory.getInstance())
-            .setEventTemplateAdditionalFields(JsonTemplateLayout
-                    .EventTemplateAdditionalFields
-                    .newBuilder()
-                    .setAdditionalFields(
-                            new EventTemplateAdditionalField[]{
-                                    EventTemplateAdditionalField
-                                            .newBuilder()
-                                            .setKey("service.name")
-                                            .setValue(SERVICE_NAME)
-                                            .build(),
-                                    EventTemplateAdditionalField
-                                            .newBuilder()
-                                            .setKey("event.dataset")
-                                            .setValue(EVENT_DATASET)
-                                            .build()
-                            })
-                    .build())
+            .setEventTemplateAdditionalFields(
+                    new EventTemplateAdditionalField[]{
+                            EventTemplateAdditionalField
+                                    .newBuilder()
+                                    .setKey("service.name")
+                                    .setValue(SERVICE_NAME)
+                                    .build(),
+                            EventTemplateAdditionalField
+                                    .newBuilder()
+                                    .setKey("event.dataset")
+                                    .setValue(EVENT_DATASET)
+                                    .build()
+                    })
             .build();
 
     private static final int LOG_EVENT_COUNT = 100;
diff --git a/log4j-layout-template-json/src/test/resources/nullEventDelimitedJsonTemplateLayoutLogging.xml b/log4j-layout-template-json/src/test/resources/additionalFieldEnrichedJsonTemplateLayoutLogging.xml
similarity index 57%
copy from log4j-layout-template-json/src/test/resources/nullEventDelimitedJsonTemplateLayoutLogging.xml
copy to log4j-layout-template-json/src/test/resources/additionalFieldEnrichedJsonTemplateLayoutLogging.xml
index 39d87a9..3449c6d 100644
--- a/log4j-layout-template-json/src/test/resources/nullEventDelimitedJsonTemplateLayoutLogging.xml
+++ b/log4j-layout-template-json/src/test/resources/additionalFieldEnrichedJsonTemplateLayoutLogging.xml
@@ -14,26 +14,21 @@
  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.
-
 -->
 <Configuration status="OFF">
-    <Appenders>
-        <Socket name="Socket"
-                host="localhost"
-                port="50514"
-                protocol="TCP"
-                ignoreExceptions="false"
-                reconnectionDelay="100"
-                immediateFlush="true">
-            <JsonTemplateLayout eventTemplate='{"$resolver": "message"}'
-                                eventDelimiter=""
-                                nullEventDelimiterEnabled="true"
-                                charset="US-ASCII"/>
-        </Socket>
-    </Appenders>
-    <Loggers>
-        <Root level="TRACE">
-            <AppenderRef ref="Socket"/>
-        </Root>
-    </Loggers>
+  <Appenders>
+    <List name="List" raw="true">
+      <JsonTemplateLayout>
+        <EventTemplateAdditionalField key="stringField" value="string"/>
+        <EventTemplateAdditionalField key="numberField" value="1" type="JSON"/>
+        <EventTemplateAdditionalField key="objectField" value='{"numberField":1}' type="JSON"/>
+        <EventTemplateAdditionalField key="listField" value='[1, "two"]' type="JSON"/>
+      </JsonTemplateLayout>
+    </List>
+  </Appenders>
+  <Loggers>
+    <Root level="TRACE">
+      <AppenderRef ref="List"/>
+    </Root>
+  </Loggers>
 </Configuration>
diff --git a/log4j-layout-template-json/src/test/resources/gcFreeJsonTemplateLayoutLogging.xml b/log4j-layout-template-json/src/test/resources/gcFreeJsonTemplateLayoutLogging.xml
index 245ff06..2a2bc82 100644
--- a/log4j-layout-template-json/src/test/resources/gcFreeJsonTemplateLayoutLogging.xml
+++ b/log4j-layout-template-json/src/test/resources/gcFreeJsonTemplateLayoutLogging.xml
@@ -14,26 +14,25 @@
  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.
-
 -->
 <Configuration status="OFF">
-    <Appenders>
-        <Console name="Console" target="SYSTEM_OUT">
-            <PatternLayout pattern="%p %c{1.} [%t] %X{aKey} %X %m%ex%n" />
-        </Console>
-        <File name="File"
-              fileName="target/gcFreeJsonTemplateLayoutLogging.log"
-              bufferedIO="false"
-              append="false">
-            <JsonTemplateLayout recyclerFactory="threadLocal"/>
-        </File>
-    </Appenders>
-    <Loggers>
-        <Root level="trace" includeLocation="false">
-            <Property name="prop1">value1</Property>
-            <Property name="prop2">value2</Property>
-            <appender-ref ref="Console" level="FATAL"/>
-            <appender-ref ref="File"/>
-        </Root>
-    </Loggers>
+  <Appenders>
+    <Console name="Console" target="SYSTEM_OUT">
+      <PatternLayout pattern="%p %c{1.} [%t] %X{aKey} %X %m%ex%n"/>
+    </Console>
+    <File name="File"
+          fileName="target/gcFreeJsonTemplateLayoutLogging.log"
+          bufferedIO="false"
+          append="false">
+      <JsonTemplateLayout recyclerFactory="threadLocal"/>
+    </File>
+  </Appenders>
+  <Loggers>
+    <Root level="trace" includeLocation="false">
+      <Property name="prop1">value1</Property>
+      <Property name="prop2">value2</Property>
+      <appender-ref ref="Console" level="FATAL"/>
+      <appender-ref ref="File"/>
+    </Root>
+  </Loggers>
 </Configuration>
diff --git a/log4j-layout-template-json/src/test/resources/nullEventDelimitedJsonTemplateLayoutLogging.xml b/log4j-layout-template-json/src/test/resources/nullEventDelimitedJsonTemplateLayoutLogging.xml
index 39d87a9..547aac8 100644
--- a/log4j-layout-template-json/src/test/resources/nullEventDelimitedJsonTemplateLayoutLogging.xml
+++ b/log4j-layout-template-json/src/test/resources/nullEventDelimitedJsonTemplateLayoutLogging.xml
@@ -14,26 +14,25 @@
  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.
-
 -->
 <Configuration status="OFF">
-    <Appenders>
-        <Socket name="Socket"
-                host="localhost"
-                port="50514"
-                protocol="TCP"
-                ignoreExceptions="false"
-                reconnectionDelay="100"
-                immediateFlush="true">
-            <JsonTemplateLayout eventTemplate='{"$resolver": "message"}'
-                                eventDelimiter=""
-                                nullEventDelimiterEnabled="true"
-                                charset="US-ASCII"/>
-        </Socket>
-    </Appenders>
-    <Loggers>
-        <Root level="TRACE">
-            <AppenderRef ref="Socket"/>
-        </Root>
-    </Loggers>
+  <Appenders>
+    <Socket name="Socket"
+            host="localhost"
+            port="50514"
+            protocol="TCP"
+            ignoreExceptions="false"
+            reconnectionDelay="100"
+            immediateFlush="true">
+      <JsonTemplateLayout eventTemplate='{"$resolver": "message"}'
+                          eventDelimiter=""
+                          nullEventDelimiterEnabled="true"
+                          charset="US-ASCII"/>
+    </Socket>
+  </Appenders>
+  <Loggers>
+    <Root level="TRACE">
+      <AppenderRef ref="Socket"/>
+    </Root>
+  </Loggers>
 </Configuration>
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmarkState.java b/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmarkState.java
index 7b59e08..90dd89d 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmarkState.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmarkState.java
@@ -26,7 +26,6 @@ import org.apache.logging.log4j.core.layout.JsonLayout;
 import org.apache.logging.log4j.core.util.KeyValuePair;
 import org.apache.logging.log4j.core.util.NetUtils;
 import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout.EventTemplateAdditionalField;
-import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout.EventTemplateAdditionalFields;
 import org.apache.logging.log4j.layout.template.json.util.ThreadLocalRecyclerFactory;
 import org.openjdk.jmh.annotations.Scope;
 import org.openjdk.jmh.annotations.State;
@@ -87,17 +86,14 @@ public class JsonTemplateLayoutBenchmarkState {
     }
 
     private static JsonTemplateLayout createJsonTemplateLayout4EcsLayout() {
-        final EventTemplateAdditionalFields additionalFields = EventTemplateAdditionalFields
-                .newBuilder()
-                .setAdditionalFields(
-                        new EventTemplateAdditionalField[]{
-                                EventTemplateAdditionalField
-                                        .newBuilder()
-                                        .setKey("service.name")
-                                        .setValue("benchmark")
-                                        .build()
-                        })
-                .build();
+        final EventTemplateAdditionalField[] additionalFields =
+                new EventTemplateAdditionalField[]{
+                        EventTemplateAdditionalField
+                                .newBuilder()
+                                .setKey("service.name")
+                                .setValue("benchmark")
+                                .build()
+                };
         return JsonTemplateLayout
                 .newBuilder()
                 .setConfiguration(CONFIGURATION)
@@ -115,20 +111,17 @@ public class JsonTemplateLayoutBenchmarkState {
                 .setCharset(CHARSET)
                 .setEventTemplateUri("classpath:GelfLayout.json")
                 .setRecyclerFactory(ThreadLocalRecyclerFactory.getInstance())
-                .setEventTemplateAdditionalFields(EventTemplateAdditionalFields
-                        .newBuilder()
-                        .setAdditionalFields(
-                                new EventTemplateAdditionalField[]{
-                                        // Adding "host" as a constant rather than using
-                                        // the "hostName" property lookup at runtime, which
-                                        // is what GelfLayout does as well.
-                                        EventTemplateAdditionalField
-                                                .newBuilder()
-                                                .setKey("host")
-                                                .setValue(NetUtils.getLocalHostname())
-                                                .build()
-                                })
-                        .build())
+                .setEventTemplateAdditionalFields(
+                        new EventTemplateAdditionalField[]{
+                                // Adding "host" as a constant rather than using
+                                // the "hostName" property lookup at runtime, which
+                                // is what GelfLayout does as well.
+                                EventTemplateAdditionalField
+                                        .newBuilder()
+                                        .setKey("host")
+                                        .setValue(NetUtils.getLocalHostname())
+                                        .build()
+                        })
                 .build();
     }
 
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/main/config-repo/log4j2.xml b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/main/config-repo/log4j2.xml
index 8921f4f..e88234f 100644
--- a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/main/config-repo/log4j2.xml
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/main/config-repo/log4j2.xml
@@ -107,24 +107,22 @@
             protocol="tcp"
             bufferedIo="true">
       <JsonTemplateLayout eventTemplateUri="classpath:GelfLayout.json" nullEventDelimiterEnabled="true">
-        <EventTemplateAdditionalFields>
-          <EventTemplateAdditionalField key="containerId" value="\${docker:containerId:-}"/>
-          <EventTemplateAdditionalField key="application" value="\${lower:${spring:spring.application.name:-spring}}"/>
-          <EventTemplateAdditionalField key="kubernetes.serviceAccountName" value="\${k8s:accountName:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.containerId" value="\${k8s:containerId:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.containerName" value="\${k8s:containerName:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.host" value="\${k8s:host:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.labels.app" value="\${k8s:labels.app:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.labels.pod-template-hash" value="\${k8s:labels.podTemplateHash:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.master_url" value="\${k8s:masterUrl:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.namespaceId" value="\${k8s:namespaceId:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.namespaceName" value="\${k8s:namespaceName:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.podID" value="\${k8s:podId:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.podIP" value="\${k8s:podIp:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.podName" value="\${k8s:podName:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.imageId" value="\${k8s:imageId:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.imageName" value="\${k8s:imageName:-}"/>
-        </EventTemplateAdditionalFields>
+        <EventTemplateAdditionalField key="containerId" value="\${docker:containerId:-}"/>
+        <EventTemplateAdditionalField key="application" value="\${lower:${spring:spring.application.name:-spring}}"/>
+        <EventTemplateAdditionalField key="kubernetes.serviceAccountName" value="\${k8s:accountName:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.containerId" value="\${k8s:containerId:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.containerName" value="\${k8s:containerName:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.host" value="\${k8s:host:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.labels.app" value="\${k8s:labels.app:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.labels.pod-template-hash" value="\${k8s:labels.podTemplateHash:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.master_url" value="\${k8s:masterUrl:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.namespaceId" value="\${k8s:namespaceId:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.namespaceName" value="\${k8s:namespaceName:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.podID" value="\${k8s:podId:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.podIP" value="\${k8s:podIp:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.podName" value="\${k8s:podName:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.imageId" value="\${k8s:imageId:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.imageName" value="\${k8s:imageName:-}"/>
       </JsonTemplateLayout>
     </Socket>
     <Console name="Console" target="SYSTEM_OUT">
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 700e1f5..1a0ea85 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -30,6 +30,9 @@
          - "remove" - Removed
     -->
     <release version="2.14.1" date="2020-MM-DD" description="GA Release 2.14.1">
+      <action issue="LOG4J2-2961" dev="vy" type="fix">
+        Fix reading of JsonTemplateLayout event additional fields from config.
+      </action>
       <action issue="LOG4J2-2916" dev="vy" type="fix" due-to="wuqian0808">
         Avoid redundant Kafka producer instantiation causing thread leaks.
       </action>
diff --git a/src/site/asciidoc/manual/json-template-layout.adoc.vm b/src/site/asciidoc/manual/json-template-layout.adoc.vm
index d90f607..2536e9b 100644
--- a/src/site/asciidoc/manual/json-template-layout.adoc.vm
+++ b/src/site/asciidoc/manual/json-template-layout.adoc.vm
@@ -242,14 +242,13 @@ custom fields:
 [source,xml]
 ----
 <JsonTemplateLayout eventTemplateUri="classpath:GelfLayout.json">
-    <EventTemplateAdditionalFields>
-        <EventTemplateAdditionalField key="host" value="www.apache.org"/>
-        <EventTemplateAdditionalField key="_serviceName" value="auth-service"/>
-        <EventTemplateAdditionalField key="_containerId" value="6ede3f0ca7d9"/>
-    </EventTemplateAdditionalFields>
+  <EventTemplateAdditionalField key="host" value="www.apache.org"/>
+  <EventTemplateAdditionalField key="_serviceName" value="auth-service"/>
+  <EventTemplateAdditionalField key="_containerId" value="6ede3f0ca7d9"/>
 </JsonTemplateLayout>
 ----
 
+The default `type` for the added new fields are `String`.
 One can also pass JSON literals into additional fields:
 
 [source,xml]
diff --git a/src/site/markdown/manual/cloud.md b/src/site/markdown/manual/cloud.md
index 84fb63f..07692c9 100644
--- a/src/site/markdown/manual/cloud.md
+++ b/src/site/markdown/manual/cloud.md
@@ -140,28 +140,26 @@ will be formatted without newlines.
             port="12345"
             protocol="tcp"
             bufferedIo="true">
-        <JsonTemplateLayout eventTemplateUri="classpath:EcsLayout.json">
-            <EventTemplateAdditionalFields>
-                <EventTemplateAdditionalField key="containerId" value="${docker:containerId:-}"/>
-                <EventTemplateAdditionalField key="application" value="${lower:${spring:spring.application.name:-spring}}"/>
-                <EventTemplateAdditionalField key="kubernetes.serviceAccountName" value="${k8s:accountName:-}"/>
-                <EventTemplateAdditionalField key="kubernetes.containerId" value="${k8s:containerId:-}"/>
-                <EventTemplateAdditionalField key="kubernetes.containerName" value="${k8s:containerName:-}"/>
-                <EventTemplateAdditionalField key="kubernetes.host" value="${k8s:host:-}"/>
-                <EventTemplateAdditionalField key="kubernetes.labels.app" value="${k8s:labels.app:-}"/>
-                <EventTemplateAdditionalField key="kubernetes.labels.pod-template-hash" value="${k8s:labels.podTemplateHash:-}"/>
-                <EventTemplateAdditionalField key="kubernetes.master_url" value="${k8s:masterUrl:-}"/>
-                <EventTemplateAdditionalField key="kubernetes.namespaceId" value="${k8s:namespaceId:-}"/>
-                <EventTemplateAdditionalField key="kubernetes.namespaceName" value="${k8s:namespaceName:-}"/>
-                <EventTemplateAdditionalField key="kubernetes.podID" value="${k8s:podId:-}"/>
-                <EventTemplateAdditionalField key="kubernetes.podIP" value="${k8s:podIp:-}"/>
-                <EventTemplateAdditionalField key="kubernetes.podName" value="${k8s:podName:-}"/>
-                <EventTemplateAdditionalField key="kubernetes.imageId" value="${k8s:imageId:-}"/>
-                <EventTemplateAdditionalField key="kubernetes.imageName" value="${k8s:imageName:-}"/>
-            </EventTemplateAdditionalFields>
-        </JsonTemplateLayout>
+      <JsonTemplateLayout eventTemplateUri="classpath:EcsLayout.json">
+        <EventTemplateAdditionalField key="containerId" value="${docker:containerId:-}"/>
+        <EventTemplateAdditionalField key="application" value="${lower:${spring:spring.application.name:-spring}}"/>
+        <EventTemplateAdditionalField key="kubernetes.serviceAccountName" value="${k8s:accountName:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.containerId" value="${k8s:containerId:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.containerName" value="${k8s:containerName:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.host" value="${k8s:host:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.labels.app" value="${k8s:labels.app:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.labels.pod-template-hash" value="${k8s:labels.podTemplateHash:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.master_url" value="${k8s:masterUrl:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.namespaceId" value="${k8s:namespaceId:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.namespaceName" value="${k8s:namespaceName:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.podID" value="${k8s:podId:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.podIP" value="${k8s:podIp:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.podName" value="${k8s:podName:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.imageId" value="${k8s:imageId:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.imageName" value="${k8s:imageName:-}"/>
+      </JsonTemplateLayout>
     </Socket>
-    
+
 The JsonTemplateLayout can also be used to generate JSON that matches the GELF specification which can     
 format the message attribute using a pattern in accordance with the PatternLayout. For example, the following
 template, named EnhancedGelf.json, can be used to generate GELF-compliant data that can be passed to Logstash. 
@@ -221,26 +219,25 @@ The logging configuration to use this template would be
             protocol="tcp"
             bufferedIo="true">
       <JsonTemplateLayout eventTemplateUri="classpath:EnhancedGelf.json" nullEventDelimiterEnabled="true">
-        <EventTemplateAdditionalFields>
-          <EventTemplateAdditionalField key="containerId" value="${docker:containerId:-}"/>
-          <EventTemplateAdditionalField key="application" value="${lower:${spring:spring.application.name:-spring}}"/>
-          <EventTemplateAdditionalField key="kubernetes.serviceAccountName" value="${k8s:accountName:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.containerId" value="${k8s:containerId:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.containerName" value="${k8s:containerName:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.host" value="${k8s:host:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.labels.app" value="${k8s:labels.app:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.labels.pod-template-hash" value="${k8s:labels.podTemplateHash:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.master_url" value="${k8s:masterUrl:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.namespaceId" value="${k8s:namespaceId:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.namespaceName" value="${k8s:namespaceName:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.podID" value="${k8s:podId:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.podIP" value="${k8s:podIp:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.podName" value="${k8s:podName:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.imageId" value="${k8s:imageId:-}"/>
-          <EventTemplateAdditionalField key="kubernetes.imageName" value="${k8s:imageName:-}"/>
-        </EventTemplateAdditionalFields>
+        <EventTemplateAdditionalField key="containerId" value="${docker:containerId:-}"/>
+        <EventTemplateAdditionalField key="application" value="${lower:${spring:spring.application.name:-spring}}"/>
+        <EventTemplateAdditionalField key="kubernetes.serviceAccountName" value="${k8s:accountName:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.containerId" value="${k8s:containerId:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.containerName" value="${k8s:containerName:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.host" value="${k8s:host:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.labels.app" value="${k8s:labels.app:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.labels.pod-template-hash" value="${k8s:labels.podTemplateHash:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.master_url" value="${k8s:masterUrl:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.namespaceId" value="${k8s:namespaceId:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.namespaceName" value="${k8s:namespaceName:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.podID" value="${k8s:podId:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.podIP" value="${k8s:podIp:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.podName" value="${k8s:podName:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.imageId" value="${k8s:imageId:-}"/>
+        <EventTemplateAdditionalField key="kubernetes.imageName" value="${k8s:imageName:-}"/>
       </JsonTemplateLayout>
     </Socket>
+
 The significant difference with this configuration from the first example is that it references the 
 custom template and it specifies an event delimiter of a null character ('\0');