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>