In AbstractConfiguration the field asyncLoggerConfigDisruptoris lazily initialized.
private AsyncLoggerConfigDisruptor asyncLoggerConfigDisruptor;
public AsyncLoggerConfigDelegate getAsyncLoggerConfigDelegate() {
// lazily instantiate only when requested by AsyncLoggers:
// loading AsyncLoggerConfigDisruptor requires LMAX Disruptor jar on classpath
if (asyncLoggerConfigDisruptor == null) {
asyncLoggerConfigDisruptor = new AsyncLoggerConfigDisruptor(asyncWaitStrategyFactory);
}
return asyncLoggerConfigDisruptor;
}
The method getAsyncLoggerConfigDelegate() would normally be called in the constructor of AsyncLoggerConfig(...):
protected AsyncLoggerConfig(
final String name,
final List<AppenderRef> appenders,
final Filter filter,
final Level level,
final boolean additive,
final Property[] properties,
final Configuration config,
final boolean includeLocation) {
super(name, appenders, filter, level, additive, properties, config, includeLocation);
delegate = config.getAsyncLoggerConfigDelegate();
delegate.setLogEventFactory(getLogEventFactory());
}
Here an assumption is being made that Configuration#getAsyncLoggerConfigDelegate() will not return null and properly initialiize the field, but since Configuration is an interface that can be implemented by anyone, anything is possible. :) It also assumes that the AsyncLoggerConfig has been created with thisAbstractConfiguration which may not be a given if created somewhere else and just added with Configuration#addLogger(String, LoggerConfig).
This also implies that the AbstractConfiguration has a certain "knowledge" of the inner workings of the AsyncLoggerConfig which I believe violates some principle of encapsulation. :)
The potential NPE comes up in the AbstractConfiguration#start() and AbstractConfiguration#stop() methods where it is assumed that the asyncLoggerConfigDisruptor has already been lazily initialized to a non-null value.
start()
if (hasAsyncLoggers()) {
asyncLoggerConfigDisruptor.start();
}
stop()
if (hasAsyncLoggers()) {
LOGGER.trace("{} stopping AsyncLoggerConfigDisruptor.", cls);
asyncLoggerConfigDisruptor.stop(timeout, timeUnit);
}
A potential fix is either doing a simple null-check or maybe adding a protected API (since you can't call start() on the AsyncLoggerConfigDelegate:
@Override
public AsyncLoggerConfigDelegate getAsyncLoggerConfigDelegate() {
return getAsyncLoggerConfigDisruptor();
}
protected AsyncLoggerConfigDisruptor getAsyncLoggerConfigDisruptor() {
// lazily instantiate only when requested by AsyncLoggers:
// loading AsyncLoggerConfigDisruptor requires LMAX Disruptor jar on classpath
if (asyncLoggerConfigDisruptor == null) {
asyncLoggerConfigDisruptor = new AsyncLoggerConfigDisruptor(asyncWaitStrategyFactory);
}
return asyncLoggerConfigDisruptor;
}
public void start() {
...
if (hasAsyncLoggers()) {
getAsyncLoggerConfigDisruptor().start();
}
...
}
public void stop() {
...
if (hasAsyncLoggers()) {
getAsyncLoggerConfigDisruptor().stop(timeout, timeUnit);
}
...
}
NOTE: additionally I think the constructor will throw an exception if the LMX Jar is not on the classpath (at the very latest in start/stop) but I am not sure how exactly it expresses itselff :)
AbstractConfiguration (Log4j 2.24.1)
In
AbstractConfiguration
the fieldasyncLoggerConfigDisruptor
is lazily initialized.The method
getAsyncLoggerConfigDelegate()
would normally be called in the constructor ofAsyncLoggerConfig(...)
:Here an assumption is being made that
Configuration#getAsyncLoggerConfigDelegate()
will not returnnull
and properly initialiize the field, but sinceConfiguration
is an interface that can be implemented by anyone, anything is possible. :) It also assumes that theAsyncLoggerConfig
has been created with thisAbstractConfiguration
which may not be a given if created somewhere else and just added withConfiguration#addLogger(String, LoggerConfig)
.This also implies that the
AbstractConfiguration
has a certain "knowledge" of the inner workings of theAsyncLoggerConfig
which I believe violates some principle of encapsulation. :)The potential NPE comes up in the
AbstractConfiguration#start()
andAbstractConfiguration#stop()
methods where it is assumed that theasyncLoggerConfigDisruptor
has already been lazily initialized to a non-null
value.start()
stop()
A potential fix is either doing a simple null-check or maybe adding a protected API (since you can't call
start()
on theAsyncLoggerConfigDelegate
: