ecs-logging-java
ecs-logging-java copied to clipboard
feature request: objectMessageAsJsonObject
Log4j2 ObjectMessage
s are automatically converted to json objects.
This makes the log statements fail, if the object cannot be converted to json or it generates invalid json.
I would propose a objectMessageAsJsonObject
config.
Putting it to false would just put the objects toString()
result into the message
field, as described in the documentation of log4j2.
e.g.
public class EcsLogTest {
static class A {
String getA() {
Objects.requireNonNull(null);
return null;
}
}
@Test
void test() {
LogManager.getLogger().info(new A());
}
}
produces:
{"@timestamp":"2022-05-16T12:06:08.284Z", "log.level": "INFO", , "ecs.version": "1.2.0","process.thread.name":"main","log.logger":"EcsLogTest"}
This patch would probably enough to implement this feature:
diff --git a/log4j2-ecs-layout/src/main/java/co/elastic/logging/log4j2/EcsLayout.java b/log4j2-ecs-layout/src/main/java/co/elastic/logging/log4j2/EcsLayout.java
index 1a99742..41937c7 100644
--- a/log4j2-ecs-layout/src/main/java/co/elastic/logging/log4j2/EcsLayout.java
+++ b/log4j2-ecs-layout/src/main/java/co/elastic/logging/log4j2/EcsLayout.java
@@ -79,9 +79,10 @@ public class EcsLayout extends AbstractStringLayout {
private final boolean includeOrigin;
private final PatternFormatter[] exceptionPatternFormatter;
private final ConcurrentMap<Class<? extends MultiformatMessage>, Boolean> supportsJson = new ConcurrentHashMap<Class<? extends MultiformatMessage>, Boolean>();
+ private final boolean objectMessageAsJsonObject;
private EcsLayout(Configuration config, String serviceName, String serviceVersion, String serviceEnvironment, String serviceNodeName, String eventDataset, boolean includeMarkers,
- KeyValuePair[] additionalFields, boolean includeOrigin, String exceptionPattern, boolean stackTraceAsArray) {
+ KeyValuePair[] additionalFields, boolean includeOrigin, String exceptionPattern, boolean stackTraceAsArray, boolean objectMessageAsJsonObject) {
super(config, UTF_8, null, null);
this.serviceName = serviceName;
this.serviceVersion = serviceVersion;
@@ -92,6 +93,7 @@ public class EcsLayout extends AbstractStringLayout {
this.includeOrigin = includeOrigin;
this.stackTraceAsArray = stackTraceAsArray;
this.additionalFields = additionalFields;
+ this.objectMessageAsJsonObject = objectMessageAsJsonObject;
fieldValuePatternFormatter = new PatternFormatter[additionalFields.length][];
for (int i = 0; i < additionalFields.length; i++) {
KeyValuePair additionalField = additionalFields[i];
@@ -253,7 +255,7 @@ public class EcsLayout extends AbstractStringLayout {
} else {
serializeSimpleMessage(builder, gcFree, message, thrown);
}
- } else if (JACKSON_SERIALIZER != null && message instanceof ObjectMessage) {
+ } else if (JACKSON_SERIALIZER != null && message instanceof ObjectMessage && objectMessageAsJsonObject) {
final StringBuilder jsonBuffer = EcsJsonSerializer.getMessageStringBuilder();
JACKSON_SERIALIZER.formatTo(jsonBuffer, (ObjectMessage) message);
addJson(builder, jsonBuffer);
@@ -377,6 +379,8 @@ public class EcsLayout extends AbstractStringLayout {
private KeyValuePair[] additionalFields = new KeyValuePair[]{};
@PluginBuilderAttribute("includeOrigin")
private boolean includeOrigin = false;
+ @PluginBuilderAttribute("objectMessageAsJsonObject")
+ private boolean objectMessageAsJsonObject = true;
Builder() {
}
@@ -428,6 +432,10 @@ public class EcsLayout extends AbstractStringLayout {
return exceptionPattern;
}
+ public boolean isObjectMessageAsJsonObject() {
+ return objectMessageAsJsonObject;
+ }
+
/**
* Additional fields to set on each log event.
*
@@ -483,11 +491,16 @@ public class EcsLayout extends AbstractStringLayout {
return this;
}
+ public EcsLayout.Builder setObjectMessageAsJsonObject(boolean objectMessageAsJsonObject) {
+ this.objectMessageAsJsonObject = objectMessageAsJsonObject;
+ return this;
+ }
+
@Override
public EcsLayout build() {
return new EcsLayout(getConfiguration(), serviceName, serviceVersion, serviceEnvironment, serviceNodeName,
EcsJsonSerializer.computeEventDataset(eventDataset, serviceName),
- includeMarkers, additionalFields, includeOrigin, exceptionPattern, stackTraceAsArray);
+ includeMarkers, additionalFields, includeOrigin, exceptionPattern, stackTraceAsArray, objectMessageAsJsonObject);
}
}
}
Another option is to bypass log4j to create object messages using a custom message factory and use -Dlog4j2.messageFactory=CustomMessageFactory
. But I think a configuration property is a nicer solution.
public class CustomMessageFactory extends AbstractMessageFactory {
@Override
public Message newMessage(String message, Object... params) {
return new ParameterizedMessage(message, params);
}
@Override
public Message newMessage(Object message) {
return new ParameterizedMessage("{}", message);
}
}