You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by mi...@apache.org on 2016/04/18 13:38:09 UTC

[23/50] logging-log4j2 git commit: [LOG4J2-1362] Create a YAML layout.

[LOG4J2-1362] Create a YAML layout.

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

Branch: refs/heads/LOG4J2-1365
Commit: 80a43988a017a8e95f6188386cd5fe7ddf3945bd
Parents: 71fbda8
Author: ggregory <gg...@apache.org>
Authored: Sun Apr 17 15:27:19 2016 -0700
Committer: ggregory <gg...@apache.org>
Committed: Sun Apr 17 15:27:19 2016 -0700

----------------------------------------------------------------------
 .../org/apache/logging/log4j/util/Strings.java  |  12 +
 .../log4j/core/jackson/Log4jYamlModule.java     |  48 ++++
 .../core/jackson/Log4jYamlObjectMapper.java     |  41 +++
 .../log4j/core/layout/JacksonFactory.java       |  34 +++
 .../logging/log4j/core/layout/YamlLayout.java   | 193 +++++++++++++
 .../logging/log4j/MarkerMixInYamlTest.java      |  31 ++
 .../log4j/core/jackson/LevelMixInYamlTest.java  |  29 ++
 .../jackson/StackTraceElementMixInTest.java     |   5 +
 .../log4j/core/layout/YamlLayoutTest.java       | 287 +++++++++++++++++++
 .../log4j/web/ServletRequestThreadContext.java  |  29 ++
 10 files changed, 709 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/80a43988/log4j-api/src/main/java/org/apache/logging/log4j/util/Strings.java
----------------------------------------------------------------------
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/Strings.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Strings.java
index 6ee7dca..294b771 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/Strings.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Strings.java
@@ -16,6 +16,8 @@
  */
 package org.apache.logging.log4j.util;
 
