--- GelfLayout.java.orig 2016-12-06 15:54:35.292000000 +0200 +++ GelfLayout.java 2016-12-07 08:46:47.384000000 +0200 @@ -1,4 +1,4 @@ -/* +/* * 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. @@ -21,6 +21,8 @@ import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Map; @@ -28,13 +30,17 @@ import java.util.zip.GZIPOutputStream; import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; 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.PluginElement; import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.lookup.StrSubstitutor; import org.apache.logging.log4j.core.net.Severity; import org.apache.logging.log4j.core.util.JsonUtils; import org.apache.logging.log4j.core.util.KeyValuePair; @@ -105,15 +111,17 @@ private final CompressionType compressionType; private final String host; private final boolean includeStacktrace; + private final boolean includeThreadContext; public GelfLayout(final String host, final KeyValuePair[] additionalFields, final CompressionType compressionType, - final int compressionThreshold, final boolean includeStacktrace) { + final int compressionThreshold, final boolean includeStacktrace, final boolean includeThreadContext) { super(StandardCharsets.UTF_8); - this.host = host; + this.host = getHostName(host); this.additionalFields = additionalFields; this.compressionType = compressionType; this.compressionThreshold = compressionThreshold; this.includeStacktrace = includeStacktrace; + this.includeThreadContext = includeThreadContext; } @PluginFactory @@ -122,13 +130,14 @@ @PluginAttribute("host") final String host, @PluginElement("AdditionalField") final KeyValuePair[] additionalFields, @PluginAttribute(value = "compressionType", - defaultString = "GZIP") final CompressionType compressionType, + defaultString = "GZIP") final CompressionType compressionType, @PluginAttribute(value = "compressionThreshold", - defaultInt = COMPRESSION_THRESHOLD) final int compressionThreshold, + defaultInt = COMPRESSION_THRESHOLD) final int compressionThreshold, @PluginAttribute(value = "includeStacktrace", - defaultBoolean = true) final boolean includeStacktrace) { - // @formatter:on - return new GelfLayout(host, additionalFields, compressionType, compressionThreshold, includeStacktrace); + defaultBoolean = true) final boolean includeStacktrace, + @PluginAttribute(value = "includeThreadContext", defaultBoolean = true) boolean includeThreadContext) { + // @formatter:on + return new GelfLayout(host, additionalFields, compressionType, compressionThreshold, includeStacktrace, includeThreadContext); } @Override @@ -183,9 +192,7 @@ } private StringBuilder toText(final LogEvent event, final StringBuilder builder, final boolean gcFree) { - builder.append('{'); - builder.append("\"version\":\"1.1\","); - builder.append("\"host\":\""); + builder.append("{\"version\":\"1.1\",\"host\":\""); JsonUtils.quoteAsString(toNullSafeString(host), builder); builder.append(QC); builder.append("\"timestamp\":").append(formatTimestamp(event.getTimeMillis())).append(C); @@ -201,19 +208,17 @@ builder.append(QC); } - for (final KeyValuePair additionalField : additionalFields) { - builder.append(QU); - JsonUtils.quoteAsString(additionalField.getKey(), builder); - builder.append("\":\""); - JsonUtils.quoteAsString(toNullSafeString(additionalField.getValue()), builder); - builder.append(QC); + if (additionalFields.length > 0) { + additionalFieldsToText(event, builder); } - for (final Map.Entry entry : event.getContextMap().entrySet()) { - builder.append(QU); - JsonUtils.quoteAsString(entry.getKey(), builder); - builder.append("\":\""); - JsonUtils.quoteAsString(toNullSafeString(entry.getValue()), builder); - builder.append(QC); + if (includeThreadContext) { + for (final Map.Entry entry : event.getContextMap().entrySet()) { + builder.append(QU); + JsonUtils.quoteAsString(entry.getKey(), builder); + builder.append("\":\""); + JsonUtils.quoteAsString(toNullSafeString(entry.getValue()), builder); + builder.append(QC); + } } if (event.getThrown() != null) { builder.append("\"full_message\":\""); @@ -245,6 +250,23 @@ return builder; } + private void additionalFieldsToText(final LogEvent event, final StringBuilder builder) { + // somehow the local configuration member on the layout is always null + Configuration config = configuration; + if (config == null) { + config = ((LoggerContext) LogManager.getContext(false)).getConfiguration(); + } + final StrSubstitutor strSubstitutor = config.getStrSubstitutor(); + for (final KeyValuePair additionalField : additionalFields) { + builder.append(QU); + JsonUtils.quoteAsString(additionalField.getKey(), builder); + builder.append("\":\""); + final String value = strSubstitutor.replace(event, additionalField.getValue()); + JsonUtils.quoteAsString(toNullSafeString(value), builder); + builder.append(QC); + } + } + private static final ThreadLocal messageStringBuilder = new ThreadLocal<>(); private static StringBuilder getMessageStringBuilder() { @@ -257,7 +279,7 @@ return result; } - private CharSequence toNullSafeString(final CharSequence s) { + private static CharSequence toNullSafeString(final CharSequence s) { return s == null ? Strings.EMPTY : s; } @@ -289,7 +311,7 @@ /** * http://en.wikipedia.org/wiki/Syslog#Severity_levels */ - private int formatLevel(final Level level) { + private static int formatLevel(final Level level) { return Severity.getSeverity(level).getCode(); } @@ -304,4 +326,24 @@ pw.flush(); return sw.getBuffer(); } + + /** + * Resolves the HOST attribute in case it is not provided to the local host name. + * + * @param host + * @return + */ + private static String getHostName(final String host) { + if (host != null) { + return host; + } + + try { + return InetAddress.getLocalHost().getHostName(); + } + catch (final UnknownHostException e) { + StatusLogger.getLogger().warn("Could not detect localhost name, falling back to \"localhost\""); + return "localhost"; + } + } }