springspring-cloudspring-logbacklogstash-logback-encoderspring-autoconfiguration

Spring boot remove logback appender added in code(auto configuration)


I’d like to have spring auto configuration which adds LogstashTcpSocketAppender.

What I have done:

  1. The LogstashTcpSocketAppender was added in to the LoggerContext from the LogstashAutoConfiguration.java
@Configuration
@ConditionalOnProperty(name = "logging.logstash.url")
@RequiredArgsConstructor
public class LogstashAutoConfiguration {
    
    @Value("${spring.application.name:null}")
    private String applicationName;
    
    @Value("${logging.logstash.url}")
    private String logstashUrl;
    
    @Bean
    public LogstashTcpSocketAppender logstashAppender() {
        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();

        LogstashTcpSocketAppender logstashTcpSocketAppender = new LogstashTcpSocketAppender();
        logstashTcpSocketAppender.setName("LOGSTASH");
        logstashTcpSocketAppender.setContext(loggerContext);
        logstashTcpSocketAppender.addDestination(logstashUrl);

        LogstashEncoder encoder = new LogstashEncoder();
        encoder.setIncludeMdc(true);
        encoder.getFieldNames().setLevelValue(null);
        encoder.setCustomFields(String.format("{\"app_name\":\"%s\"}", applicationName));

        logstashTcpSocketAppender.setEncoder(encoder);
        logstashTcpSocketAppender.start();

        loggerContext.getLogger(Logger.ROOT_LOGGER_NAME).addAppender(logstashTcpSocketAppender);

        return logstashTcpSocketAppender;
    }
}

  1. After a while, the Spring Boot trigger a event will make the app reconfig. (for example I use Consul so I just change property in key/value storage, then spring refresh my context)
  2. It call the initializeWithConventions in the AbstractLoggingSystem.java
  3. then it will call the loadConfiguration in the LogbackLoggingSystem.java
  4. then it will stopAndReset(loggerContext). here it will stop all the appenders, and it will resetAllListeners();, which will clear the all logback listeners. (so I cannot use logback listeners for addition appenders again)

Are there correct way add Appender through spring auto configuration? How can I prevent remove LogstashTcpSocketAppender from LoggerContext when spring makes the app reconfig?


Solution

  • In the above Configuration class, the intent is to add an appender for Logstash and that takes care of sending the logs.

    In case of an environment variable changes or the context gets refreshed, you can listen to the relevant events, then check if your Logstash appender is configured or not. Make sure you add the Logstash appender only if it is missing.

    Here is the class that would do the same.

    @Configuration
    @ConditionalOnProperty(name = "logging.logstash.url")
    public class LogstashAppenderConfiguration {
    
        @Value("${spring.application.name:null}")
        private String applicationName;
    
        @Value("${logging.logstash.url}")
        private String logstashUrl;
    
        @EventListener(ContextRefreshedEvent.class)
        public void onContextRefreshedEvent(ContextRefreshedEvent event) {
            this.addLogStashAppenderIfMissing();
        }
    
        @EventListener(RefreshScopeRefreshedEvent.class)
        public void onRefreshScopeRefreshedEvent(RefreshScopeRefreshedEvent event) {
            this.addLogStashAppenderIfMissing();
        }
    
        @EventListener(EnvironmentChangeEvent.class)
        public void onEnvironmentChangeEvent(EnvironmentChangeEvent event) {
            this.addLogStashAppenderIfMissing();
        }
    
        public void addLogStashAppenderIfMissing() {
            LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
            synchronized (this) {
                if (Objects.isNull(loggerContext.getLogger(Logger.ROOT_LOGGER_NAME).getAppender("LOGSTASH"))) {
                    LogstashTcpSocketAppender logstashTcpSocketAppender = new LogstashTcpSocketAppender();
                    logstashTcpSocketAppender.setName("LOGSTASH");
                    logstashTcpSocketAppender.setContext(loggerContext);
                    logstashTcpSocketAppender.addDestination(logstashUrl);
    
                    LogstashEncoder encoder = new LogstashEncoder();
                    encoder.setIncludeMdc(true);
                    encoder.getFieldNames().setLevelValue(null);
                    encoder.setCustomFields(String.format("{\"app_name\":\"%s\"}", applicationName));
    
                    logstashTcpSocketAppender.setEncoder(encoder);
                    logstashTcpSocketAppender.start();
    
                    loggerContext.getLogger(Logger.ROOT_LOGGER_NAME).addAppender(logstashTcpSocketAppender);                                                                                      ;
                }
            }
        }
    }