+import java.util.Locale;
+
 /**
  * <em>Consider this class private.</em>
  * 
@@ -127,6 +129,16 @@ public final class Strings {
     }
 
     /**
+     * Shorthand for {@code str.toUpperCase(Locale.ROOT);}
+     * @param str The string to upper case.
+     * @return a new string
+     * @see String#toLowerCase(Locale)
+     */
+    public String toRootUpperCase(final String str) {
+        return str.toUpperCase(Locale.ROOT);
+    }
+    
+    /**
      * <p>
      * Removes control characters (char &lt;= 32) from both ends of this String returning {@code null} if the String is
      * empty ("") after the trim or if it is {@code null}.

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/80a43988/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
new file mode 100644
index 0000000..4052320
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jYamlModule.java
@@ -0,0 +1,48 @@
+/*
+ * 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 org.apache.logging.log4j.core.jackson.Initializers.SetupContextInitializer;
+import org.apache.logging.log4j.core.jackson.Initializers.SimpleModuleInitializer;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+
+/**
+ * <p>
+ * <em>Consider this class private.</em>
+ * </p>
+ */
+final class Log4jYamlModule extends SimpleModule {
+
+    private static final long serialVersionUID = 1L;
+
+    Log4jYamlModule() {
+        super(Log4jYamlModule.class.getName(), new Version(2, 0, 0, null, null, null));
+        // MUST init here.
+        // Calling this from setupModule is too late!
+        //noinspection ThisEscapedInObjectConstruction
+        new SimpleModuleInitializer().initialize(this);
+    }
+
+    @Override
+    public void setupModule(final SetupContext context) {
+        // Calling super is a MUST!
+        super.setupModule(context);
+        new SetupContextInitializer().setupModule(context);
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/80a43988/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
new file mode 100644
index 0000000..9ab787a
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jYamlObjectMapper.java
@@ -0,0 +1,41 @@
+/*
+ * 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 com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
+
+/**
+ * A Jackson {@link ObjectMapper} initialized for Log4j.
+ * <p>
+ * <em>Consider this class private.</em>
+ * </p>
+ */
+public class Log4jYamlObjectMapper extends YAMLMapper {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Create a new instance using the {@link Log4jYamlModule}.
+     */
+    public Log4jYamlObjectMapper() {
+        this.registerModule(new Log4jYamlModule());
+        this.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/80a43988/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 537b634..44731f4 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
@@ -23,6 +23,7 @@ import org.apache.logging.log4j.core.impl.Log4jLogEvent;
 import org.apache.logging.log4j.core.jackson.JsonConstants;
 import org.apache.logging.log4j.core.jackson.Log4jJsonObjectMapper;
 import org.apache.logging.log4j.core.jackson.Log4jXmlObjectMapper;
+import org.apache.logging.log4j.core.jackson.Log4jYamlObjectMapper;
 import org.apache.logging.log4j.core.jackson.XmlConstants;
 
 import com.fasterxml.jackson.core.PrettyPrinter;
@@ -103,6 +104,39 @@ abstract class JacksonFactory {
         }
     }
 
+    static class YAML extends JacksonFactory {
+
+        @Override
+        protected String getPropertNameForContextMap() {
+            return JsonConstants.ELT_CONTEXT_MAP;
+        }
+
+        @Override
+        protected String getPropertNameForSource() {
+            return JsonConstants.ELT_SOURCE;
+        }
+
+        @Override
+        protected String getPropertNameForNanoTime() {
+            return JsonConstants.ELT_NANO_TIME;
+        }
+
+        @Override
+        protected PrettyPrinter newCompactPrinter() {
+            return new MinimalPrettyPrinter();
+        }
+
+        @Override
+        protected ObjectMapper newObjectMapper() {
+            return new Log4jYamlObjectMapper();
+        }
+
+        @Override
+        protected PrettyPrinter newPrettyPrinter() {
+            return new DefaultPrettyPrinter();
+        }
+    }
+
     abstract protected String getPropertNameForContextMap();
 
     abstract protected String getPropertNameForSource();

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/80a43988/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/YamlLayout.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/YamlLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/YamlLayout.java
new file mode 100644
index 0000000..0426fea
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/YamlLayout.java
@@ -0,0 +1,193 @@
+/*
+ * 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.layout;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.DefaultConfiguration;
+import org.apache.logging.log4j.core.config.Node;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+/**
+ * Appends a series of YAML events as strings serialized as bytes.
+ *
+ * <h3>Complete well-formed YAML vs. fragment YAML</h3>
+ * <p>
+ * If you configure {@code complete="true"}, the appender outputs a well-formed YAML document. By default, with
+ * {@code complete="false"}, you should include the output as an <em>external file</em> in a separate file to form a
+ * well-formed YAML document.
+ * </p>
+ * <p>
+ * A well-formed YAML event follows this pattern:
+ * </p>
+ *
+ * <pre>
+ * 
+ * </pre>
+ * <p>
+ * If {@code complete="false"}, the appender does not write the YAML open array character "[" at the start of the
+ * document, "]" and the end, nor comma "," between records.
+ * </p>
+ * <p>
+ * This approach enforces the independence of the YamlLayout and the appender where you embed it.
+ * </p>
+ * <h3>Encoding</h3>
+ * <p>
+ * Appenders using this layout should have their {@code charset} set to {@code UTF-8} or {@code UTF-16}, otherwise
+ * events containing non ASCII characters could result in corrupted log files.
+ * </p>
+ * <h3>Pretty vs. compact YAML</h3>
+ * <p>
+ * By default, the YAML layout is not compact (a.k.a. "pretty") with {@code compact="false"}, which means the appender
+ * uses end-of-line characters and indents lines to format the text. If {@code compact="true"}, then no end-of-line or
+ * indentation is used. Message content may contain, of course, escaped end-of-lines.
+ * </p>
+ */
+@Plugin(name = "JsonLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
+public final class YamlLayout extends AbstractJacksonLayout {
+
+    private static final String DEFAULT_FOOTER = ""; // TODO maybe
+
+    private static final String DEFAULT_HEADER = ""; // TODO maybe
+
+    static final String CONTENT_TYPE = "application/yaml";
+
+    protected YamlLayout(final Configuration config, final boolean locationInfo, final boolean properties,
+            final boolean complete, final boolean compact, final boolean eventEol, final String headerPattern,
+            final String footerPattern, final Charset charset) {
+        super(config, new JacksonFactory.YAML().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));
+    }
+
+    /**
+     * Returns appropriate YAML header.
+     *
+     * @return a byte array containing the header, opening the YAML array.
+     */
+    @Override
+    public byte[] getHeader() {
+        if (!this.complete) {
+            return null;
+        }
+        final StringBuilder buf = new StringBuilder();
+        final String str = serializeToString(getHeaderSerializer());
+        if (str != null) {
+            buf.append(str);
+        }
+        buf.append(this.eol);
+        return getBytes(buf.toString());
+    }
+
+    /**
+     * Returns appropriate YAML footer.
+     *
+     * @return a byte array containing the footer, closing the YAML array.
+     */
+    @Override
+    public byte[] getFooter() {
+        if (!this.complete) {
+            return null;
+        }
+        final StringBuilder buf = new StringBuilder();
+        buf.append(this.eol);
+        final String str = serializeToString(getFooterSerializer());
+        if (str != null) {
+            buf.append(str);
+        }
+        buf.append(this.eol);
+        return getBytes(buf.toString());
+    }
+
+    @Override
+    public Map<String, String> getContentFormat() {
+        final Map<String, String> result = new HashMap<>();
+        result.put("version", "2.0");
+        return result;
+    }
+
+    @Override
+    /**
+     * @return The content type.
+     */
+    public String getContentType() {
+        return CONTENT_TYPE + "; charset=" + this.getCharset();
+    }
+
+    /**
+     * Creates a YAML Layout.
+     * 
+     * @param config
+     *            The plugin configuration.
+     * @param locationInfo
+     *            If "true", includes the location information in the generated YAML.
+     * @param properties
+     *            If "true", includes the thread context in the generated YAML.
+     * @param headerPattern
+     *            The header pattern, defaults to {@code ""} if null.
+     * @param footerPattern
+     *            The header pattern, defaults to {@code ""} if null.
+     * @param footerPattern
+     * @param charset
+     *            The character set to use, if {@code null}, uses "UTF-8".
+     * @return A YAML Layout.
+     */
+    @PluginFactory
+    public static AbstractJacksonLayout createLayout(
+            // @formatter:off
+            @PluginConfiguration final Configuration config,
+            @PluginAttribute(value = "locationInfo", defaultBoolean = false) final boolean locationInfo,
+            @PluginAttribute(value = "properties", defaultBoolean = false) final boolean properties,
+            @PluginAttribute(value = "header", defaultString = DEFAULT_HEADER) final String headerPattern,
+            @PluginAttribute(value = "footer", defaultString = DEFAULT_FOOTER) final String footerPattern,
+            @PluginAttribute(value = "charset", defaultString = "UTF-8") final Charset charset
+            // @formatter:on
+    ) {
+        return new YamlLayout(config, locationInfo, properties, false, false, true, headerPattern, footerPattern,
+                charset);
+    }
+
+    /**
+     * Creates a YAML Layout using the default settings. Useful for testing.
+     *
+     * @return A YAML Layout.
+     */
+    public static AbstractJacksonLayout createDefaultLayout() {
+        return new YamlLayout(new DefaultConfiguration(), false, false, false, false, false, DEFAULT_HEADER,
+                DEFAULT_FOOTER, StandardCharsets.UTF_8);
+    }
+
+    @Override
+    public void toSerializable(final LogEvent event, final Writer writer) throws IOException {
+        if (complete && eventCount > 0) {
+            writer.append(", ");
+        }
+        super.toSerializable(event, writer);
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/80a43988/log4j-core/src/test/java/org/apache/logging/log4j/MarkerMixInYamlTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/MarkerMixInYamlTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/MarkerMixInYamlTest.java
new file mode 100644
index 0000000..89f02a1
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/MarkerMixInYamlTest.java
@@ -0,0 +1,31 @@
+/*
+* 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;
+
+import org.apache.logging.log4j.core.jackson.Log4jYamlObjectMapper;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class MarkerMixInYamlTest extends MarkerMixInTest {
+
+    @Override
+    protected ObjectMapper newObjectMapper() {
+        return new Log4jYamlObjectMapper();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/80a43988/log4j-core/src/test/java/org/apache/logging/log4j/core/jackson/LevelMixInYamlTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/jackson/LevelMixInYamlTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/jackson/LevelMixInYamlTest.java
new file mode 100644
index 0000000..cff034e
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/jackson/LevelMixInYamlTest.java
@@ -0,0 +1,29 @@
+/*
+ * 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 com.fasterxml.jackson.databind.ObjectMapper;
+
+public class LevelMixInYamlTest extends LevelMixInTest {
+
+    @Override
+    protected ObjectMapper newObjectMapper() {
+        return new Log4jYamlObjectMapper();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/80a43988/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 ed9b3da..5f3383b 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
@@ -34,6 +34,11 @@ public class StackTraceElementMixInTest {
         this.roundtrip(new Log4jJsonObjectMapper());
     }
 
+    @Test
+    public void testLog4jYamlObjectMapper() throws Exception {
+        this.roundtrip(new Log4jYamlObjectMapper());
+    }
+
     /**
      * @param mapper
      * @throws JsonProcessingException

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/80a43988/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
new file mode 100644
index 0000000..fd30bbd
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/YamlLayoutTest.java
@@ -0,0 +1,287 @@
+/*
+ * 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.layout;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.BasicConfigurationFactory;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.ConfigurationFactory;
+import org.apache.logging.log4j.core.impl.Log4jLogEvent;
+import org.apache.logging.log4j.core.jackson.Log4jYamlObjectMapper;
+import org.apache.logging.log4j.message.SimpleMessage;
+import org.apache.logging.log4j.spi.AbstractLogger;
+import org.apache.logging.log4j.test.appender.ListAppender;
+import org.apache.logging.log4j.util.Strings;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Tests the YamlLayout class.
+ */
+public class YamlLayoutTest {
+    static ConfigurationFactory cf = new BasicConfigurationFactory();
+
+    private static final String DQUOTE = "\"";
+
+    @AfterClass
+    public static void cleanupClass() {
+        ConfigurationFactory.removeConfigurationFactory(cf);
+        ThreadContext.clearAll();
+    }
+
+    @BeforeClass
+    public static void setupClass() {
+        ThreadContext.clearAll();
+        ConfigurationFactory.setConfigurationFactory(cf);
+        final LoggerContext ctx = LoggerContext.getContext();
+        ctx.reconfigure();
+    }
+
+    LoggerContext ctx = LoggerContext.getContext();
+
+    Logger rootLogger = this.ctx.getLogger("");
+
+    private void checkAt(final String expected, final int lineIndex, final List<String> list) {
+        final String trimedLine = list.get(lineIndex).trim();
+        assertTrue("Incorrect line index " + lineIndex + ": " + Strings.dquote(trimedLine),
+                trimedLine.equals(expected));
+    }
+
+    private void checkContains(final String expected, final List<String> list) {
+        for (final String string : list) {
+            final String trimedLine = string.trim();
+            if (trimedLine.equals(expected)) {
+                return;
+            }
+        }
+        Assert.fail("Cannot find " + expected + " in " + list);
+    }
+
+    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);
+        assertTrue("Cannot find " + expected + " in " + str, str.contains(expected));
+    }
+
+    private void checkProperty(final String key, final String value, final boolean compact, final String str,
+            final boolean isValue) {
+        final String propSep = this.toPropertySeparator(compact, isValue);
+        // {"key":"MDC.B","value":"B_Value"}
+        final String expected = String.format("%s%s\"%s\"", key, propSep, value);
+        assertTrue("Cannot find " + expected + " in " + str, str.contains(expected));
+    }
+
+    private void checkPropertyName(final String name, final boolean compact, final String str, final boolean isValue) {
+        final String propSep = this.toPropertySeparator(compact, isValue);
+        assertTrue(str, str.contains(name + propSep));
+    }
+
+    private void testAllFeatures(final boolean includeSource, final boolean compact, final boolean eventEol,
+            final boolean includeContext) throws Exception {
+        final Log4jLogEvent expected = LogEventFixtures.createLogEvent();
+        final AbstractJacksonLayout layout = YamlLayout.createLayout(null, includeSource, includeContext, null, null,
+                StandardCharsets.UTF_8);
+        final String str = layout.toSerializable(expected);
+        // System.out.println(str);
+        final String propSep = this.toPropertySeparator(compact, true);
+        // Just check for \n since \r might or might not be there.
+        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);
+        LogEventFixtures.assertEqualLogEvents(expected, actual, includeSource, includeContext);
+        if (includeContext) {
+            this.checkMapEntry("MDC.A", "A_Value", compact, str);
+            this.checkMapEntry("MDC.B", "B_Value", compact, str);
+        }
+        //
+        assertNull(actual.getThrown());
+        // make sure the names we want are used
+        this.checkPropertyName("timeMillis", compact, str, true);
+        this.checkPropertyName("thread", compact, str, true); // and not threadName
+        this.checkPropertyName("level", compact, str, true);
+        this.checkPropertyName("loggerName", compact, str, true);
+        this.checkPropertyName("marker", compact, str, false);
+        this.checkPropertyName("name", compact, str, true);
+        this.checkPropertyName("parents", compact, str, false);
+        this.checkPropertyName("message", compact, str, true);
+        this.checkPropertyName("thrown", compact, str, false);
+        this.checkPropertyName("cause", compact, str, false);
+        this.checkPropertyName("class", compact, str, true);
+        this.checkPropertyName("method", compact, str, true);
+        this.checkPropertyName("file", compact, str, true);
+        this.checkPropertyName("line", compact, str, true);
+        this.checkPropertyName("exact", compact, str, true);
+        this.checkPropertyName("location", compact, str, true);
+        this.checkPropertyName("version", compact, str, true);
+        this.checkPropertyName("commonElementCount", compact, str, true);
+        this.checkPropertyName("localizedMessage", compact, str, true);
+        this.checkPropertyName("extendedStackTrace", compact, str, false);
+        this.checkPropertyName("suppressed", compact, str, false);
+        this.checkPropertyName("loggerFqcn", compact, str, true);
+        this.checkPropertyName("endOfBatch", compact, str, true);
+        if (includeContext) {
+            this.checkPropertyName("contextMap", compact, str, false);
+        }
+        this.checkPropertyName("contextStack", compact, str, false);
+        if (includeSource) {
+            this.checkPropertyName("source", compact, str, false);
+        }
+        // check some attrs
+        this.checkProperty("loggerFqcn", "f.q.c.n", compact, str, true);
+        this.checkProperty("loggerName", "a.B", compact, str, true);
+    }
+
+    @Test
+    public void testContentType() {
+        final AbstractJacksonLayout layout = YamlLayout.createDefaultLayout();
+        assertEquals("application/yaml; charset=UTF-8", layout.getContentType());
+    }
+
+    @Test
+    public void testDefaultCharset() {
+        final AbstractJacksonLayout layout = YamlLayout.createDefaultLayout();
+        assertEquals(StandardCharsets.UTF_8, layout.getCharset());
+    }
+
+    @Test
+    public void testEscapeLayout() throws Exception {
+        final Map<String, Appender> appenders = this.rootLogger.getAppenders();
+        for (final Appender appender : appenders.values()) {
+            this.rootLogger.removeAppender(appender);
+        }
+        final Configuration configuration = rootLogger.getContext().getConfiguration();
+        // set up appender
+        final AbstractJacksonLayout layout = YamlLayout.createLayout(configuration, true, true, null, null, null);
+        final ListAppender appender = new ListAppender("List", null, layout, true, false);
+        appender.start();
+
+        // set appender on root and set level to debug
+        this.rootLogger.addAppender(appender);
+        this.rootLogger.setLevel(Level.DEBUG);
+
+        // output starting message
+        this.rootLogger.debug("Here is a quote ' and then a double quote \"");
+
+        appender.stop();
+
+        final List<String> list = appender.getMessages();
+
+        this.checkAt("---", 0, list);
+        this.checkContains("level: \"DEBUG\"", list);
+        this.checkContains("message: \"Here is a quote ' and then a double quote \\\"\"", list);
+        this.checkContains("loggerFqcn: \"" + AbstractLogger.class.getName() + "\"", list);
+        for (final Appender app : appenders.values()) {
+            this.rootLogger.addAppender(app);
+        }
+    }
+
+    /**
+     * Test case for MDC conversion pattern.
+     */
+    @Test
+    public void testLayout() throws Exception {
+        final Map<String, Appender> appenders = this.rootLogger.getAppenders();
+        for (final Appender appender : appenders.values()) {
+            this.rootLogger.removeAppender(appender);
+        }
+        final Configuration configuration = rootLogger.getContext().getConfiguration();
+        // set up appender
+        // Use [[ and ]] to test header and footer (instead of [ and ])
+        final AbstractJacksonLayout layout = YamlLayout.createLayout(configuration, true, true, "[[", "]]", null);
+        final ListAppender appender = new ListAppender("List", null, layout, true, false);
+        appender.start();
+
+        // set appender on root and set level to debug
+        this.rootLogger.addAppender(appender);
+        this.rootLogger.setLevel(Level.DEBUG);
+
+        // output starting message
+        this.rootLogger.debug("starting mdc pattern test");
+
+        this.rootLogger.debug("empty mdc");
+
+        ThreadContext.put("key1", "value1");
+        ThreadContext.put("key2", "value2");
+
+        this.rootLogger.debug("filled mdc");
+
+        ThreadContext.remove("key1");
+        ThreadContext.remove("key2");
+
+        this.rootLogger.error("finished mdc pattern test", new NullPointerException("test"));
+
+        appender.stop();
+
+        final List<String> list = appender.getMessages();
+
+        this.checkAt("---", 0, list);
+        this.checkContains("loggerFqcn: \"" + AbstractLogger.class.getName() + "\"", list);
+        this.checkContains("level: \"DEBUG\"", list);
+        this.checkContains("message: \"starting mdc pattern test\"", list);
+        for (final Appender app : appenders.values()) {
+            this.rootLogger.addAppender(app);
+        }
+    }
+
+    @Test
+    public void testLayoutLoggerName() throws Exception {
+        final AbstractJacksonLayout layout = YamlLayout.createLayout(null, false, false, null, null,
+                StandardCharsets.UTF_8);
+        final Log4jLogEvent expected = Log4jLogEvent.newBuilder() //
+                .setLoggerName("a.B") //
+                .setLoggerFqcn("f.q.c.n") //
+                .setLevel(Level.DEBUG) //
+                .setMessage(new SimpleMessage("M")) //
+                .setThreadName("threadName") //
+                .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);
+        assertEquals(expected.getLoggerName(), actual.getLoggerName());
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    public void testLocationOffCompactOffMdcOff() throws Exception {
+        this.testAllFeatures(false, false, false, false);
+    }
+
+    @Test
+    public void testLocationOnCompactOffEventEolOffMdcOn() throws Exception {
+        this.testAllFeatures(true, false, false, true);
+    }
+
+    private String toPropertySeparator(final boolean compact, final boolean value) {
+        return value ? ": " : ":";
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/80a43988/log4j-web/src/main/java/org/apache/logging/log4j/web/ServletRequestThreadContext.java
----------------------------------------------------------------------
diff --git a/log4j-web/src/main/java/org/apache/logging/log4j/web/ServletRequestThreadContext.java b/log4j-web/src/main/java/org/apache/logging/log4j/web/ServletRequestThreadContext.java
new file mode 100644
index 0000000..6615531
--- /dev/null
+++ b/log4j-web/src/main/java/org/apache/logging/log4j/web/ServletRequestThreadContext.java
@@ -0,0 +1,29 @@
+package org.apache.logging.log4j.web;
+
+import java.util.Objects;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.logging.log4j.ThreadContext;
+
+public class ServletRequestThreadContext {
+
+    public static void put(String key, ServletRequest servletRequest) {
+        put(key, "RemoteAddr", servletRequest.getRemoteAddr());
+        put(key, "RemoteHost", servletRequest.getRemoteHost());
+        put(key, "RemotePort", servletRequest.getRemotePort());
+    }
+
+    public static void put(String key, String field, Object value) {
+        put(key + "." + field, Objects.toString(value));
+    }
+
+    public static void put(String key, String value) {
+        ThreadContext.put(key, value);
+    }
+
+    public static void put(String key, HttpServletRequest servletRequest) {
+        put(key, (ServletRequest) servletRequest);
+    }
+}