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 2014/09/15 14:34:20 UTC

git commit: [LOG4J2-428] Implement a GELF layout. Tests GZIP. Use Jackson for JSON encoding instead of custom code.

Repository: logging-log4j2
Updated Branches:
  refs/heads/master 82d43d09b -> 837e0aa18


[LOG4J2-428] Implement a GELF layout. Tests GZIP. Use Jackson for JSON
encoding instead of custom code.

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

Branch: refs/heads/master
Commit: 837e0aa18f73f1956f7cb2f901a2fbe960501c61
Parents: 82d43d0
Author: Gary Gregory <ga...@gmail.com>
Authored: Mon Sep 15 08:34:16 2014 -0400
Committer: Gary Gregory <ga...@gmail.com>
Committed: Mon Sep 15 08:34:16 2014 -0400

----------------------------------------------------------------------
 log4j-core/pom.xml                              |   5 +
 .../logging/log4j/core/layout/GelfLayout.java   |  69 +++++-----
 .../log4j/core/layout/GelfLayoutTest.java       | 138 ++++++++++++-------
 pom.xml                                         |   6 +
 4 files changed, 136 insertions(+), 82 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/837e0aa1/log4j-core/pom.xml
----------------------------------------------------------------------
diff --git a/log4j-core/pom.xml b/log4j-core/pom.xml
index c10e8bc..88cfffd 100644
--- a/log4j-core/pom.xml
+++ b/log4j-core/pom.xml
@@ -220,6 +220,11 @@
       <artifactId>json-unit</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
+      <scope>test</scope>
+    </dependency>
     <!-- Other -->
     <dependency>
       <groupId>commons-codec</groupId>

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/837e0aa1/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java
index b85cbcf..c2d9c99 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java
@@ -37,6 +37,8 @@ import org.apache.logging.log4j.core.util.Charsets;
 import org.apache.logging.log4j.core.util.KeyValuePair;
 import org.apache.logging.log4j.status.StatusLogger;
 
+import com.fasterxml.jackson.core.io.JsonStringEncoder;
+
 /**
  * Lays out Graylog Extended Log Format (GELF) 1.1 log events.
  * <p>
@@ -65,12 +67,12 @@ import org.apache.logging.log4j.status.StatusLogger;
 public final class GelfLayout extends AbstractStringLayout {
 
     public static enum CompressionType {
-        GZIP, ZLIB, NONE
+        GZIP, NONE, ZLIB
     }
 
-    private static final byte[] EMPTY_BYTES = new byte[0];
     private static final char C = ',';
     private static final int COMPRESSION_THRESHOLD = 1024;
+    private static final byte[] EMPTY_BYTES = new byte[0];
     private static final char Q = '\"';
     private static final String QC = "\",";
     private static final String QU = "\"_";
@@ -90,19 +92,35 @@ public final class GelfLayout extends AbstractStringLayout {
         return new GelfLayout(host, additionalFields, compressionType, compressionThreshold);
     }
 
+    /**
+     * http://en.wikipedia.org/wiki/Syslog#Severity_levels
+     */
+    static int formatLevel(final Level level) {
+        return Severity.getSeverity(level).getCode();
+    }
+
+    static String formatThrowable(final Throwable throwable) {
+        // stack traces are big enough to provide a reasonably large initial capacity here
+        final StringWriter sw = new StringWriter(2048);
+        final PrintWriter pw = new PrintWriter(sw);
+        throwable.printStackTrace(pw);
+        pw.flush();
+        return sw.toString();
+    }
+
     static String formatTimestamp(final long timeMillis) {
         return new BigDecimal(timeMillis).divide(TIME_DIVISOR).toPlainString();
     }
 
     private final KeyValuePair[] additionalFields;
 
-    private final String host;
-
     private final int compressionThreshold;
 
-    private CompressionType compressionType;
+    private final CompressionType compressionType;
 
