jsonFactoryDecorator appears to be ignored by LoggingEventCompositeJsonEncoder
joca-bt opened this issue · 1 comments
joca-bt commented
I am trying to use jsonFactoryDecorator
with LoggingEventCompositeJsonEncoder
through XML configuration but the decorator appears to be ignored. I defined a custom JsonFactoryDecorator
that makes null fields not be serialized. However, it doesn't seem to be running as fields with null are still serialized. When I define all my configuration through code with no xml, it works fine.
XML + Java:
elk-logger.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="name" value="elk"/>
<property name="file" value="elk.log"/>
<logger name="${name}" additivity="false" level="all">
<appender-ref ref="appender"/>
</logger>
<appender name="appender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${file}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${file}.%d.%i</fileNamePattern>
<maxFileSize>100 MB</maxFileSize>
<maxHistory>1</maxHistory>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<jsonFactoryDecorator class="...ElkLogger.ObjectMapperCustomizer"/>
<providers>
<arguments/>
</providers>
</encoder>
</appender>
</configuration>
ElkLogger.java
public class ElkLogger {
private final Logger logger;
public ElkLogger() {
this.logger = getLogger();
}
public void log(Map<String, ?> map) {
logger.info(null, StructuredArguments.entries(map));
}
private Logger getLogger() {
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(context);
try (var stream = Thread.currentThread().getContextClassLoader().getResourceAsStream("elk-logger.xml")) {
configurator.doConfigure(stream);
} catch (Exception exception) {
throw new InitializationException(exception);
}
return context.getLogger("elk");
}
public static class ObjectMapperCustomizer implements JsonFactoryDecorator {
@Override
public JsonFactory decorate(JsonFactory factory) {
ObjectMapper objectMapper = (ObjectMapper) factory.getCodec();
objectMapper.setSerializationInclusion(Include.NON_NULL);
return factory;
}
}
}
Only Java:
ElkLogger.java
public class ElkLogger {
private final Logger logger;
private ElkLogger(Builder builder) {
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
this.logger = context.getLogger(builder.name);
logger.addAppender(newAppender(context, builder.file));
logger.setAdditive(false);
logger.setLevel(ALL);
}
public void log(Map<String, ?> map) {
logger.info(null, StructuredArguments.entries(map));
}
private RollingFileAppender<ILoggingEvent> newAppender(Context context, Path file) {
JsonProviders<ILoggingEvent> providers = new JsonProviders<>();
providers.addProvider(new ArgumentsJsonProvider());
providers.setContext(context);
LoggingEventCompositeJsonEncoder encoder = new LoggingEventCompositeJsonEncoder();
encoder.setProviders(providers);
encoder.setContext(context);
ObjectMapperCustomizer factoryDecorator = new ObjectMapperCustomizer();
encoder.setJsonFactoryDecorator(factoryDecorator);
SizeAndTimeBasedRollingPolicy<ILoggingEvent> policy = new SizeAndTimeBasedRollingPolicy<>();
policy.setFileNamePattern("%s.%%d.%%i".formatted(file.toString()));
policy.setMaxFileSize(new FileSize(100 * MB_COEFFICIENT));
policy.setMaxHistory(1);
policy.setContext(context);
RollingFileAppender<ILoggingEvent> appender = new RollingFileAppender<>();
appender.setFile(file.toString());
appender.setRollingPolicy(policy);
appender.setEncoder(encoder);
appender.setContext(context);
policy.setParent(appender);
providers.start();
encoder.start();
policy.start();
appender.start();
return appender;
}
public static class Builder {
private Path file;
private String name;
public ElkLogger build() {
Objects.requireNonNull(file);
Objects.requireNonNull(name);
return new ElkLogger(this);
}
public Builder file(Path file) {
this.file = file;
return this;
}
public Builder name(String name) {
this.name = name;
return this;
}
}
private static class ObjectMapperCustomizer implements JsonFactoryDecorator {
@Override
public JsonFactory decorate(JsonFactory factory) {
ObjectMapper objectMapper = (ObjectMapper) factory.getCodec();
objectMapper.setSerializationInclusion(Include.NON_NULL);
return factory;
}
}
}
Test:
record Entry(String key, String anotherKey) {};
ElkLogger elk = new ElkLogger.Builder()
.name("elk")
.file(Path.of("elk.log"))
.build();
elk.log(Map.of("@input", new Entry("value", "anotherValue")));
elk.log(Map.of("@input", new Entry("value", null)));
joca-bt commented
The class must be the internal, so for inner classes using $
instead of .
. Closing.