| /* |
| * 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 com.fasterxml.jackson.core.io.JsonStringEncoder; |
| import org.apache.commons.io.IOUtils; |
| import org.apache.logging.log4j.Level; |
| import org.apache.logging.log4j.ThreadContext; |
| import org.apache.logging.log4j.core.*; |
| import org.apache.logging.log4j.core.config.ConfigurationFactory; |
| import org.apache.logging.log4j.core.layout.GelfLayout.CompressionType; |
| import org.apache.logging.log4j.core.lookup.JavaLookup; |
| import org.apache.logging.log4j.core.util.KeyValuePair; |
| import org.apache.logging.log4j.core.util.NetUtils; |
| import org.apache.logging.log4j.junit.ThreadContextRule; |
| import org.apache.logging.log4j.test.appender.EncodingListAppender; |
| import org.apache.logging.log4j.test.appender.ListAppender; |
| import org.junit.AfterClass; |
| import org.junit.BeforeClass; |
| import org.junit.Rule; |
| import org.junit.Test; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.List; |
| import java.util.zip.GZIPInputStream; |
| import java.util.zip.InflaterInputStream; |
| |
| import static net.javacrumbs.jsonunit.JsonAssert.assertJsonEquals; |
| import static org.junit.Assert.assertEquals; |
| |
| public class GelfLayoutTest { |
| |
| 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 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"; |
| |
| @Rule |
| public final ThreadContextRule threadContextRule = new ThreadContextRule(); |
| |
| @AfterClass |
| public static void cleanupClass() { |
| ConfigurationFactory.removeConfigurationFactory(configFactory); |
| } |
| |
| @BeforeClass |
| public static void setupClass() { |
| ConfigurationFactory.setConfigurationFactory(configFactory); |
| final LoggerContext ctx = LoggerContext.getContext(); |
| ctx.reconfigure(); |
| } |
| |
| LoggerContext ctx = LoggerContext.getContext(); |
| |
| Logger root = ctx.getRootLogger(); |
| |
| private void testCompressedLayout(final CompressionType compressionType, final boolean includeStacktrace, |
| final boolean includeThreadContext, String host, final boolean includeNullDelimiter) throws IOException { |
| for (final Appender appender : root.getAppenders().values()) { |
| root.removeAppender(appender); |
| } |
| // set up appenders |
| final GelfLayout layout = GelfLayout.newBuilder() |
| .setConfiguration(ctx.getConfiguration()) |
| .setHost(host) |
| .setAdditionalFields(new KeyValuePair[] { |
| new KeyValuePair(KEY1, VALUE1), |
| new KeyValuePair(KEY2, "${java:runtime}"), }) |
| .setCompressionType(compressionType) |
| .setCompressionThreshold(1024) |
| .setIncludeStacktrace(includeStacktrace) |
| .setIncludeThreadContext(includeThreadContext) |
| .setIncludeNullDelimiter(includeNullDelimiter) |
| .build(); |
| final ListAppender eventAppender = new ListAppender("Events", null, null, true, false); |
| final ListAppender rawAppender = new ListAppender("Raw", null, layout, true, true); |
| final ListAppender formattedAppender = new ListAppender("Formatted", null, layout, true, false); |
| final EncodingListAppender encodedAppender = new EncodingListAppender("Encoded", null, layout, false, true); |
| eventAppender.start(); |
| rawAppender.start(); |
| formattedAppender.start(); |
| encodedAppender.start(); |
| |
| if (host == null) { |
| host = NetUtils.getLocalHostname(); |
| } |
| |
| final JavaLookup javaLookup = new JavaLookup(); |
| |
| // set appenders on root and set level to debug |
| root.addAppender(eventAppender); |
| root.addAppender(rawAppender); |
| root.addAppender(formattedAppender); |
| root.addAppender(encodedAppender); |
| root.setLevel(Level.DEBUG); |
| |
| root.debug(LINE1); |
| |
| ThreadContext.put(MDCKEY1, MDCVALUE1); |
| ThreadContext.put(MDCKEY2, MDCVALUE2); |
| |
| root.info(LINE2); |
| |
| final Exception exception = new RuntimeException("some error"); |
| root.error(LINE3, exception); |
| |
| formattedAppender.stop(); |
| |
| final List<LogEvent> events = eventAppender.getEvents(); |
| final List<byte[]> raw = rawAppender.getData(); |
| final List<String> messages = formattedAppender.getMessages(); |
| final List<byte[]> raw2 = encodedAppender.getData(); |
| final String threadName = Thread.currentThread().getName(); |
| |
| //@formatter:off |
| assertJsonEquals("{" + |
| "\"version\": \"1.1\"," + |
| "\"host\": \"" + host + "\"," + |
| "\"timestamp\": " + GelfLayout.formatTimestamp(events.get(0).getTimeMillis()) + "," + |
| "\"level\": 7," + |
| "\"_thread\": \"" + threadName + "\"," + |
| "\"_logger\": \"\"," + |
| "\"short_message\": \"" + LINE1 + "\"," + |
| "\"_" + KEY1 + "\": \"" + VALUE1 + "\"," + |
| "\"_" + KEY2 + "\": \"" + javaLookup.getRuntime() + "\"" + |
| "}", |
| messages.get(0)); |
| |
| assertJsonEquals("{" + |
| "\"version\": \"1.1\"," + |
| "\"host\": \"" + host + "\"," + |
| "\"timestamp\": " + GelfLayout.formatTimestamp(events.get(1).getTimeMillis()) + "," + |
| "\"level\": 6," + |
| "\"_thread\": \"" + threadName + "\"," + |
| "\"_logger\": \"\"," + |
| "\"short_message\": \"" + LINE2 + "\"," + |
| (includeThreadContext ? |
| "\"_" + MDCKEY1 + "\": \"" + MDCVALUE1 + "\"," + |
| "\"_" + MDCKEY2 + "\": \"" + MDCVALUE2 + "\"," |
| : |
| "") + |
| "\"_" + KEY1 + "\": \"" + VALUE1 + "\"," + |
| "\"_" + KEY2 + "\": \"" + javaLookup.getRuntime() + "\"" + |
| "}", |
| messages.get(1)); |
| //@formatter:on |
| final byte[] compressed = raw.get(2); |
| final byte[] compressed2 = raw2.get(2); |
| final ByteArrayInputStream bais = new ByteArrayInputStream(compressed); |
| final ByteArrayInputStream bais2 = new ByteArrayInputStream(compressed2); |
| InputStream inflaterStream; |
| InputStream inflaterStream2; |
| switch (compressionType) { |
| case GZIP: |
| inflaterStream = new GZIPInputStream(bais); |
| inflaterStream2 = new GZIPInputStream(bais2); |
| break; |
| case ZLIB: |
| inflaterStream = new InflaterInputStream(bais); |
| inflaterStream2 = new InflaterInputStream(bais2); |
| break; |
| case OFF: |
| inflaterStream = bais; |
| inflaterStream2 = bais2; |
| break; |
| default: |
| throw new IllegalStateException("Missing test case clause"); |
| } |
| final byte[] uncompressed = IOUtils.toByteArray(inflaterStream); |
| final byte[] uncompressed2 = IOUtils.toByteArray(inflaterStream2); |
| inflaterStream.close(); |
| inflaterStream2.close(); |
| final String uncompressedString = new String(uncompressed, layout.getCharset()); |
| final String uncompressedString2 = new String(uncompressed2, layout.getCharset()); |
| //@formatter:off |
| final String expected = "{" + |
| "\"version\": \"1.1\"," + |
| "\"host\": \"" + host + "\"," + |
| "\"timestamp\": " + GelfLayout.formatTimestamp(events.get(2).getTimeMillis()) + "," + |
| "\"level\": 3," + |
| "\"_thread\": \"" + threadName + "\"," + |
| "\"_logger\": \"\"," + |
| "\"short_message\": \"" + LINE3 + "\"," + |
| "\"full_message\": \"" + String.valueOf(JsonStringEncoder.getInstance().quoteAsString( |
| includeStacktrace ? GelfLayout.formatThrowable(exception).toString() : exception.toString())) + "\"," + |
| (includeThreadContext ? |
| "\"_" + MDCKEY1 + "\": \"" + MDCVALUE1 + "\"," + |
| "\"_" + MDCKEY2 + "\": \"" + MDCVALUE2 + "\"," |
| : "") + |
| "\"_" + KEY1 + "\": \"" + VALUE1 + "\"," + |
| "\"_" + KEY2 + "\": \"" + javaLookup.getRuntime() + "\"" + |
| "}"; |
| //@formatter:on |
| assertJsonEquals(expected, uncompressedString); |
| assertJsonEquals(expected, uncompressedString2); |
| } |
| |
| @Test |
| public void testLayoutGzipCompression() throws Exception { |
| testCompressedLayout(CompressionType.GZIP, true, true, HOSTNAME, false); |
| } |
| |
| @Test |
| public void testLayoutNoCompression() throws Exception { |
| testCompressedLayout(CompressionType.OFF, true, true, HOSTNAME, false); |
| } |
| |
| @Test |
| public void testLayoutZlibCompression() throws Exception { |
| testCompressedLayout(CompressionType.ZLIB, true, true, HOSTNAME, false); |
| } |
| |
| @Test |
| public void testLayoutNoStacktrace() throws Exception { |
| testCompressedLayout(CompressionType.OFF, false, true, HOSTNAME, false); |
| } |
| |
| @Test |
| public void testLayoutNoThreadContext() throws Exception { |
| testCompressedLayout(CompressionType.OFF, true, false, HOSTNAME, false); |
| } |
| |
| @Test |
| public void testLayoutNoHost() throws Exception { |
| testCompressedLayout(CompressionType.OFF, true, true, null, false); |
| } |
| |
| @Test |
| public void testLayoutNullDelimiter() throws Exception { |
| testCompressedLayout(CompressionType.OFF, false, true, HOSTNAME, true); |
| } |
| |
| @Test |
| public void testFormatTimestamp() { |
| assertEquals("0", GelfLayout.formatTimestamp(0L).toString()); |
| assertEquals("1.000", GelfLayout.formatTimestamp(1000L).toString()); |
| assertEquals("1.001", GelfLayout.formatTimestamp(1001L).toString()); |
| assertEquals("1.010", GelfLayout.formatTimestamp(1010L).toString()); |
| assertEquals("1.100", GelfLayout.formatTimestamp(1100L).toString()); |
| assertEquals("1458741206.653", GelfLayout.formatTimestamp(1458741206653L).toString()); |
| assertEquals("9223372036854775.807", GelfLayout.formatTimestamp(Long.MAX_VALUE).toString()); |
| } |
| |
| private void testRequiresLocation(String messagePattern, Boolean requiresLocation) { |
| GelfLayout layout = GelfLayout.newBuilder() |
| .setMessagePattern(messagePattern) |
| .build(); |
| |
| assertEquals(layout.requiresLocation(), requiresLocation); |
| } |
| |
| @Test |
| public void testRequiresLocationPatternNotSet() { |
| testRequiresLocation(null, false); |
| } |
| |
| @Test |
| public void testRequiresLocationPatternNotContainsLocation() { |
| testRequiresLocation("%m %n", false); |
| } |
| |
| @Test |
| public void testRequiresLocationPatternContainsLocation() { |
| testRequiresLocation("%C %m %t", true); |
| } |
| } |