Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions log4j2-ecs-layout/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Log4j2 ECS Layout

The minimum required log4j2 version is 2.7.

## Step 1: add dependency

Latest version: [![Maven Central](https://img.shields.io/maven-central/v/co.elastic.logging/log4j2-ecs-layout.svg)](https://search.maven.org/search?q=g:co.elastic.logging%20AND%20a:log4j2-ecs-layout)
Expand Down
16 changes: 15 additions & 1 deletion log4j2-ecs-layout/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

<properties>
<parent.base.dir>${project.basedir}/..</parent.base.dir>
<version.log4j>2.14.0</version.log4j>
</properties>

<artifactId>log4j2-ecs-layout</artifactId>
Expand All @@ -30,12 +31,25 @@
<configuration>
<proc>only</proc>
<annotationProcessors>
<annotationProcessor>org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor</annotationProcessor>
<annotationProcessor>org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor
</annotationProcessor>
</annotationProcessors>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.2</version>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@
import co.elastic.logging.EcsJsonSerializer;
import co.elastic.logging.JsonUtils;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
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.PluginBuilderAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.layout.AbstractStringLayout;
import org.apache.logging.log4j.core.layout.ByteBufferDestination;
Expand All @@ -46,11 +48,10 @@
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.MultiformatMessage;
import org.apache.logging.log4j.message.ObjectMessage;
import org.apache.logging.log4j.util.MultiFormatStringBuilderFormattable;
import org.apache.logging.log4j.util.StringBuilderFormattable;
import org.apache.logging.log4j.util.TriConsumer;

import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
Expand All @@ -59,18 +60,9 @@
public class EcsLayout extends AbstractStringLayout {

public static final Charset UTF_8 = Charset.forName("UTF-8");
static final String[] JSON_FORMAT = {"JSON"};

private final TriConsumer<String, Object, StringBuilder> WRITE_MDC = new TriConsumer<String, Object, StringBuilder>() {
@Override
public void accept(final String key, final Object value, final StringBuilder stringBuilder) {
stringBuilder.append('\"');
JsonUtils.quoteAsString(key, stringBuilder);
stringBuilder.append("\":\"");
JsonUtils.quoteAsString(EcsJsonSerializer.toNullSafeString(String.valueOf(value)), stringBuilder);
stringBuilder.append("\",");
}
};
private static final ObjectMessageJacksonSerializer JACKSON_SERIALIZER = ObjectMessageJacksonSerializer.Resolver.resolve();
private static final MdcSerializer MDC_SERIALIZER = MdcSerializer.Resolver.resolve();
private static final MultiFormatHandler MULTI_FORMAT_HANDLER = MultiFormatHandler.Resolver.resolve();

private final KeyValuePair[] additionalFields;
private final PatternFormatter[][] fieldValuePatternFormatter;
Expand All @@ -80,7 +72,6 @@ public void accept(final String key, final Object value, final StringBuilder str
private final boolean includeMarkers;
private final boolean includeOrigin;
private final ConcurrentMap<Class<? extends MultiformatMessage>, Boolean> supportsJson = new ConcurrentHashMap<Class<? extends MultiformatMessage>, Boolean>();
private final ObjectMessageJacksonSerializer objectMessageJacksonSerializer = ObjectMessageJacksonSerializer.Resolver.INSTANCE.resolve();

private EcsLayout(Configuration config, String serviceName, String eventDataset, boolean includeMarkers, KeyValuePair[] additionalFields, boolean includeOrigin, boolean stackTraceAsArray) {
super(config, UTF_8, null, null);
Expand All @@ -103,7 +94,7 @@ private EcsLayout(Configuration config, String serviceName, String eventDataset,

@PluginBuilderFactory
public static EcsLayout.Builder newBuilder() {
return new EcsLayout.Builder().asBuilder();
return new EcsLayout.Builder();
}

private static boolean valueNeedsLookup(final String value) {
Expand Down Expand Up @@ -148,40 +139,38 @@ private StringBuilder toText(LogEvent event, StringBuilder builder, boolean gcFr

private void serializeAdditionalFieldsAndMDC(LogEvent event, StringBuilder builder) {
final int length = additionalFields.length;
if (!event.getContextData().isEmpty() || length > 0) {
if (length > 0) {
final StrSubstitutor strSubstitutor = getConfiguration().getStrSubstitutor();
for (int i = 0; i < length; i++) {
KeyValuePair additionalField = additionalFields[i];
PatternFormatter[] formatters = fieldValuePatternFormatter[i];
CharSequence value = null;
if (formatters != null) {
StringBuilder buffer = EcsJsonSerializer.getMessageStringBuilder();
formatPattern(event, formatters, buffer);
if (buffer.length() > 0) {
value = buffer;
}
} else if (valueNeedsLookup(additionalField.getValue())) {
StringBuilder lookupValue = EcsJsonSerializer.getMessageStringBuilder();
lookupValue.append(additionalField.getValue());
if (strSubstitutor.replaceIn(event, lookupValue)) {
value = lookupValue;
}
} else {
value = additionalField.getValue();
if (length > 0) {
final StrSubstitutor strSubstitutor = getConfiguration().getStrSubstitutor();
for (int i = 0; i < length; i++) {
KeyValuePair additionalField = additionalFields[i];
PatternFormatter[] formatters = fieldValuePatternFormatter[i];
CharSequence value = null;
if (formatters != null) {
StringBuilder buffer = EcsJsonSerializer.getMessageStringBuilder();
formatPattern(event, formatters, buffer);
if (buffer.length() > 0) {
value = buffer;
}

if (value != null) {
builder.append('\"');
JsonUtils.quoteAsString(additionalField.getKey(), builder);
builder.append("\":\"");
JsonUtils.quoteAsString(EcsJsonSerializer.toNullSafeString(value), builder);
builder.append("\",");
} else if (valueNeedsLookup(additionalField.getValue())) {
StringBuilder lookupValue = EcsJsonSerializer.getMessageStringBuilder();
lookupValue.append(additionalField.getValue());
if (strSubstitutor.replaceIn(event, lookupValue)) {
value = lookupValue;
}
} else {
value = additionalField.getValue();
}

if (value != null) {
builder.append('\"');
JsonUtils.quoteAsString(additionalField.getKey(), builder);
builder.append("\":\"");
JsonUtils.quoteAsString(EcsJsonSerializer.toNullSafeString(value), builder);
builder.append("\",");
}
}
event.getContextData().forEach(WRITE_MDC, builder);
}
MDC_SERIALIZER.serializeMdc(event, builder);
}

private static void formatPattern(LogEvent event, PatternFormatter[] formatters, StringBuilder buffer) {
Expand All @@ -192,7 +181,13 @@ private static void formatPattern(LogEvent event, PatternFormatter[] formatters,
}

private void serializeTags(LogEvent event, StringBuilder builder) {
List<String> contextStack = event.getContextStack().asList();
ThreadContext.ContextStack stack = event.getContextStack();
List<String> contextStack;
if (stack == null) {
contextStack = Collections.emptyList();
} else {
contextStack = stack.asList();
}
Marker marker = event.getMarker();
boolean hasTags = !contextStack.isEmpty() || (includeMarkers && marker != null);
if (hasTags) {
Expand Down Expand Up @@ -235,9 +230,9 @@ private void serializeMessage(StringBuilder builder, boolean gcFree, Message mes
} else {
serializeSimpleMessage(builder, gcFree, message, thrown);
}
} else if (objectMessageJacksonSerializer != null && message instanceof ObjectMessage) {
} else if (JACKSON_SERIALIZER != null && message instanceof ObjectMessage) {
final StringBuilder jsonBuffer = EcsJsonSerializer.getMessageStringBuilder();
objectMessageJacksonSerializer.formatTo(jsonBuffer, (ObjectMessage) message);
JACKSON_SERIALIZER.formatTo(jsonBuffer, (ObjectMessage) message);
addJson(builder, jsonBuffer);
} else {
serializeSimpleMessage(builder, gcFree, message, thrown);
Expand All @@ -246,11 +241,7 @@ private void serializeMessage(StringBuilder builder, boolean gcFree, Message mes

private static void serializeJsonMessage(StringBuilder builder, MultiformatMessage message) {
final StringBuilder messageBuffer = EcsJsonSerializer.getMessageStringBuilder();
if (message instanceof MultiFormatStringBuilderFormattable) {
((MultiFormatStringBuilderFormattable) message).formatTo(JSON_FORMAT, messageBuffer);
} else {
messageBuffer.append(message.getFormattedMessage(JSON_FORMAT));
}
MULTI_FORMAT_HANDLER.formatJsonTo(message, messageBuffer);
addJson(builder, messageBuffer);
}

Expand Down Expand Up @@ -283,20 +274,27 @@ private void serializeSimpleMessage(StringBuilder builder, boolean gcFree, Messa
((StringBuilderFormattable) message).formatTo(messageBuffer);
JsonUtils.quoteAsString(messageBuffer, builder);
} finally {
trimToMaxSize(messageBuffer);
trimToMaxSizeCopy(messageBuffer);
}
} else {
JsonUtils.quoteAsString(EcsJsonSerializer.toNullSafeString(message.getFormattedMessage()), builder);
}
builder.append("\", ");
}

static void trimToMaxSizeCopy(final StringBuilder stringBuilder) {
if (stringBuilder.length() > MAX_STRING_BUILDER_SIZE) {
stringBuilder.setLength(MAX_STRING_BUILDER_SIZE);
stringBuilder.trimToSize();
}
}

private static boolean isObject(StringBuilder messageBuffer) {
return messageBuffer.length() > 1 && messageBuffer.charAt(0) == '{' && messageBuffer.charAt(messageBuffer.length() -1) == '}';
return messageBuffer.length() > 1 && messageBuffer.charAt(0) == '{' && messageBuffer.charAt(messageBuffer.length() - 1) == '}';
}

private static boolean isString(StringBuilder messageBuffer) {
return messageBuffer.length() > 1 && messageBuffer.charAt(0) == '"' && messageBuffer.charAt(messageBuffer.length() -1) == '"';
return messageBuffer.length() > 1 && messageBuffer.charAt(0) == '"' && messageBuffer.charAt(messageBuffer.length() - 1) == '"';
}

private static void moveToRoot(StringBuilder messageBuffer) {
Expand All @@ -319,9 +317,10 @@ private boolean supportsJson(MultiformatMessage message) {
return supportsJson;
}

public static class Builder extends AbstractStringLayout.Builder<EcsLayout.Builder>
implements org.apache.logging.log4j.core.util.Builder<EcsLayout> {
public static class Builder implements org.apache.logging.log4j.core.util.Builder<EcsLayout> {

@PluginConfiguration
private Configuration configuration;
@PluginBuilderAttribute("serviceName")
private String serviceName;
@PluginBuilderAttribute("eventDataset")
Expand All @@ -336,8 +335,15 @@ public static class Builder extends AbstractStringLayout.Builder<EcsLayout.Build
private boolean includeOrigin = false;

Builder() {
super();
setCharset(UTF_8);
}

public Configuration getConfiguration() {
return configuration;
}

public EcsLayout.Builder setConfiguration(final Configuration configuration) {
this.configuration = configuration;
return this;
}

public KeyValuePair[] getAdditionalFields() {
Expand Down Expand Up @@ -367,32 +373,32 @@ public boolean isIncludeOrigin() {
*/
public EcsLayout.Builder setAdditionalFields(final KeyValuePair[] additionalFields) {
this.additionalFields = additionalFields.clone();
return asBuilder();
return this;
}

public EcsLayout.Builder setServiceName(final String serviceName) {
this.serviceName = serviceName;
return asBuilder();
return this;
}

public EcsLayout.Builder setEventDataset(String eventDataset) {
this.eventDataset = eventDataset;
return asBuilder();
return this;
}

public EcsLayout.Builder setIncludeMarkers(final boolean includeMarkers) {
this.includeMarkers = includeMarkers;
return asBuilder();
return this;
}

public EcsLayout.Builder setIncludeOrigin(final boolean includeOrigin) {
this.includeOrigin = includeOrigin;
return asBuilder();
return this;
}

public EcsLayout.Builder setStackTraceAsArray(boolean stackTraceAsArray) {
this.stackTraceAsArray = stackTraceAsArray;
return asBuilder();
return this;
}

@Override
Expand Down
Loading