LOG4J2-623, LOG4J2-1362: fixed format of ThreadContext map to be a "natural" 
JSON/YAML map, instead of a list of map entries where each entry has a "key" 
attribute with the key value and a "value" attribute with the value value


Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/10316c39
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/10316c39
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/10316c39

Branch: refs/heads/master
Commit: 10316c393a3912d7e0e0717c177b720aec96cd1d
Parents: 42a7154
Author: rpopma <[email protected]>
Authored: Wed May 25 01:21:45 2016 +0900
Committer: rpopma <[email protected]>
Committed: Wed May 25 01:21:45 2016 +0900

----------------------------------------------------------------------
 .../log4j/core/jackson/Initializers.java        |  20 +++
 .../log4j/core/jackson/Log4jJsonModule.java     |  10 +-
 .../core/jackson/Log4jJsonObjectMapper.java     |   9 +-
 .../log4j/core/jackson/Log4jYamlModule.java     |  10 +-
 .../core/jackson/Log4jYamlObjectMapper.java     |   9 +-
 .../log4j/core/jackson/LogEventJsonMixIn.java   | 140 +++++++++++++++++++
 .../log4j/core/layout/JacksonFactory.java       |  12 +-
 .../logging/log4j/core/layout/JsonLayout.java   |  15 +-
 .../server/JsonInputStreamLogEventBridge.java   |   6 +-
 .../log4j/core/jackson/LevelMixInJsonTest.java  |   2 +-
 .../jackson/StackTraceElementMixInTest.java     |   3 +-
 .../log4j/core/layout/JsonLayoutTest.java       |  52 ++++---
 .../log4j/core/layout/YamlLayoutTest.java       |  13 +-
 .../net/server/AbstractSocketServerTest.java    |  12 +-
 src/changes/changes.xml                         |   6 +
 src/site/xdoc/manual/layouts.xml.vm             |   7 +
 16 files changed, 279 insertions(+), 47 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/10316c39/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Initializers.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Initializers.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Initializers.java
