You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by gg...@apache.org on 2015/09/07 11:37:36 UTC

logging-log4j2 git commit: [LOG4J2-1088] Add Comma Separated Value (CSV) layouts for parameter and event logging.

Repository: logging-log4j2
Updated Branches:
  refs/heads/master ecdc012e1 -> 6cee32d8b


[LOG4J2-1088] Add Comma Separated Value (CSV) layouts for parameter and
event logging.

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

Branch: refs/heads/master
Commit: 6cee32d8b160e7b9f475fc0ff10d01a71bb2b78b
Parents: ecdc012
Author: ggregory <gg...@apache.org>
Authored: Mon Sep 7 02:37:32 2015 -0700
Committer: ggregory <gg...@apache.org>
Committed: Mon Sep 7 02:37:32 2015 -0700

----------------------------------------------------------------------
 .../log4j/message/ObjectArrayMessage.java       | 134 +++++++++++++++++
 .../log4j/message/ObjectArrayMessageTest.java   |  40 ++++++
 log4j-core/pom.xml                              |   6 +
 .../logging/log4j/core/layout/CsvLayout.java    |  97 +++++++++++++
 .../log4j/core/layout/CsvLogEventLayout.java    |  81 +++++++++++
 .../log4j/core/layout/CsvParameterLayout.java   |  88 ++++++++++++
 .../core/layout/CsvLogEventLayoutTest.java      | 136 ++++++++++++++++++
 .../core/layout/CsvParameterLayoutTest.java     | 143 +++++++++++++++++++
 pom.xml                                         |   6 +
 src/changes/changes.xml                         |   3 +
 src/site/xdoc/manual/layouts.xml.vm             | 123 ++++++++++++++++
 src/site/xdoc/runtime-dependencies.xml          |   4 +
 12 files changed, 861 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/6cee32d8/log4j-api/src/main/java/org/apache/logging/log4j/message/ObjectArrayMessage.java
----------------------------------------------------------------------
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ObjectArrayMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ObjectArrayMessage.java
new file mode 100644
index 0000000..729d9c5
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ObjectArrayMessage.java
@@ -0,0 +1,134 @@
+/*
+ * 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.message;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Arrays;
+
+/**
+ * Handles messages that contain an Object[].
+ * <p>
+ * Created for use with the CSV layout. For example:
+ * </p>
+ * <p>
+ * {@code logger.debug(new ObjectArrayMessage(1, 2, "Bob"));}
+ * </p>
+ * 
+ * @since 2.4
+ */
+public final class ObjectArrayMessage implements Message {
+
+    private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
+
+    private static final long serialVersionUID = -5903272448334166185L;
+
+    private transient Object[] array;
+    private transient String arrayString;
+
+    /**
+     * Creates the ObjectMessage.
+     * 
+     * @param obj
+     *            The Object to format.
+     */
+    public ObjectArrayMessage(final Object... obj) {
+        this.array = obj == null ? EMPTY_OBJECT_ARRAY : obj;
+    }
+
+    private boolean equalObjectsOrStrings(final Object[] left, final Object[] right) {
+        return left.equals(right) || String.valueOf(left).equals(String.valueOf(right));
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final ObjectArrayMessage that = (ObjectArrayMessage) o;
+        return array == null ? that.array == null : equalObjectsOrStrings(array, that.array);
+    }
+
+    /**
+     * Returns the object formatted using its toString method.
+     * 
+     * @return the String representation of the object.
+     */
+    @Override
+    public String getFormat() {
+        return getFormattedMessage();
+    }
+
+    /**
+     * Returns the formatted object message.
+     * 
+     * @return the formatted object message.
+     */
+    @Override
+    public String getFormattedMessage() {
+        // LOG4J2-763: cache formatted string in case obj changes later
+        if (arrayString == null) {
+            arrayString = Arrays.toString(array);
+        }
+        return arrayString;
+    }
+
+    /**
+     * Returns the object as if it were a parameter.
+     * 
+     * @return The object.
+     */
+    @Override
+    public Object[] getParameters() {
+        return array;
+    }
+
+    /**
+     * Returns null.
+     *
+     * @return null.
+     */
+    @Override
+    public Throwable getThrowable() {
+        return null;
+    }
+
+    @Override
+    public int hashCode() {
+        return array.hashCode();
+    }
+
+    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
+        in.defaultReadObject();
+        array = (Object[]) in.readObject();
+    }
+
+    @Override
+    public String toString() {
+        return "ObjectArrayMessage[obj=" + getFormattedMessage() + ']';
+    }
+
+    private void writeObject(final ObjectOutputStream out) throws IOException {
+        out.defaultWriteObject();
+        out.writeObject(array);
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/6cee32d8/log4j-api/src/test/java/org/apache/logging/log4j/message/ObjectArrayMessageTest.java
----------------------------------------------------------------------
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/ObjectArrayMessageTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/message/ObjectArrayMessageTest.java
new file mode 100644
index 0000000..5f11cf8
--- /dev/null
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/message/ObjectArrayMessageTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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.message;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * @since 2.4
+ */
+public class ObjectArrayMessageTest {
+    
+    private static final Object[] ARRAY = { "A", "B", "C" };
+    private static final ObjectArrayMessage OBJECT_ARRAY_MESSAGE = new ObjectArrayMessage(ARRAY);
+
+    @Test
+    public void testGetParameters() {
+        Assert.assertArrayEquals(ARRAY, OBJECT_ARRAY_MESSAGE.getParameters());
+    }
+
+    @Test
+    public void testGetThrowable() {
+        Assert.assertEquals(null, OBJECT_ARRAY_MESSAGE.getThrowable());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/6cee32d8/log4j-core/pom.xml
----------------------------------------------------------------------
diff --git a/log4j-core/pom.xml b/log4j-core/pom.xml
index 92a1023..1c00fbc 100644
--- a/log4j-core/pom.xml
+++ b/log4j-core/pom.xml
@@ -120,6 +120,12 @@
       <artifactId>commons-compress</artifactId>
       <optional>true</optional>
     </dependency>
+    <!-- Used for the CSV layout -->
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-csv</artifactId>
+      <optional>true</optional>
+    </dependency>
 
     <!-- TEST DEPENDENCIES -->
 

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/6cee32d8/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvLayout.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvLayout.java
new file mode 100644
index 0000000..67da734
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvLayout.java
@@ -0,0 +1,97 @@
+package org.apache.logging.log4j.core.layout;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVPrinter;
+import org.apache.commons.csv.QuoteMode;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.Node;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.status.StatusLogger;
+
+/**
+ * A Comma-Separated Value (CSV) layout.
+ * 
+ * <p>
+ * Best used with:
+ * </p>
+ * <p>
+ * {@code logger.debug(new ObjectArrayMessage(1, 2, "Bob"));}
+ * </p>
+ * 
+ * Depends on Apache Commons CSV 1.2.
+ * 
+ * @since 2.4
+ */
+@Plugin(name = "CsvLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
+public class CsvLayout extends AbstractStringLayout {
+
+    private static final String CONTENT_TYPE = "text/csv";
+    protected static final String DEFAULT_CHARSET = "UTF-8";
+    protected static final String DEFAULT_FORMAT = "Default";
+    private static final long serialVersionUID = 1L;
+
+    protected static CSVFormat createFormat(final String format, final Character delimiter, final Character escape, final Character quote, final QuoteMode quoteMode, final String nullString,
+            final String recordSeparator) {
+                CSVFormat csvFormat = CSVFormat.valueOf(format);
+                if (delimiter != null) {
+                    csvFormat = csvFormat.withDelimiter(delimiter);
+                }
+                if (escape != null) {
+                    csvFormat = csvFormat.withEscape(escape);
+                }
+                if (quote != null) {
+                    csvFormat = csvFormat.withQuote(quote);
+                }
+                if (quoteMode != null) {
+                    csvFormat = csvFormat.withQuoteMode(quoteMode);
+                }
+                if (nullString != null) {
+                    csvFormat = csvFormat.withNullString(nullString);
+                }
+                if (recordSeparator != null) {
+                    csvFormat = csvFormat.withRecordSeparator(recordSeparator);
+                }
+                return csvFormat;
+            }
+
+    private final CSVFormat format;
+
+    protected CsvLayout(final Charset charset, final CSVFormat csvFormat, final String header,
+            final String footer) {
+        super(charset, toBytes(header, charset), toBytes(footer, charset));
+        this.format = csvFormat;
+    }
+
+    @Override
+    public String getContentType() {
+        return CONTENT_TYPE + "; charset=" + this.getCharset();
+    }
+
+    public CSVFormat getFormat() {
+        return format;
+    }
+
+    @Override
+    public String toSerializable(final LogEvent event) {
+        final Message message = event.getMessage();
+        final Object[] parameters = message.getParameters();
+        final StringBuilder buffer = new StringBuilder(1024);
+        try {
+            // Revisit when 1.3 is out so that we do not need to create a new
+            // printer for each event.
+            // No need to close the printer.
+            final CSVPrinter printer = new CSVPrinter(buffer, getFormat());
+            printer.printRecord(parameters);
+            return buffer.toString();
+        } catch (final IOException e) {
+            StatusLogger.getLogger().error(message, e);
+            return getFormat().getCommentMarker() + " " + e;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/6cee32d8/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvLogEventLayout.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvLogEventLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvLogEventLayout.java
new file mode 100644
index 0000000..7be65d6
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvLogEventLayout.java
@@ -0,0 +1,81 @@
+package org.apache.logging.log4j.core.layout;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVPrinter;
+import org.apache.commons.csv.QuoteMode;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.status.StatusLogger;
+
+public class CsvLogEventLayout extends CsvLayout {
+
+    /**
+     * 
+     */
+    private static final long serialVersionUID = 1L;
+
+    public static CsvLogEventLayout createDefaultLayout() {
+        return new CsvLogEventLayout(Charset.forName(DEFAULT_CHARSET), CSVFormat.valueOf(DEFAULT_FORMAT), null, null);
+    }
+
+    public static CsvLogEventLayout createLayout(final CSVFormat format) {
+        return new CsvLogEventLayout(Charset.forName(DEFAULT_CHARSET), format, null, null);
+    }
+
+    @PluginFactory
+    public static CsvLogEventLayout createLayout(
+            // @formatter:off
+            @PluginAttribute(value = "format", defaultString = DEFAULT_FORMAT) final String format,
+            @PluginAttribute("delimiter") final Character delimiter,
+            @PluginAttribute("escape") final Character escape,
+            @PluginAttribute("quote") final Character quote,
+            @PluginAttribute("quoteMode") final QuoteMode quoteMode,
+            @PluginAttribute("nullString") final String nullString,
+            @PluginAttribute("recordSeparator") final String recordSeparator,
+            @PluginAttribute(value = "charset", defaultString = DEFAULT_CHARSET) final Charset charset,
+            @PluginAttribute("header") final String header,
+            @PluginAttribute("footer") final String footer)
+            // @formatter:on
+    {
+
+        final CSVFormat csvFormat = createFormat(format, delimiter, escape, quote, quoteMode, nullString, recordSeparator);
+        return new CsvLogEventLayout(charset, csvFormat, header, footer);
+    }
+   
+    protected CsvLogEventLayout(final Charset charset, final CSVFormat csvFormat, final String header, final String footer) {
+        super(charset, csvFormat, header, footer);
+    }
+
+    @Override
+    public String toSerializable(final LogEvent event) {
+        final StringBuilder buffer = new StringBuilder(1024);
+        try {
+            // Revisit when 1.3 is out so that we do not need to create a new
+            // printer for each event.
+            // No need to close the printer.
+            final CSVPrinter printer = new CSVPrinter(buffer, getFormat());
+            printer.print(event.getNanoTime());
+            printer.print(event.getTimeMillis());
+            printer.print(event.getLevel());
+            printer.print(event.getThreadName());
+            printer.print(event.getMessage().getFormattedMessage());
+            printer.print(event.getLoggerFqcn());
+            printer.print(event.getLoggerName());
+            printer.print(event.getMarker());
+            printer.print(event.getThrownProxy());
+            printer.print(event.getSource());
+            printer.print(event.getContextMap());
+            printer.print(event.getContextStack());
+            printer.println();
+            return buffer.toString();
+        } catch (final IOException e) {
+            StatusLogger.getLogger().error(event.toString(), e);
+            return getFormat().getCommentMarker() + " " + e;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/6cee32d8/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvParameterLayout.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvParameterLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvParameterLayout.java
new file mode 100644
index 0000000..09d1ef1
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvParameterLayout.java
@@ -0,0 +1,88 @@
+package org.apache.logging.log4j.core.layout;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVPrinter;
+import org.apache.commons.csv.QuoteMode;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+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.PluginFactory;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.status.StatusLogger;
+
+/**
+ * A Comma-Separated Value (CSV) layout to log event parameters.
+ * The event message is currently ignored. 
+ * 
+ * <p>
+ * Best used with:
+ * </p>
+ * <p>
+ * {@code logger.debug(new ObjectArrayMessage(1, 2, "Bob"));}
+ * </p>
+ * 
+ * Depends on Apache Commons CSV 1.2.
+ * 
+ * @since 2.4
+ */
+@Plugin(name = "CsvLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
+public class CsvParameterLayout extends CsvLayout {
+
+    private static final long serialVersionUID = 1L;
+
+    public static CsvLayout createDefaultLayout() {
+        return new CsvParameterLayout(Charset.forName(DEFAULT_CHARSET), CSVFormat.valueOf(DEFAULT_FORMAT), null, null);
+    }
+
+    public static CsvLayout createLayout(final CSVFormat format) {
+        return new CsvParameterLayout(Charset.forName(DEFAULT_CHARSET), format, null, null);
+    }
+
+    @PluginFactory
+    public static CsvLayout createLayout(
+            // @formatter:off
+            @PluginAttribute(value = "format", defaultString = DEFAULT_FORMAT) final String format,
+            @PluginAttribute("delimiter") final Character delimiter,
+            @PluginAttribute("escape") final Character escape,
+            @PluginAttribute("quote") final Character quote,
+            @PluginAttribute("quoteMode") final QuoteMode quoteMode,
+            @PluginAttribute("nullString") final String nullString,
+            @PluginAttribute("recordSeparator") final String recordSeparator,
+            @PluginAttribute(value = "charset", defaultString = DEFAULT_CHARSET) final Charset charset,
+            @PluginAttribute("header") final String header,
+            @PluginAttribute("footer") final String footer)
+            // @formatter:on
+    {
+
+        final CSVFormat csvFormat = createFormat(format, delimiter, escape, quote, quoteMode, nullString, recordSeparator);
+        return new CsvParameterLayout(charset, csvFormat, header, footer);
+    }
+
+    public CsvParameterLayout(final Charset charset, final CSVFormat csvFormat, final String header, final String footer) {
+        super(charset, csvFormat, header, footer);
+    }
+
+    @Override
+    public String toSerializable(final LogEvent event) {
+        final Message message = event.getMessage();
+        final Object[] parameters = message.getParameters();
+        final StringBuilder buffer = new StringBuilder(1024);
+        try {
+            // Revisit when 1.3 is out so that we do not need to create a new
+            // printer for each event.
+            // No need to close the printer.
+            final CSVPrinter printer = new CSVPrinter(buffer, getFormat());
+            printer.printRecord(parameters);
+            return buffer.toString();
+        } catch (final IOException e) {
+            StatusLogger.getLogger().error(message, e);
+            return getFormat().getCommentMarker() + " " + e;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/6cee32d8/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/CsvLogEventLayoutTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/CsvLogEventLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/CsvLogEventLayoutTest.java
new file mode 100644
index 0000000..4df936e
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/CsvLogEventLayoutTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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 java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.csv.CSVFormat;
+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.ConfigurationFactory;
+import org.apache.logging.log4j.test.appender.ListAppender;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Tests {@link CsvLayout}.
+ *
+ * @since 2.4
+ */
+public class CsvLogEventLayoutTest {
+    static ConfigurationFactory cf = new BasicConfigurationFactory();
+
+    @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();
+    }
+
+    private final LoggerContext ctx = LoggerContext.getContext();
+
+    private final Logger root = ctx.getLogger("");
+
+    @Test
+    public void testCustomCharset() {
+        final CsvLayout layout = CsvLogEventLayout.createLayout("Excel", null, null, null, null, null, null,
+                StandardCharsets.UTF_16, null, null);
+        assertEquals("text/csv; charset=UTF-16", layout.getContentType());
+    }
+
+    @Test
+    public void testDefaultCharset() {
+        final CsvLayout layout = CsvLogEventLayout.createDefaultLayout();
+        assertEquals(StandardCharsets.UTF_8, layout.getCharset());
+    }
+
+    @Test
+    public void testDefaultContentType() {
+        final CsvLayout layout = CsvLogEventLayout.createDefaultLayout();
+        assertEquals("text/csv; charset=UTF-8", layout.getContentType());
+    }
+
+    private void testLayout(final CSVFormat format) {
+        final CsvLayout layout = CsvLogEventLayout.createLayout(format);
+        final Map<String, Appender> appenders = root.getAppenders();
+        for (final Appender appender : appenders.values()) {
+            root.removeAppender(appender);
+        }
+        // set up appender
+        final ListAppender appender = new ListAppender("List", null, layout, true, false);
+        appender.start();
+
+        // set appender on root and set level to debug
+        root.addAppender(appender);
+        root.setLevel(Level.DEBUG);
+
+        root.debug("one={}, two={}, three={}", 1, 2, 3);
+        root.info("Hello");
+        appender.stop();
+
+        final List<String> list = appender.getMessages();
+        final String event0 = list.get(0);
+        final char del = format.getDelimiter();
+        Assert.assertTrue(event0, event0.contains(del + "DEBUG" + del));
+        final String quote = del == ',' ? "\"" : "";
+        Assert.assertTrue(event0, event0.contains(del + quote + "one=1, two=2, three=3" + quote + del));
+        final String event1 = list.get(1);
+        Assert.assertTrue(event1, event1.contains(del + "INFO" + del));
+    }
+
+    @Test
+    public void testLayoutDefault() throws Exception {
+        testLayout(CSVFormat.DEFAULT);
+    }
+
+    @Test
+    public void testLayoutExcel() throws Exception {
+        testLayout(CSVFormat.EXCEL);
+    }
+
+    @Test
+    public void testLayoutMySQL() throws Exception {
+        testLayout(CSVFormat.MYSQL);
+    }
+
+    @Test
+    public void testLayoutRFC4180() throws Exception {
+        testLayout(CSVFormat.RFC4180);
+    }
+
+    @Test
+    public void testLayoutTab() throws Exception {
+        testLayout(CSVFormat.TDF);
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/6cee32d8/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/CsvParameterLayoutTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/CsvParameterLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/CsvParameterLayoutTest.java
new file mode 100644
index 0000000..d9a04c5
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/CsvParameterLayoutTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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 java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.csv.CSVFormat;
+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.ConfigurationFactory;
+import org.apache.logging.log4j.message.ObjectArrayMessage;
+import org.apache.logging.log4j.test.appender.ListAppender;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Tests {@link CsvLayout}.
+ *
+ * @since 2.4
+ */
+public class CsvParameterLayoutTest {
+    static ConfigurationFactory cf = new BasicConfigurationFactory();
+
+    @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();
+    }
+
+    private final LoggerContext ctx = LoggerContext.getContext();
+
+    private final Logger root = ctx.getLogger("");
+
+    @Test
+    public void testCustomCharset() {
+        final CsvLayout layout = CsvParameterLayout.createLayout("Excel", null, null, null, null, null, null,
+                StandardCharsets.UTF_16, null, null);
+        assertEquals("text/csv; charset=UTF-16", layout.getContentType());
+    }
+
+    @Test
+    public void testDefaultCharset() {
+        final CsvLayout layout = CsvParameterLayout.createDefaultLayout();
+        assertEquals(StandardCharsets.UTF_8, layout.getCharset());
+    }
+
+    @Test
+    public void testDefaultContentType() {
+        final CsvLayout layout = CsvParameterLayout.createDefaultLayout();
+        assertEquals("text/csv; charset=UTF-8", layout.getContentType());
+    }
+
+    private void testLayoutNormalApi(final CsvLayout layout, boolean messageApi) throws Exception {
+        final Map<String, Appender> appenders = root.getAppenders();
+        for (final Appender appender : appenders.values()) {
+            root.removeAppender(appender);
+        }
+        // set up appender
+        final ListAppender appender = new ListAppender("List", null, layout, true, false);
+        appender.start();
+
+        // set appender on root and set level to debug
+        root.addAppender(appender);
+        root.setLevel(Level.DEBUG);
+
+        // output messages
+        if (messageApi) {
+            logDebugObjectArrayMessage();
+        } else {
+            logDebugNormalApi();
+        }
+
+        appender.stop();
+
+        final List<String> list = appender.getMessages();
+        final char d = layout.getFormat().getDelimiter();
+        Assert.assertEquals("1" + d + "2" + d + "3", list.get(0));
+        Assert.assertEquals("2" + d + "3", list.get(1));
+        Assert.assertEquals("5" + d + "6", list.get(2));
+        Assert.assertEquals("7" + d + "8" + d + "9" + d + "10", list.get(3));
+    }
+
+    private void logDebugNormalApi() {
+        root.debug(null, 1, 2, 3);
+        root.debug(null, 2, 3);
+        root.debug(null, 5, 6);
+        root.debug(null, 7, 8, 9, 10);
+    }
+
+    private void logDebugObjectArrayMessage() {
+        root.debug(new ObjectArrayMessage(1, 2, 3));
+        root.debug(new ObjectArrayMessage(2, 3));
+        root.debug(new ObjectArrayMessage(5, 6));
+        root.debug(new ObjectArrayMessage(7, 8, 9, 10));
+    }
+
+    @Test
+    public void testLayoutDefaultNormal() throws Exception {
+        testLayoutNormalApi(CsvParameterLayout.createDefaultLayout(), false);
+    }
+
+    @Test
+    public void testLayoutDefaultObjectArrayMessage() throws Exception {
+        testLayoutNormalApi(CsvParameterLayout.createDefaultLayout(), true);
+    }
+
+    @Test
+    public void testLayoutTab() throws Exception {
+        testLayoutNormalApi(CsvParameterLayout.createLayout(CSVFormat.TDF), true);
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/6cee32d8/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 9a74e24..019b089 100644
--- a/pom.xml
+++ b/pom.xml
@@ -692,6 +692,12 @@
         <artifactId>commons-compress</artifactId>
         <version>1.10</version>
       </dependency>
+      <!-- Used for the CSV layout -->
+      <dependency>
+        <groupId>org.apache.commons</groupId>
+        <artifactId>commons-csv</artifactId>
+        <version>1.2</version>
+      </dependency>
     </dependencies>
   </dependencyManagement>
   <build>

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/6cee32d8/src/changes/changes.xml
----------------------------------------------------------------------
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 1f37bc8..a11ea10 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -34,6 +34,9 @@
       <action issue="LOG4J2-1107" dev="ggregory" type="add" due-to="Mikael Ståldal">
         New Appender for Apache Kafka.
       </action>
+      <action issue="LOG4J2-1088" dev="ggregory" type="add" due-to="Gary Gregory">
+        Add Comma Separated Value (CSV) layouts for parameter and event logging.
+      </action>
       <action issue="LOG4J2-812" dev="rgoers" type="update">
         PatternLayout timestamp formatting performance improvement: replaced synchronized SimpleDateFormat with
         Apache Commons FastDateFormat. This and better caching resulted in a ~3-30X faster timestamp formatting.

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/6cee32d8/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 a04921d..0127bb2 100644
--- a/src/site/xdoc/manual/layouts.xml.vm
+++ b/src/site/xdoc/manual/layouts.xml.vm
@@ -49,6 +49,129 @@
           where the default there is UTF-8. Each layout that extends <code>org.apache.logging.log4j.core.layout.AbstractStringLayout</code> 
           can provide its own default. See each layout below.
         </p>
+        <a name="CSVLayouts"/>
+        <subsection name="CSV Layouts">
+          <p>
+            The CSV layout can be used in two ways: First, using <code>CsvParameterLayout</code> to log event parameters 
+            to create a custom database, usually to a logger and file appender uniquely configured for this purpose. 
+            Second, using <code>CsvLogEventLayout</code> to log events to create a database, as an alternative to using a 
+            full DBMS or using a JDBC driver that supports the CSV format.
+          </p>
+          <p>
+            The <code>CsvParameterLayout</code> converts an event's parameters into a CSV record, ignoring the message. 
+            To log CSV records, you can use the usual Logger methods <code>info()</code>, <code>debug()</code>, and so on:
+          </p>
+          <pre class="prettyprint linenums">
+logger.info("Ignored", value1, value2, value3);
+</pre>
+          <p>
+            Which will create the CSV record:
+          </p>            
+          <pre class="prettyprint linenums">
+value1, value2, value3
+</pre>            
+          <p>
+            Alternatively, you can use a <code>ObjectArrayMessage</code>, which only carries parameters:
+          </p>
+          <pre class="prettyprint linenums">
+logger.info(new ObjectArrayMessage(value1, value2, value3));
+</pre>
+          <p>
+            The layouts CsvParameterLayout and CsvLogEventLayout are configured with the following parameters:
+          </p>
+          <table>
+            <caption>CsvParameterLayout and CsvLogEventLayout</caption>
+            <tr>
+              <th>Parameter Name</th>
+              <th>Type</th>
+              <th>Description</th>
+            </tr>
+            <tr>
+              <td>format</td>
+              <td>String</td>
+              <td>
+                One of the predefined formats: <code>Default</code>, <code>Excel</code>, <code>MySQL</code>, 
+                <code>RFC4180</code>, <code>TDF</code>.
+                See 
+                  <a href="https://commons.apache.org/proper/commons-csv/archives/1.2/apidocs/org/apache/commons/csv/CSVFormat.Predefined.html">CSVFormat.Predefined</a>.
+              </td>
+            </tr>
+            <tr>
+              <td>delimiter</td>
+              <td>Character</td>
+              <td>Sets the delimiter of the format to the specified character.</td>
+            </tr>
+            <tr>
+              <td>escape</td>
+              <td>Character</td>
+              <td>Sets the escape character of the format to the specified character.</td>
+            </tr>
+            <tr>
+              <td>quote</td>
+              <td>Character</td>
+              <td>Sets the quoteChar of the format to the specified character.</td>
+            </tr>
+            <tr>
+              <td>quoteMode</td>
+              <td>String</td>
+              <td>
+                Sets the output quote policy of the format to the specified value. One of: <code>ALL</code>, 
+                <code>MINIMAL</code>, <code>NON_NUMERIC</code>, <code>NONE</code>.
+              </td>
+            </tr>
+            <tr>
+              <td>nullString</td>
+              <td>String</td>
+              <td>Writes null as the given nullString when writing records.</td>
+            </tr>
+            <tr>
+              <td>recordSeparator</td>
+              <td>String</td>
+              <td>Sets the record separator of the format to the specified String.</td>
+            </tr>
+            <tr>
+              <td>charset</td>
+              <td>Charset</td>
+              <td>The output Charset.</td>
+            </tr>
+            <tr>
+              <td>header</td>
+              <td>Sets the header to include when the stream is opened.</td>
+              <td>Desc.</td>
+            </tr>
+            <tr>
+              <td>footer</td>
+              <td>Sets the footer to include when the stream is closed.</td>
+              <td>Desc.</td>
+            </tr>
+        </table>           
+        <p>
+          Logging as a CSV events looks like this:
+        </p>
+        <pre class="prettyprint linenums">
+logger.debug("one={}, two={}, three={}", 1, 2, 3); 
+</pre>                    
+        <p>
+          Produces a CSV record with the following fields:
+          <ol>
+            <li>Time Nanos</li>
+            <li>Time Millis</li>
+            <li>Level</li>
+            <li>Thread Name</li>
+            <li>Formatted Message</li>
+            <li>Logger FQCN</li>
+            <li>Logger Name</li>
+            <li>Marker</li>
+            <li>Thrown Proxy</li>
+            <li>Source</li>
+            <li>Context Map</li>
+            <li>Context Stack</li>
+          </ol>
+        </p>            
+        <pre class="prettyprint linenums">
+0,1441617184044,DEBUG,main,"one=1, two=2, three=3",org.apache.logging.log4j.spi.AbstractLogger,,,,org.apache.logging.log4j.core.layout.CsvLogEventLayoutTest.testLayout(CsvLogEventLayoutTest.java:98),{},[]
+</pre>                    
+        </subsection>
         <a name="JSONLayout"/>
         <subsection name="JSONLayout">
           <!-- From Javadoc of org.apache.logging.log4j.core.layout.JSONLayout -->

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/6cee32d8/src/site/xdoc/runtime-dependencies.xml
----------------------------------------------------------------------
diff --git a/src/site/xdoc/runtime-dependencies.xml b/src/site/xdoc/runtime-dependencies.xml
index af8447f..bbee62a 100644
--- a/src/site/xdoc/runtime-dependencies.xml
+++ b/src/site/xdoc/runtime-dependencies.xml
@@ -53,6 +53,10 @@
           <th>Requirements</th>
         </tr>
         <tr>
+          <td>CSV Layout</td>
+          <td><a href="https://commons.apache.org/proper/commons-csv/">Apache Commons CSV</a></td>
+        </tr>        
+        <tr>
           <td>XML configuration</td>
           <td>-</td>
         </tr>