I'm using Spring Boot 1.5.2
with Logback
, which is configured using a logback-spring.xml
. There, I define an appender of a custom type (subclass of RollingFileAppender
) and would like to get a pair of beans injected.
Is this possible? I naively tried annotating the appender @Component
etc. but as it is created by Logback/Joran
, it of course doesn't work. Is there a trick I can apply?
If not possible, what would be the canonical way of achieving my goal (inserting beans from the application context into an appender)?
As mentioned also in the question, by default, Logback instantiates and manages the lifecycle of different logging components (appenders, etc) itself. It knows nothing of Spring. And, Logback typically configures itself way before Spring is started (as Spring also uses it for logging).
So, you cannot really use Spring to configure an instance of FileAppender
(or some other rather fundamental appender) and then inject that into Logback.
However, in case your appender is not truly fundamental (or you are happy to ignore logging events during Spring Boot startup), you can follow the "simple" approach below. In case you would like to capture all events (including the ones during startup), keep on reading.
Simple approach (loses events during startup)
Create your appender as a Spring component:
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import org.springframework.context.SmartLifecycle;
import org.springframework.stereotype.Component;
@Component
public class LogbackCustomAppender extends UnsynchronizedAppenderBase<ILoggingEvent> implements SmartLifecycle {
@Override
protected void append(ILoggingEvent event) {
// TODO handle event here
}
@Override
public boolean isRunning() {
return isStarted();
}
}
As you can see, it is annotated with @Component
so that Spring will pick it up during classpath scanning. Also, it implements SmartLifecycle
so that Spring will call Logback Lifecycle
interface methods (luckily, start()
and stop()
methods have exactly the same signature, so we only need to implement isRunning()
which will delegate to Logback isStarted()
).
Now, by the end of Spring application context startup, we can retrieve the fully initialized LogbackCustomAppender
instance. But Logback is blissfully unaware of this appender, so we need to register it with Logback.
One way of doing this is within your Spring Boot Application class:
@SpringBootApplication
@ComponentScan(basePackages = {"net.my.app"})
public class CustomApplication {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(CustomApplication.class, args);
context.start();
addCustomAppender(context, (LoggerContext) LoggerFactory.getILoggerFactory());
}
private static void addCustomAppender(ConfigurableApplicationContext context, LoggerContext loggerContext) {
LogbackErrorCollector customAppender = context.getBean(LogbackCustomAppender.class);
Logger rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.addAppender(customAppender);
}
}
No need to change anything in your Logback configuration file.
More complicated approach (captures all events)
As mentioned above, you might be interested in not losing the events logged during Spring Boot startup.
For this, you could implement a placeholder appender (that would buffer startup events internally):
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import java.util.ArrayList;
public class BufferingAppenderWrapper<E> extends UnsynchronizedAppenderBase<E> {
private final ArrayList<E> eventBuffer = new ArrayList<>(1024);
private Appender<E> delegate;
@Override
protected void append(E event) {
synchronized (eventBuffer) {
if (delegate != null) {
delegate.doAppend(event);
}
else {
eventBuffer.add(event);
}
}
}
public void setDelegateAndReplayBuffer(Appender<E> delegate) {
synchronized (eventBuffer) {
this.delegate = delegate;
for (E event : this.eventBuffer) {
delegate.doAppend(event);
}
this.eventBuffer.clear();
}
}
}
We register that appender with Logback the usual way (e.g. logback.xml):
<appender name="DELEGATE" class="my.app.BufferingAppenderWrapper" />
<root level="INFO">
<appender-ref ref="DELEGATE" />
</root>
After Spring has started, look that appender up by name and register your Spring-configured appender with the placeholder (flushing the buffered events in the process):
@SpringBootApplication
@ComponentScan(basePackages = {"net.my.app"})
public class CustomApplication {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(CustomApplication.class, args);
context.start();
addCustomAppender(context, (LoggerContext) LoggerFactory.getILoggerFactory());
}
private static void addCustomAppender(ConfigurableApplicationContext context, LoggerContext loggerContext) {
LogbackErrorCollector customAppender = context.getBean(LogbackCustomAppender.class);
Logger rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
BufferingAppenderWrapper<ILoggingEvent> delegate = (BufferingAppenderWrapper<ILoggingEvent>) rootLogger.getAppender("DELEGATE");
delegate.setDelegateAndReplayBuffer(customAppender);
}
}
LogbackCustomAppender
stays the same.
It isn't possible to do what you are trying to do. Logback is initialised before the application context is created so there's nothing to perform the dependency injection.
Perhaps you could ask another question describing what you'd like your appender to be able to do? There may be a solution that doesn't involve injecting Spring-managed beans into it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With