index f6cc708..d0cbe2d 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Initializers.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Initializers.java
@@ -53,6 +53,26 @@ class Initializers {
     }
 
     /**
+     * Used to set up {@link SetupContext} from different {@link 
SimpleModule}s.
+     * Differs from SetupContextInitializer by installing {@code 
LogEventJsonMixIn} for LogEvents,
+     * not {@code LogEventMixIn}, so it handles ThreadContext serialization 
differently.
+     */
+    static class SetupContextJsonInitializer {
+
+        void setupModule(final SetupContext context) {
+            // JRE classes: we cannot edit those with Jackson annotations
+            context.setMixInAnnotations(StackTraceElement.class, 
StackTraceElementMixIn.class);
+            // Log4j API classes: we do not want to edit those with Jackson 
annotations because the API module should not depend on Jackson.
+            context.setMixInAnnotations(Marker.class, MarkerMixIn.class);
+            context.setMixInAnnotations(Level.class, LevelMixIn.class);
+            context.setMixInAnnotations(LogEvent.class, 
LogEventJsonMixIn.class); // different ThreadContext handling
+            // Log4j Core classes: we do not want to bring in Jackson at 
runtime if we do not have to.
+            context.setMixInAnnotations(ExtendedStackTraceElement.class, 
ExtendedStackTraceElementMixIn.class);
+            context.setMixInAnnotations(ThrowableProxy.class, 
ThrowableProxyMixIn.class);
+        }
+    }
+
+    /**
      * Used to set up {@link SimpleModule} from different {@link SimpleModule} 
subclasses.
      */
     static class SimpleModuleInitializer {

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/10316c39/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jJsonModule.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jJsonModule.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jJsonModule.java
index 1525fad..87f7eb6 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jJsonModule.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jJsonModule.java
@@ -30,9 +30,11 @@ import com.fasterxml.jackson.databind.module.SimpleModule;
 class Log4jJsonModule extends SimpleModule {
 
     private static final long serialVersionUID = 1L;
+    private final boolean encodeThreadContextAsList;
 
-    Log4jJsonModule() {
+    Log4jJsonModule(final boolean encodeThreadContextAsList) {
         super(Log4jJsonModule.class.getName(), new Version(2, 0, 0, null, 
null, null));
+        this.encodeThreadContextAsList = encodeThreadContextAsList;
         // MUST init here.
         // Calling this from setupModule is too late!
         //noinspection ThisEscapedInObjectConstruction
@@ -43,6 +45,10 @@ class Log4jJsonModule extends SimpleModule {
     public void setupModule(final SetupContext context) {
         // Calling super is a MUST!
         super.setupModule(context);
-        new SetupContextInitializer().setupModule(context);
+        if (encodeThreadContextAsList) {
+            new SetupContextInitializer().setupModule(context);
+        } else {
+            new 
Initializers.SetupContextJsonInitializer().setupModule(context);
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/10316c39/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jJsonObjectMapper.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jJsonObjectMapper.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jJsonObjectMapper.java
index 3e2f0da..ebb8dbd 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jJsonObjectMapper.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jJsonObjectMapper.java
@@ -33,7 +33,14 @@ public class Log4jJsonObjectMapper extends ObjectMapper {
      * Create a new instance using the {@link Log4jJsonModule}.
      */
     public Log4jJsonObjectMapper() {
-        this.registerModule(new Log4jJsonModule());
+        this(false);
+    }
+
+    /**
+     * Create a new instance using the {@link Log4jJsonModule}.
+     */
+    public Log4jJsonObjectMapper(final boolean encodeThreadContextAsList) {
+        this.registerModule(new Log4jJsonModule(encodeThreadContextAsList));
         this.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
     }
 

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/10316c39/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jYamlModule.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jYamlModule.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jYamlModule.java
index 4052320..106942f 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jYamlModule.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jYamlModule.java
@@ -30,9 +30,11 @@ import com.fasterxml.jackson.databind.module.SimpleModule;
 final class Log4jYamlModule extends SimpleModule {
 
     private static final long serialVersionUID = 1L;
+    private final boolean encodeThreadContextAsList;
 
-    Log4jYamlModule() {
+    Log4jYamlModule(final boolean encodeThreadContextAsList) {
         super(Log4jYamlModule.class.getName(), new Version(2, 0, 0, null, 
null, null));
+        this.encodeThreadContextAsList = encodeThreadContextAsList;
         // MUST init here.
         // Calling this from setupModule is too late!
         //noinspection ThisEscapedInObjectConstruction
@@ -43,6 +45,10 @@ final class Log4jYamlModule extends SimpleModule {
     public void setupModule(final SetupContext context) {
         // Calling super is a MUST!
         super.setupModule(context);
-        new SetupContextInitializer().setupModule(context);
+        if (encodeThreadContextAsList) {
+            new SetupContextInitializer().setupModule(context);
+        } else {
+            new 
Initializers.SetupContextJsonInitializer().setupModule(context);
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/10316c39/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jYamlObjectMapper.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jYamlObjectMapper.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jYamlObjectMapper.java
index 9ab787a..2e6ce1a 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jYamlObjectMapper.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jYamlObjectMapper.java
@@ -34,7 +34,14 @@ public class Log4jYamlObjectMapper extends YAMLMapper {
      * Create a new instance using the {@link Log4jYamlModule}.
      */
     public Log4jYamlObjectMapper() {
-        this.registerModule(new Log4jYamlModule());
+        this(false);
+    }
+
+    /**
+     * Create a new instance using the {@link Log4jYamlModule}.
+     */
+    public Log4jYamlObjectMapper(final boolean encodeThreadContextAsList) {
+        this.registerModule(new Log4jYamlModule(encodeThreadContextAsList));
         this.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
     }
 

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/10316c39/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/LogEventJsonMixIn.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/LogEventJsonMixIn.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/LogEventJsonMixIn.java
new file mode 100644
index 0000000..717815d
--- /dev/null
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/LogEventJsonMixIn.java
@@ -0,0 +1,140 @@
+/*
+ * 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.core.jackson;
+
+import java.util.Map;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.ThreadContext.ContextStack;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.impl.ThrowableProxy;
+import org.apache.logging.log4j.message.Message;
+
+import com.fasterxml.jackson.annotation.JsonFilter;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.annotation.JsonRootName;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.deser.std.MapDeserializer;
+import com.fasterxml.jackson.databind.ser.std.MapSerializer;
+import 
com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+
+@JsonRootName(XmlConstants.ELT_EVENT)
+@JacksonXmlRootElement(namespace = XmlConstants.XML_NAMESPACE, localName = 
XmlConstants.ELT_EVENT)
+@JsonFilter("org.apache.logging.log4j.core.impl.Log4jLogEvent")
+@JsonPropertyOrder({ "timeMillis", "threadName", "level", "loggerName", 
"marker", "message", "thrown", XmlConstants.ELT_CONTEXT_MAP,
+        JsonConstants.ELT_CONTEXT_STACK, "loggerFQCN", "Source", "endOfBatch" 
})
+abstract class LogEventJsonMixIn implements LogEvent {
+
+    private static final long serialVersionUID = 1L;
+
+    @JsonProperty(JsonConstants.ELT_CONTEXT_MAP)
+    @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = 
XmlConstants.ELT_CONTEXT_MAP)
+//    @JsonSerialize(using = MapSerializer.class)
+//    @JsonDeserialize(using = MapDeserializer.class)
+    @Override
+    public abstract Map<String, String> getContextMap();
+
+    @JsonProperty(JsonConstants.ELT_CONTEXT_STACK)
+    @JacksonXmlElementWrapper(namespace = XmlConstants.XML_NAMESPACE, 
localName = XmlConstants.ELT_CONTEXT_STACK)
+    @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = 
XmlConstants.ELT_CONTEXT_STACK_ITEM)
+    @Override
+    public abstract ContextStack getContextStack();
+
+    @JsonProperty()
+    @JacksonXmlProperty(isAttribute = true)
+    @Override
+    public abstract Level getLevel();
+
+    @JsonProperty()
+    @JacksonXmlProperty(isAttribute = true)
+    @Override
+    public abstract String getLoggerFqcn();
+
+    @JsonProperty()
+    @JacksonXmlProperty(isAttribute = true)
+    @Override
+    public abstract String getLoggerName();
+
+    @JsonProperty(JsonConstants.ELT_MARKER)
+    @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = 
XmlConstants.ELT_MARKER)
+    @Override
+    public abstract Marker getMarker();
+
+    @JsonProperty(JsonConstants.ELT_MESSAGE)
+    @JsonSerialize(using = MessageSerializer.class)
+    @JsonDeserialize(using = SimpleMessageDeserializer.class)
+    @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = 
XmlConstants.ELT_MESSAGE)
+    @Override
+    public abstract Message getMessage();
+
+    @JsonProperty(JsonConstants.ELT_SOURCE)
+    @JsonDeserialize(using = Log4jStackTraceElementDeserializer.class)
+    @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = 
XmlConstants.ELT_SOURCE)
+    @Override
+    public abstract StackTraceElement getSource();
+
+    @Override
+    @JsonProperty("threadId")
+    @JacksonXmlProperty(isAttribute = true, localName = "threadId")
+    public abstract long getThreadId();
+
+    @Override
+    @JsonProperty("thread")
+    @JacksonXmlProperty(isAttribute = true, localName = "thread")
+    public abstract String getThreadName();
+
+    @Override
+    @JsonProperty("threadPriority")
+    @JacksonXmlProperty(isAttribute = true, localName = "threadPriority")
+    public abstract int getThreadPriority();
+
+    @JsonIgnore
+    @Override
+    public abstract Throwable getThrown();
+
+    @JsonProperty(JsonConstants.ELT_THROWN)
+    @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = 
XmlConstants.ELT_THROWN)
+    @Override
+    public abstract ThrowableProxy getThrownProxy();
+
+    @JsonProperty()
+    @JacksonXmlProperty(isAttribute = true)
+    @Override
+    public abstract long getTimeMillis();
+
+    @JsonProperty()
+    @JacksonXmlProperty(isAttribute = true)
+    @Override
+    public abstract boolean isEndOfBatch();
+
+    @JsonIgnore
+    @Override
+    public abstract boolean isIncludeLocation();
+
+    @Override
+    public abstract void setEndOfBatch(boolean endOfBatch);
+
+    @Override
+    public abstract void setIncludeLocation(boolean locationRequired);
+
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/10316c39/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JacksonFactory.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JacksonFactory.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JacksonFactory.java
index ea0877f..5869435 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JacksonFactory.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JacksonFactory.java
@@ -42,6 +42,12 @@ abstract class JacksonFactory {
 
     static class JSON extends JacksonFactory {
 
+        private final boolean encodeThreadContextAsList;
+
+        public JSON(final boolean encodeThreadContextAsList) {
+            this.encodeThreadContextAsList = encodeThreadContextAsList;
+        }
+
         @Override
         protected String getPropertNameForContextMap() {
             return JsonConstants.ELT_CONTEXT_MAP;
@@ -64,7 +70,7 @@ abstract class JacksonFactory {
 
         @Override
         protected ObjectMapper newObjectMapper() {
-            return new Log4jJsonObjectMapper();
+            return new Log4jJsonObjectMapper(encodeThreadContextAsList);
         }
 
         @Override
@@ -76,7 +82,7 @@ abstract class JacksonFactory {
     static class XML extends JacksonFactory {
 
         static final int DEFAULT_INDENT = 1;
-        
+
         @Override
         protected String getPropertNameForContextMap() {
             return XmlConstants.ELT_CONTEXT_MAP;
@@ -133,7 +139,7 @@ abstract class JacksonFactory {
 
         @Override
         protected ObjectMapper newObjectMapper() {
-            return new Log4jYamlObjectMapper();
+            return new Log4jYamlObjectMapper(false);
         }
 
         @Override

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/10316c39/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JsonLayout.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JsonLayout.java 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JsonLayout.java
index ff4bf0b..90074bc 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JsonLayout.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JsonLayout.java
@@ -808,10 +808,11 @@ public final class JsonLayout extends 
AbstractJacksonLayout {
     static final String CONTENT_TYPE = "application/json";
 
     protected JsonLayout(final Configuration config, final boolean 
locationInfo, final boolean properties,
+            final boolean encodeThreadContextAsList,
             final boolean complete, final boolean compact, final boolean 
eventEol, final String headerPattern,
             final String footerPattern, final Charset charset) {
-        super(config, new JacksonFactory.JSON().newWriter(locationInfo, 
properties, compact), charset, compact,
-                complete, eventEol,
+        super(config, new 
JacksonFactory.JSON(encodeThreadContextAsList).newWriter(locationInfo, 
properties, compact),
+                charset, compact, complete, eventEol,
                 PatternLayout.createSerializer(config, null, headerPattern, 
DEFAULT_HEADER, null, false, false),
                 PatternLayout.createSerializer(config, null, footerPattern, 
DEFAULT_FOOTER, null, false, false));
     }
@@ -872,7 +873,7 @@ public final class JsonLayout extends AbstractJacksonLayout 
{
 
     /**
      * Creates a JSON Layout.
-     * @param config 
+     * @param config
      *           The plugin configuration.
      * @param locationInfo
      *            If "true", includes the location information in the 
generated JSON.
@@ -899,6 +900,7 @@ public final class JsonLayout extends AbstractJacksonLayout 
{
             @PluginConfiguration final Configuration config,
             @PluginAttribute(value = "locationInfo", defaultBoolean = false) 
final boolean locationInfo,
             @PluginAttribute(value = "properties", defaultBoolean = false) 
final boolean properties,
+            @PluginAttribute(value = "propertiesAsList", defaultBoolean = 
false) final boolean propertiesAsList,
             @PluginAttribute(value = "complete", defaultBoolean = false) final 
boolean complete,
             @PluginAttribute(value = "compact", defaultBoolean = false) final 
boolean compact,
             @PluginAttribute(value = "eventEol", defaultBoolean = false) final 
boolean eventEol,
@@ -907,7 +909,9 @@ public final class JsonLayout extends AbstractJacksonLayout 
{
             @PluginAttribute(value = "charset", defaultString = "UTF-8") final 
Charset charset
             // @formatter:on
     ) {
-        return new JsonLayout(config, locationInfo, properties, complete, 
compact, eventEol, headerPattern, footerPattern, charset);
+        final boolean encodeThreadContextAsList = properties && 
propertiesAsList;
+        return new JsonLayout(config, locationInfo, properties, 
encodeThreadContextAsList, complete, compact, eventEol,
+                headerPattern, footerPattern, charset);
     }
 
     /**
@@ -916,7 +920,8 @@ public final class JsonLayout extends AbstractJacksonLayout 
{
      * @return A JSON Layout.
      */
     public static JsonLayout createDefaultLayout() {
-        return new JsonLayout(new DefaultConfiguration(), false, false, false, 
false, false, DEFAULT_HEADER, DEFAULT_FOOTER, StandardCharsets.UTF_8);
+        return new JsonLayout(new DefaultConfiguration(), false, false, false, 
false, false, false,
+                DEFAULT_HEADER, DEFAULT_FOOTER, StandardCharsets.UTF_8);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/10316c39/log4j-core/src/main/java/org/apache/logging/log4j/core/net/server/JsonInputStreamLogEventBridge.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/server/JsonInputStreamLogEventBridge.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/server/JsonInputStreamLogEventBridge.java
index 8ed2732..cf89b5a 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/server/JsonInputStreamLogEventBridge.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/server/JsonInputStreamLogEventBridge.java
@@ -33,13 +33,15 @@ public class JsonInputStreamLogEventBridge extends 
InputStreamLogEventBridge {
     private static final char EVENT_START_MARKER = '{';
     private static final char JSON_ESC = '\\';
     private static final char JSON_STR_DELIM = Chars.DQUOTE;
+    private static final boolean THREAD_CONTEXT_MAP_AS_LIST = false;
 
     public JsonInputStreamLogEventBridge() {
         this(1024, Charset.defaultCharset());
     }
 
     public JsonInputStreamLogEventBridge(final int bufferSize, final Charset 
charset) {
-        super(new Log4jJsonObjectMapper(), bufferSize, charset, 
String.valueOf(EVENT_END_MARKER));
+        super(new Log4jJsonObjectMapper(THREAD_CONTEXT_MAP_AS_LIST), 
bufferSize, charset,
+                String.valueOf(EVENT_END_MARKER));
     }
 
     @Override
@@ -58,7 +60,7 @@ public class JsonInputStreamLogEventBridge extends 
InputStreamLogEventBridge {
             if (inEsc) {
                // Skip this char and continue
                inEsc = false;
-            } else { 
+            } else {
                 switch (c) {
                 case EVENT_START_MARKER:
                     if (!inStr) {

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/10316c39/log4j-core/src/test/java/org/apache/logging/log4j/core/jackson/LevelMixInJsonTest.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/jackson/LevelMixInJsonTest.java
 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/jackson/LevelMixInJsonTest.java
index f2e63e5..da77aa0 100644
--- 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/jackson/LevelMixInJsonTest.java
+++ 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/jackson/LevelMixInJsonTest.java
@@ -23,7 +23,7 @@ public class LevelMixInJsonTest extends LevelMixInTest {
 
     @Override
     protected ObjectMapper newObjectMapper() {
-        return new Log4jJsonObjectMapper();
+        return new Log4jJsonObjectMapper(false);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/10316c39/log4j-core/src/test/java/org/apache/logging/log4j/core/jackson/StackTraceElementMixInTest.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/jackson/StackTraceElementMixInTest.java
 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/jackson/StackTraceElementMixInTest.java
index 5f3383b..3be145c 100644
--- 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/jackson/StackTraceElementMixInTest.java
+++ 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/jackson/StackTraceElementMixInTest.java
@@ -77,7 +77,8 @@ public class StackTraceElementMixInTest {
     @Test
     public void testFromJsonWithLog4jModule() throws Exception {
         final ObjectMapper mapper = new ObjectMapper();
-        final SimpleModule module = new Log4jJsonModule();
+        final boolean encodeThreadContextAsList = false;
+        final SimpleModule module = new 
Log4jJsonModule(encodeThreadContextAsList);
         module.addDeserializer(StackTraceElement.class, new 
Log4jStackTraceElementDeserializer());
         mapper.registerModule(module);
         final StackTraceElement expected = new 
StackTraceElement("package.SomeClass", "someMethod", "SomeClass.java", 123);

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/10316c39/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/JsonLayoutTest.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/JsonLayoutTest.java
 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/JsonLayoutTest.java
index 22b017f..55a22b9 100644
--- 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/JsonLayoutTest.java
+++ 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/JsonLayoutTest.java
@@ -84,11 +84,18 @@ public class JsonLayoutTest {
         Assert.fail("Cannot find " + expected + " in " + list);
     }
 
-    private void checkMapEntry(final String key, final String value, final 
boolean compact, final String str) {
+    private void checkMapEntry(final String key, final String value, final 
boolean compact, final String str,
+            final boolean contextMapAslist) {
         final String propSep = this.toPropertySeparator(compact);
-        // "name":"value"
-        final String expected = 
String.format("{\"key\":\"%s\",\"value\":\"%s\"}", key, value);
-        assertTrue("Cannot find " + expected + " in " + str, 
str.contains(expected));
+        if (contextMapAslist) {
+            // {"key":"KEY", "value":"VALUE"}
+            final String expected = 
String.format("{\"key\":\"%s\",\"value\":\"%s\"}", key, value);
+            assertTrue("Cannot find contextMapAslist " + expected + " in " + 
str, str.contains(expected));
+        } else {
+            // "KEY":"VALUE"
+            final String expected = String.format("\"%s\":\"%s\"", key, value);
+            assertTrue("Cannot find contextMap " + expected + " in " + str, 
str.contains(expected));
+        }
     }
 
     private void checkProperty(final String key, final String value, final 
boolean compact, final String str) {
@@ -103,11 +110,12 @@ public class JsonLayoutTest {
         assertTrue(str, str.contains(DQUOTE + name + DQUOTE + propSep));
     }
 
-    private void testAllFeatures(final boolean includeSource, final boolean 
compact, final boolean eventEol, final boolean includeContext)
+    private void testAllFeatures(final boolean includeSource, final boolean 
compact, final boolean eventEol,
+            final boolean includeContext, final boolean contextMapAslist)
             throws Exception {
         final Log4jLogEvent expected = LogEventFixtures.createLogEvent();
         final AbstractJacksonLayout layout = JsonLayout.createLayout(null, 
includeSource,
-                includeContext, false, compact, eventEol, null, null, 
StandardCharsets.UTF_8);
+                includeContext, contextMapAslist, false, compact, eventEol, 
null, null, StandardCharsets.UTF_8);
         final String str = layout.toSerializable(expected);
         // System.out.println(str);
         final String propSep = this.toPropertySeparator(compact);
@@ -115,11 +123,11 @@ public class JsonLayoutTest {
         assertEquals(str, !compact || eventEol, str.contains("\n"));
         assertEquals(str, includeSource, str.contains("source"));
         assertEquals(str, includeContext, str.contains("contextMap"));
-        final Log4jLogEvent actual = new 
Log4jJsonObjectMapper().readValue(str, Log4jLogEvent.class);
+        final Log4jLogEvent actual = new 
Log4jJsonObjectMapper(contextMapAslist).readValue(str, Log4jLogEvent.class);
         LogEventFixtures.assertEqualLogEvents(expected, actual, includeSource, 
includeContext);
         if (includeContext) {
-            this.checkMapEntry("MDC.A", "A_Value", compact, str);
-            this.checkMapEntry("MDC.B", "B_Value", compact, str);
+            this.checkMapEntry("MDC.A", "A_Value", compact, str, 
contextMapAslist);
+            this.checkMapEntry("MDC.B", "B_Value", compact, str, 
contextMapAslist);
         }
         //
         assertNull(actual.getThrown());
@@ -179,7 +187,9 @@ public class JsonLayoutTest {
         }
         final Configuration configuration = 
rootLogger.getContext().getConfiguration();
         // set up appender
-        final AbstractJacksonLayout layout = 
JsonLayout.createLayout(configuration, true, true, true, false, false, null, 
null, null);
+        final boolean propertiesAsList = false;
+        final AbstractJacksonLayout layout = 
JsonLayout.createLayout(configuration, true, true, propertiesAsList,
+                true, false, false, null, null, null);
         final ListAppender appender = new ListAppender("List", null, layout, 
true, false);
         appender.start();
 
@@ -216,7 +226,9 @@ public class JsonLayoutTest {
         final Configuration configuration = 
rootLogger.getContext().getConfiguration();
         // set up appender
         // Use [[ and ]] to test header and footer (instead of [ and ])
-        final AbstractJacksonLayout layout = 
JsonLayout.createLayout(configuration, true, true, true, false, false, "[[", 
"]]", null);
+        final boolean propertiesAsList = false;
+        final AbstractJacksonLayout layout = 
JsonLayout.createLayout(configuration, true, true, propertiesAsList,
+                true, false, false, "[[", "]]", null);
         final ListAppender appender = new ListAppender("List", null, layout, 
true, false);
         appender.start();
 
@@ -255,8 +267,9 @@ public class JsonLayoutTest {
 
     @Test
     public void testLayoutLoggerName() throws Exception {
-        final AbstractJacksonLayout layout = JsonLayout.createLayout(null, 
false, false, false, true, false, null, null,
-                StandardCharsets.UTF_8);
+        final boolean propertiesAsList = false;
+        final AbstractJacksonLayout layout = JsonLayout.createLayout(null, 
false, false, propertiesAsList,
+                false, true, false, null, null, StandardCharsets.UTF_8);
         final Log4jLogEvent expected = Log4jLogEvent.newBuilder() //
                 .setLoggerName("a.B") //
                 .setLoggerFqcn("f.q.c.n") //
@@ -266,24 +279,29 @@ public class JsonLayoutTest {
                 .setTimeMillis(1).build();
         final String str = layout.toSerializable(expected);
         assertTrue(str, str.contains("\"loggerName\":\"a.B\""));
-        final Log4jLogEvent actual = new 
Log4jJsonObjectMapper().readValue(str, Log4jLogEvent.class);
+        final Log4jLogEvent actual = new 
Log4jJsonObjectMapper(propertiesAsList).readValue(str, Log4jLogEvent.class);
         assertEquals(expected.getLoggerName(), actual.getLoggerName());
         assertEquals(expected, actual);
     }
 
     @Test
     public void testLocationOffCompactOffMdcOff() throws Exception {
-        this.testAllFeatures(false, false, false, false);
+        this.testAllFeatures(false, false, false, false, false);
     }
 
     @Test
     public void testLocationOnCompactOnMdcOn() throws Exception {
-        this.testAllFeatures(true, true, false, true);
+        this.testAllFeatures(true, true, false, true, false);
     }
 
     @Test
     public void testLocationOnCompactOnEventEolOnMdcOn() throws Exception {
-        this.testAllFeatures(true, true, true, true);
+        this.testAllFeatures(true, true, true, true, false);
+    }
+
+    @Test
+    public void testLocationOnCompactOnEventEolOnMdcOnMdcAsList() throws 
Exception {
+        this.testAllFeatures(true, true, true, true, true);
     }
 
     private String toPropertySeparator(final boolean compact) {

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/10316c39/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/YamlLayoutTest.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/YamlLayoutTest.java
 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/YamlLayoutTest.java
index fd30bbd..ae588a2 100644
--- 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/YamlLayoutTest.java
+++ 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/YamlLayoutTest.java
@@ -88,7 +88,8 @@ public class YamlLayoutTest {
     private void checkMapEntry(final String key, final String value, final 
boolean compact, final String str) {
         final String propSep = this.toPropertySeparator(compact, true);
         // "name":"value"
-        final String expected = String.format("- key: \"%s\"\n  value: 
\"%s\"", key, value);
+        //final String expected = String.format("- key: \"%s\"\n  value: 
\"%s\"", key, value);
+        final String expected = String.format("%s: \"%s\"", key, value);
         assertTrue("Cannot find " + expected + " in " + str, 
str.contains(expected));
     }
 
@@ -106,7 +107,7 @@ public class YamlLayoutTest {
     }
 
     private void testAllFeatures(final boolean includeSource, final boolean 
compact, final boolean eventEol,
-            final boolean includeContext) throws Exception {
+            final boolean includeContext, final boolean contextMapAslist) 
throws Exception {
         final Log4jLogEvent expected = LogEventFixtures.createLogEvent();
         final AbstractJacksonLayout layout = YamlLayout.createLayout(null, 
includeSource, includeContext, null, null,
                 StandardCharsets.UTF_8);
@@ -117,7 +118,7 @@ public class YamlLayoutTest {
         assertEquals(str, !compact || eventEol, str.contains("\n"));
         assertEquals(str, includeSource, str.contains("source"));
         assertEquals(str, includeContext, str.contains("contextMap"));
-        final Log4jLogEvent actual = new 
Log4jYamlObjectMapper().readValue(str, Log4jLogEvent.class);
+        final Log4jLogEvent actual = new 
Log4jYamlObjectMapper(contextMapAslist).readValue(str, Log4jLogEvent.class);
         LogEventFixtures.assertEqualLogEvents(expected, actual, includeSource, 
includeContext);
         if (includeContext) {
             this.checkMapEntry("MDC.A", "A_Value", compact, str);
@@ -266,19 +267,19 @@ public class YamlLayoutTest {
                 .setTimeMillis(1).build();
         final String str = layout.toSerializable(expected);
         assertTrue(str, str.contains("loggerName: \"a.B\""));
-        final Log4jLogEvent actual = new 
Log4jYamlObjectMapper().readValue(str, Log4jLogEvent.class);
+        final Log4jLogEvent actual = new 
Log4jYamlObjectMapper(false).readValue(str, Log4jLogEvent.class);
         assertEquals(expected.getLoggerName(), actual.getLoggerName());
         assertEquals(expected, actual);
     }
 
     @Test
     public void testLocationOffCompactOffMdcOff() throws Exception {
-        this.testAllFeatures(false, false, false, false);
+        this.testAllFeatures(false, false, false, false, false);
     }
 
     @Test
     public void testLocationOnCompactOffEventEolOffMdcOn() throws Exception {
-        this.testAllFeatures(true, false, false, true);
+        this.testAllFeatures(true, false, false, true, false);
     }
 
     private String toPropertySeparator(final boolean compact, final boolean 
value) {

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/10316c39/log4j-core/src/test/java/org/apache/logging/log4j/core/net/server/AbstractSocketServerTest.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/server/AbstractSocketServerTest.java
 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/net/server/AbstractSocketServerTest.java
index ee5c2de..9000698 100644
--- 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/server/AbstractSocketServerTest.java
+++ 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/net/server/AbstractSocketServerTest.java
@@ -54,9 +54,9 @@ public abstract class AbstractSocketServerTest {
     private static final String MESSAGE = "This is test message";
 
     private static final String MESSAGE_2 = "This is test message 2";
-    
+
     private static final String MESSAGE_WITH_SPECIAL_CHARS = 
"{This}\n[is]\"n\"a\"\r\ntrue:\n\ttest,\nmessage";
-    
+
     static final int PORT_NUM = AvailablePortFinder.getNextAvailable();
 
     static final int PORT = PORT_NUM;
@@ -78,7 +78,7 @@ public abstract class AbstractSocketServerTest {
     }
 
     protected Layout<String> createJsonLayout() {
-        return JsonLayout.createLayout(null, true, true, false, false, false, 
null, null, null);
+        return JsonLayout.createLayout(null, true, true, false, false, false, 
false, null, null, null);
     }
 
     protected abstract Layout<? extends Serializable> createLayout();
@@ -145,13 +145,13 @@ public abstract class AbstractSocketServerTest {
             testServer(m1, m2);
         }
     }
-    
-    
+
+
     @Test
     public void testMessagesWithSpecialChars() throws Exception {
         testServer(MESSAGE_WITH_SPECIAL_CHARS);
     }
-    
+
 
     private void testServer(final int size) throws Exception {
         final String[] messages = new String[size];

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/10316c39/src/changes/changes.xml
----------------------------------------------------------------------
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index ca6b2d4..3593ad7 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -92,6 +92,12 @@
       <action issue="LOG4J2-1269" dev="rpopma" type="fix">
         (GC) AsyncLogger should use thread-local translator by default.
       </action>
+      <action issue="LOG4J2-623" dev="rpopma" type="fix">
+        Generate MDC properties as a JSON map in JSONLayout, with option to 
output as list of map entries.
+      </action>
+      <action issue="LOG4J2-1362" dev="rpopma" type="add" due-to="Gary 
Gregory">
+        Added a YAML layout.
+      </action>
       <action issue="LOG4J2-1387" dev="rpopma" type="fix">
         Fixed memory leak related to shutdown hook.
       </action>

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/10316c39/src/site/xdoc/manual/layouts.xml.vm
----------------------------------------------------------------------
diff --git a/src/site/xdoc/manual/layouts.xml.vm 
b/src/site/xdoc/manual/layouts.xml.vm
index 3a9d175..9d6924e 100644
--- a/src/site/xdoc/manual/layouts.xml.vm
+++ b/src/site/xdoc/manual/layouts.xml.vm
@@ -369,6 +369,13 @@ logger.debug("one={}, two={}, three={}", 1, 2, 3);
               <td>If true, the appender includes the thread context map in the 
generated JSON. Defaults to false.</td>
             </tr>
             <tr>
+              <td>propertiesAsList</td>
+              <td>boolean</td>
+              <td>If true, the thread context map is included as a list of map 
entry objects, where each entry has
+              a "key" attribute (whose value is the key) and a "value" 
attribute (whose value is the value).
+              Defaults to false, in which case the thread context map is 
included as a simple map of key-value pairs.</td>
+            </tr>
+            <tr>
               <td>locationInfo</td>
               <td>boolean</td>
               <td>

Reply via email to