添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

The default logging framework in Spring Boot is Logback , which autoconfigures when we use the spring-boot-starter-parent in our Spring Boot project. The problem is that although Spring Boot is responsible for detecting, initializing, and configuring Logback in a Spring Boot application, Logback-related classes such as appenders are not beans and are not managed by the Spring ApplicationContext .

In this article, we’ll dive deep into this problem and provide three solutions for fixing the issue.

A bit of background:

Before offering solutions, let’s provide some background on Logback and Spring Boot logging.

What is Logback?

Logback is a logging framework for Java applications designed to be faster and more feature-rich than its predecessor, Log4j 1.x.

After a brief introduction about Logback and Spring Boot logging, let’s dive into the code and see what the root of the problem is. In my case, I have a custom Logback appender ( NotificationAppender ) that is supposed to send a notification (for example, email, push notification, or…) under certain circumstances (for example, the occurrence of a specific number of error logs or finding a specific text in the logs, or…).

To send that notification, the NotificationAppender needs to access a Spring bean called Notifier , but as we mentioned before, Logback is not managed by the Spring ApplicationContext , so it does not access ApplicationContext , and we can not inject the Notifier bean in our custom appender ( NotificationAppender ), it means this code will not work:

public class NotificationAppender extends AppenderBase<ILoggingEvent> {
    @Autowired
    private  Notifier notifier;
    @Override
    protected void append(ILoggingEvent loggingEvent) {
        notifier.notify(loggingEvent.getFormattedMessage());

I registered the NotificationAppender in the logback-spring.xml:

<configuration debug="true">
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <include resource="org/springframework/boot/logging/logback/console-appender.xml" />
    <appender name="NOTIFY" class="com.saeed.springlogbackappender.NotificationAppender"/>
    <logger name="org.springframework.web" level="DEBUG"/>
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="NOTIFY" />
    </root>
</configuration>

We will get errors like this because, as I mentioned, Logback does not access the ApplicationContext:

10:41:05,660 |-ERROR in com.saeed.springlogbackappender.NotificationAppender[NOTIFY] - Appender [NOTIFY] failed to append. java.lang.NullPointerException: Cannot invoke "com.saeed.springlogbackappender.Notifier.notify(String)" because "this.notifier" is null
 at java.lang.NullPointerException: Cannot invoke "com.saeed.springlogbackappender.Notifier.notify(String)" because "this.notifier" is null

Even if we change the appender class and make it a Spring bean by adding @Component on top of the class, The situation is getting worse.

@Component
public class NotificationAppender extends AppenderBase<ILoggingEvent> {
    private  Notifier notifier;
    public NotificationAppender(Notifier notifier) {
        this.notifier = notifier;
    @Override
    protected void append(ILoggingEvent loggingEvent) {
        notifier.notify(loggingEvent.getFormattedMessage());

And we will get an error similar to this:

10:53:57,887 |-ERROR in ch.qos.logback.core.model.processor.AppenderModelHandler - Could not create an Appender of type [com.saeed.springlogbackappender.NotificationAppender]. ch.qos.logback.core.util.DynamicClassLoadingException: Failed to instantiate type com.saeed.springlogbackappender.NotificationAppender
Caused by: java.lang.NoSuchMethodException: com.saeed.springlogbackappender.NotificationAppender.<init>()

This is because Logback needs a default constructor to initialize the NotificationAppender. If we add the default constructor to the class, we will get the previous NullPointerException for the Notifier bean because now we have two instances of NotificationAppender in our application, one instantiated and managed by Logback and the other by ApplicationContext!

I hope I was able to explain the problem. Now, we want to solve this problem by providing three solutions. I have created a spring boot project called spring-logback-appender in GitHub and created separate commits for each solution.

1- Spring Boot creates the bean and adds it as a Logback appender dynamically in @PostConstruct

In this approach, we define the NotificationAppender as a Spring bean, so we can inject every Spring bean into it without a problem. But As we saw in the problem statement before, how do we want to introduce this Spring bean as an appender to Logback? We will do it programmatically using the LoggerContext:

@Component
public class NotificationAppender extends AppenderBase<ILoggingEvent> {
    private final Notifier notifier;
    public NotificationAppender(Notifier notifier) {
        this.notifier = notifier;
    @Override
    protected void append(ILoggingEvent loggingEvent) {
        notifier.notify(loggingEvent.getFormattedMessage());
    @PostConstruct
    public void init() {
        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        Logger rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
        rootLogger.addAppender(this);
        setContext(context);
        start();

This will work, and if we call the /hello API, we will see that the Notifier will notify using the appender.

For me, this approach has some drawbacks:

<configuration debug="true">
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <include resource="org/springframework/boot/logging/logback/console-appender.xml" />
    <appender name="NOTIFY" class="com.saeed.springlogbackappender.NotificationAppender"/>
    <logger name="org.springframework.web" level="DEBUG"/>
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="NOTIFY" />
    </root>
</configuration>

The other change that we need to make is to make the Notifier field static and NotificationAppender implement the ApplicationContextAware:

@Component
public class NotificationAppender extends AppenderBase<ILoggingEvent> implements ApplicationContextAware {
    private static Notifier notifier;
    @Override
    protected void append(ILoggingEvent loggingEvent) {
        if (notifier != null)
            notifier.notify(loggingEvent.getFormattedMessage());
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        notifier = applicationContext.getAutowireCapableBeanFactory().getBean(Notifier.class);

This approach will result in a similar result to the first approach, but now the appender is configurable using a standard method in the Logback.

We still have some drawbacks to this approach:

public class AppenderDelegator<E> extends UnsynchronizedAppenderBase<E> {
    private final ArrayList<E> logBuffer = new ArrayList<>(1024);
    private Appender<E> delegate;
    @Override
    protected void append(E event) {
        synchronized (logBuffer) {
            if (delegate != null) {
                delegate.doAppend(event);
            } else {
                logBuffer.add(event);
    public void setDelegateAndReplayBuffer(Appender<E> delegate) {
        synchronized (logBuffer) {
            this.delegate = delegate;
            for (E event : this.logBuffer) {
                delegate.doAppend(event);
            this.logBuffer.clear();
@Component
public class NotificationAppender extends AppenderBase<ILoggingEvent> implements SmartLifecycle {
    private final Notifier notifier;
    public NotificationAppender(Notifier notifier) {
        this.notifier = notifier;
    @Override
    protected void append(ILoggingEvent loggingEvent) {
        notifier.notify(loggingEvent.getFormattedMessage());
    @Override
    public boolean isRunning() {
        return isStarted();
    @Override
    public void start() {
        super.start();
        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        Logger rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
        AppenderDelegator<ILoggingEvent> delegate = (AppenderDelegator<ILoggingEvent>) rootLogger.getAppender("DELEGATOR");
        delegate.setDelegateAndReplayBuffer(this);

It is worth mentioning that, unlike the second approach, we don’t add the NotificationAppender to the LoggerContext’s root logger.

This approach is the most complicated one, but it offers the most flexibility and log coverage in the appender.

Dev/Test Observability using Digma

Logs, metrics, and traces are the three pillars of observability, and this problem that we tried to solve happened to me in a real project where we wanted to produce a metric in specific circumstances in the logs.

In the last section of this article, I want to introduce you to Digma, a tool that is very helpful for me during testing and development. The Digma has an IntelliJ plugin that profiles our code execution in runtime on our local machine without needing to deploy it to other environments. It helps us find the root cause of different issues, such as bottlenecksscaling problems, or DB query issues locally.

We discuss the challenge of accessing a Spring Bean from a custom Logback appender in a Spring Boot application and provide three solutions to fix this issue. The second approach should usually work. In the last section, we also discuss how to correlate the Log and Trace using Digma to improve application observability.


Install Digma: Here

Spread the news:

Post navigation

Similar Posts

44 Tools, Plugins and Libraries to Get Started with Your First Java Spring/Quarkus App