-    public GelfLayout(final String host, final KeyValuePair[] additionalFields, CompressionType compressionType,
+    private final String host;
+
+    public GelfLayout(final String host, final KeyValuePair[] additionalFields, final CompressionType compressionType,
             final int compressionThreshold) {
         super(Charsets.UTF_8);
         this.host = host;
@@ -135,24 +153,6 @@ public final class GelfLayout extends AbstractStringLayout {
         }
     }
 
-    private String escapeJson(final String s) {
-        return s.replace("\\", "\\\\").replace("\"", "\\\"");
-    }
-
-    /**
-     * http://en.wikipedia.org/wiki/Syslog#Severity_levels
-     */
-    private int formatLevel(final Level level) {
-        return Severity.getSeverity(level).getCode();
-    }
-
-    private String formatThrowable(final Throwable throwable) {
-        final StringWriter sw = new StringWriter();
-        final PrintWriter pw = new PrintWriter(sw);
-        throwable.printStackTrace(pw);
-        return sw.toString();
-    }
-
     @Override
     public Map<String, String> getContentFormat() {
         return Collections.emptyMap();
@@ -172,31 +172,34 @@ public final class GelfLayout extends AbstractStringLayout {
     @Override
     public String toSerializable(final LogEvent event) {
         final StringBuilder builder = new StringBuilder(256);
+        JsonStringEncoder jsonEncoder = JsonStringEncoder.getInstance();
         builder.append('{');
         builder.append("\"version\":\"1.1\",");
-        builder.append("\"host\":\"").append(escapeJson(host)).append(QC);
+        builder.append("\"host\":\"").append(jsonEncoder.quoteAsString(host)).append(QC);
         builder.append("\"timestamp\":").append(formatTimestamp(event.getTimeMillis())).append(C);
         builder.append("\"level\":").append(formatLevel(event.getLevel())).append(C);
         if (event.getThreadName() != null) {
-            builder.append("\"_thread\":\"").append(escapeJson(event.getThreadName())).append(QC);
+            builder.append("\"_thread\":\"").append(jsonEncoder.quoteAsString(event.getThreadName())).append(QC);
         }
         if (event.getLoggerName() != null) {
-            builder.append("\"_logger\":\"").append(escapeJson(event.getLoggerName())).append(QC);
+            builder.append("\"_logger\":\"").append(jsonEncoder.quoteAsString(event.getLoggerName())).append(QC);
         }
 
         for (final KeyValuePair additionalField : additionalFields) {
-            builder.append(QU).append(escapeJson(additionalField.getKey())).append("\":\"")
-                    .append(escapeJson(additionalField.getValue())).append(QC);
+            builder.append(QU).append(jsonEncoder.quoteAsString(additionalField.getKey())).append("\":\"")
+                    .append(jsonEncoder.quoteAsString(additionalField.getValue())).append(QC);
         }
         for (final Map.Entry<String, String> entry : event.getContextMap().entrySet()) {
-            builder.append(QU).append(escapeJson(entry.getKey())).append("\":\"").append(escapeJson(entry.getValue()))
-                    .append(QC);
+            builder.append(QU).append(jsonEncoder.quoteAsString(entry.getKey())).append("\":\"")
+                    .append(jsonEncoder.quoteAsString(entry.getValue())).append(QC);
         }
         if (event.getThrown() != null) {
-            builder.append("\"full_message\":\"").append(escapeJson(formatThrowable(event.getThrown()))).append(QC);
+            builder.append("\"full_message\":\"").append(jsonEncoder.quoteAsString(formatThrowable(event.getThrown())))
+                    .append(QC);
         }
 
-        builder.append("\"short_message\":\"").append(escapeJson(event.getMessage().getFormattedMessage())).append(Q);
+        builder.append("\"short_message\":\"")
+                .append(jsonEncoder.quoteAsString(event.getMessage().getFormattedMessage())).append(Q);
         builder.append('}');
         return builder.toString();
     }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/837e0aa1/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest.java
index 207d1b6..0d8dac6 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest.java
@@ -16,10 +16,22 @@
  */
 package org.apache.logging.log4j.core.layout;
 
+import static net.javacrumbs.jsonunit.JsonAssert.assertJsonEquals;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.List;
+import java.util.zip.GZIPInputStream;
+
+import org.apache.commons.io.IOUtils;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.ThreadContext;
-import org.apache.logging.log4j.core.*;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.BasicConfigurationFactory;
+import org.apache.logging.log4j.core.LogEvent;
+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.core.layout.GelfLayout.CompressionType;
 import org.apache.logging.log4j.core.util.KeyValuePair;
@@ -28,28 +40,29 @@ import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-import java.util.List;
-
-import static net.javacrumbs.jsonunit.JsonAssert.assertJsonEquals;
+import com.fasterxml.jackson.core.io.JsonStringEncoder;
 
 public class GelfLayoutTest {
-    LoggerContext ctx = (LoggerContext) LogManager.getContext();
-    Logger root = ctx.getLogger("");
-
+    static ConfigurationFactory configFactory = new BasicConfigurationFactory();
     private static final String HOSTNAME = "TheHost";
+
     private static final String KEY1 = "Key1";
     private static final String KEY2 = "Key2";
-    private static final String VALUE1 = "Value1";
-    private static final String VALUE2 = "Value2";
+    private static final String LINE1 = "empty mdc";
+    private static final String LINE2 = "filled mdc";
+    private static final String LINE3 = "error message";
     private static final String MDCKEY1 = "MdcKey1";
     private static final String MDCKEY2 = "MdcKey2";
     private static final String MDCVALUE1 = "MdcValue1";
     private static final String MDCVALUE2 = "MdcValue2";
+    private static final String VALUE1 = "Value1";
+    private static final String VALUE2 = "Value2";
 
-    private static final String LINE1 = "empty mdc";
-    private static final String LINE2 = "filled mdc";
-
-    static ConfigurationFactory configFactory = new BasicConfigurationFactory();
+    @AfterClass
+    public static void cleanupClass() {
+        ConfigurationFactory.removeConfigurationFactory(configFactory);
+        ThreadContext.clearAll();
+    }
 
     @BeforeClass
     public static void setupClass() {
@@ -59,30 +72,31 @@ public class GelfLayoutTest {
         ctx.reconfigure();
     }
 
-    @AfterClass
-    public static void cleanupClass() {
-        ConfigurationFactory.removeConfigurationFactory(configFactory);
-        ThreadContext.clearAll();
-    }
+    LoggerContext ctx = (LoggerContext) LogManager.getContext();
+
+    Logger root = ctx.getLogger("");
 
     @Test
     public void testLayout() throws Exception {
         for (final Appender appender : root.getAppenders().values()) {
             root.removeAppender(appender);
         }
-        // set up appender
+        // set up appenders
         final GelfLayout layout = GelfLayout.createLayout(HOSTNAME, new KeyValuePair[] {
                 new KeyValuePair(KEY1, VALUE1),
                 new KeyValuePair(KEY2, VALUE2), }, CompressionType.GZIP, 1024);
         // ConsoleAppender appender = new ConsoleAppender("Console", layout);
         final ListAppender eventAppender = new ListAppender("Events", null, null, true, false);
-        final ListAppender appender = new ListAppender("Layouted", null, layout, true, false);
+        final ListAppender rawAppender = new ListAppender("Raw", null, layout, true, true);
+        final ListAppender formattedAppender = new ListAppender("Formatted", null, layout, true, false);
         eventAppender.start();
-        appender.start();
+        rawAppender.start();
+        formattedAppender.start();
 
-        // set appender on root and set level to debug
+        // set appenders on root and set level to debug
         root.addAppender(eventAppender);
-        root.addAppender(appender);
+        root.addAppender(rawAppender);
+        root.addAppender(formattedAppender);
         root.setLevel(Level.DEBUG);
 
         root.debug(LINE1);
@@ -92,41 +106,67 @@ public class GelfLayoutTest {
 
         root.info(LINE2);
 
+        final Exception exception = new RuntimeException("some error");
+        root.error(LINE3, exception);
+
         ThreadContext.clearMap();
 
-        appender.stop();
+        formattedAppender.stop();
 
         final List<LogEvent> events = eventAppender.getEvents();
-        final List<String> list = appender.getMessages();
+        final List<byte[]> raw = rawAppender.getData();
+        final List<String> messages = formattedAppender.getMessages();
 
         //@formatter:off
         assertJsonEquals("{" +
-                "\"version\": \"1.1\"," +
-                "\"host\": \"" + HOSTNAME + "\"," +
-                "\"timestamp\": "+GelfLayout.formatTimestamp(events.get(0).getTimeMillis())+"," +
-                "\"level\": 7," +
-                "\"_thread\": \"main\"," +
-                "\"_logger\": \"\"," +
-                "\"short_message\": \"" + LINE1 + "\"," +
-                "\"_" + KEY1 + "\": \"" + VALUE1 + "\"," +
-                "\"_" + KEY2 + "\": \"" + VALUE2 + "\"" +
-                "}",
-        list.get(0));
+                        "\"version\": \"1.1\"," +
+                        "\"host\": \"" + HOSTNAME + "\"," +
+                        "\"timestamp\": " + GelfLayout.formatTimestamp(events.get(0).getTimeMillis()) + "," +
+                        "\"level\": 7," +
+                        "\"_thread\": \"main\"," +
+                        "\"_logger\": \"\"," +
+                        "\"short_message\": \"" + LINE1 + "\"," +
+                        "\"_" + KEY1 + "\": \"" + VALUE1 + "\"," +
+                        "\"_" + KEY2 + "\": \"" + VALUE2 + "\"" +
+                        "}",
+                messages.get(0));
 
         assertJsonEquals("{" +
-                "\"version\": \"1.1\"," +
-                "\"host\": \"" + HOSTNAME + "\"," +
-                "\"timestamp\": "+GelfLayout.formatTimestamp(events.get(1).getTimeMillis())+"," +
-                "\"level\": 6," +
-                "\"_thread\": \"main\"," +
-                "\"_logger\": \"\"," +
-                "\"short_message\": \"" + LINE2 + "\"," +
-                "\"_" + KEY1 + "\": \"" + VALUE1 + "\"," +
-                "\"_" + KEY2 + "\": \"" + VALUE2 + "\"," +
-                "\"_" + MDCKEY1 + "\": \"" + MDCVALUE1 + "\"," +
-                "\"_" + MDCKEY2 + "\": \"" + MDCVALUE2 + "\"" +
-                "}",
-        list.get(1));
+                        "\"version\": \"1.1\"," +
+                        "\"host\": \"" + HOSTNAME + "\"," +
+                        "\"timestamp\": " + GelfLayout.formatTimestamp(events.get(1).getTimeMillis()) + "," +
+                        "\"level\": 6," +
+                        "\"_thread\": \"main\"," +
+                        "\"_logger\": \"\"," +
+                        "\"short_message\": \"" + LINE2 + "\"," +
+                        "\"_" + KEY1 + "\": \"" + VALUE1 + "\"," +
+                        "\"_" + KEY2 + "\": \"" + VALUE2 + "\"," +
+                        "\"_" + MDCKEY1 + "\": \"" + MDCVALUE1 + "\"," +
+                        "\"_" + MDCKEY2 + "\": \"" + MDCVALUE2 + "\"" +
+                        "}",
+                messages.get(1));
+
+        final byte[] compressed = raw.get(2);
+        final InputStream gzipStream = new GZIPInputStream(new ByteArrayInputStream(compressed));
+        final byte[] uncompressed = IOUtils.toByteArray(gzipStream);
+        gzipStream.close();
+        final String uncompressedString = new String(uncompressed, layout.getCharset());
+        assertJsonEquals("{" +
+                        "\"version\": \"1.1\"," +
+                        "\"host\": \"" + HOSTNAME + "\"," +
+                        "\"timestamp\": " + GelfLayout.formatTimestamp(events.get(2).getTimeMillis()) + "," +
+                        "\"level\": 3," +
+                        "\"_thread\": \"main\"," +
+                        "\"_logger\": \"\"," +
+                        "\"short_message\": \"" + LINE3 + "\"," +
+                        "\"full_message\": \"" + String.valueOf(JsonStringEncoder.getInstance().quoteAsString(
+                                GelfLayout.formatThrowable(exception))) + "\"," +
+                        "\"_" + KEY1 + "\": \"" + VALUE1 + "\"," +
+                        "\"_" + KEY2 + "\": \"" + VALUE2 + "\"," +
+                        "\"_" + MDCKEY1 + "\": \"" + MDCVALUE1 + "\"," +
+                        "\"_" + MDCKEY2 + "\": \"" + MDCVALUE2 + "\"" +
+                        "}",
+                uncompressedString);
         //@formatter:on
     }
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/837e0aa1/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 24781ca..5319e5a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -650,6 +650,12 @@
         <version>1.1.6</version>
         <scope>test</scope>
       </dependency>      
+      <dependency>
+        <groupId>commons-io</groupId>
+        <artifactId>commons-io</artifactId>
+        <version>2.4</version>
+        <scope>test</scope>
+      </dependency>
     </dependencies>
   </dependencyManagement>
   <build>