Print stacktrace with Log4j2 in JSON with JSONLayout in a single line
I solved this using a custom Log4j layout:
@Plugin(name = "FlatJsonLayout", category = "Core", elementType = "layout", printObject = true)public class FlatJsonLayout extends AbstractStringLayout { private final ObjectMapper objectMapper; FlatJsonLayout(Charset charset) { super(charset); SimpleModule module = new SimpleModule(); module.addSerializer(LogEvent.class, new LogEventSerializer()); module.addSerializer(Throwable.class, new ThrowableSerializer()); module.addSerializer(ReadOnlyStringMap.class, new ContextDataSerializer() { }); objectMapper = new ObjectMapper(); objectMapper.registerModule(module); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); } @Override public String toSerializable(LogEvent logEvent) { try { return objectMapper.writeValueAsString(logEvent); } catch (JsonProcessingException e) { throw new IllegalStateException(e); } } private static class LogEventSerializer extends StdSerializer<LogEvent> { LogEventSerializer() { super(LogEvent.class); } @Override public void serialize(LogEvent value, JsonGenerator gen, SerializerProvider provider) throws IOException { gen.writeStartObject(); gen.writeNumberField("timeMillis", value.getTimeMillis()); gen.writeStringField("threadName", value.getThreadName()); gen.writeStringField("level", value.getLevel().name()); gen.writeStringField("loggerName", value.getLoggerName()); gen.writeObjectField("marker", value.getMarker()); gen.writeObjectField("message", value.getMessage().getFormattedMessage()); gen.writeObjectField("thrown", value.getThrown()); gen.writeObjectField("ContextMap", value.getContextData()); gen.writeObjectField("ContextStack", value.getContextStack()); gen.writeStringField("loggerFQCN", value.getLoggerFqcn()); gen.writeObjectField("Source", value.getSource()); gen.writeBooleanField("endOfBatch", value.isEndOfBatch()); gen.writeEndObject(); } } private static class ThrowableSerializer extends StdSerializer<Throwable> { ThrowableSerializer() { super(Throwable.class); } @Override public void serialize(Throwable value, JsonGenerator gen, SerializerProvider provider) throws IOException { try (StringWriter stringWriter = new StringWriter()) { try (PrintWriter printWriter = new PrintWriter(stringWriter)) { value.printStackTrace(printWriter); gen.writeString(stringWriter.toString()); } } catch (IOException e) { throw new IllegalStateException(e); } } } @PluginBuilderFactory public static <B extends Builder<B>> B newBuilder() { return new Builder<B>().asBuilder(); } public static class Builder<B extends Builder<B>> extends org.apache.logging.log4j.core.layout.AbstractStringLayout.Builder<B> implements org.apache.logging.log4j.core.util.Builder<FlatJsonLayout> { Builder() { this.setCharset(StandardCharsets.UTF_8); } public FlatJsonLayout build() { return new FlatJsonLayout(this.getCharset()); } }}
The class has to be on the classpath of the application. It can then be configured in the log4j2.xml
like this:
<?xml version="1.0" encoding="UTF-8"?><Configuration status="info"> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <FlatJsonLayout/> </Console> </Appenders> <Loggers> <Root level="info"> <AppenderRef ref="Console"/> </Root> </Loggers></Configuration>