Controlling logback environment dependent properties in external application properties file

By | December 24, 2017
Questions:

In my spring-boot application I use logback for logging. The application uses external application.properties file for setting environment specific application properties and starts with option: -spring.config.location=path/to/file. I would like logback configuration to read properties from there as well so that all environment properties are managed in one place.

I tried the approach described here, but since the properties file is not on the classpath, I get the error:

java.lang.IllegalStateException: Logback configuration error detected: 
ERROR in ch.qos.logback.core.joran.action.PropertyAction - Could not find resource [application.properties]

Is there something I am missing?

UPDATE:

Adding this configuration works:

   <property file="path/to/file" />

But I would like to avoid hardcoding the file path.

Answers:

add system environment variable, so logback falls back to this location for configuration file

logback.configurationFile=path/to/config.xml

Questions:
Answers:

Since you are using Spring you can use Spring extensions for Logback: logback-ext-spring

What this library does is basically gives you possibility to delegate the actual logging to spring managed objects giving you access to the spring context. You can create and configure appenders within the Spring configuration where you will have access to the Environment variables and therefore also to application.properties.

You can find more info on the GitHub page but here is an example configuration, based on the example provided in the link, where the pattern for the console appender is retrieved from the environment property "consolePattern":

logback.xml:

<configuration>

    <appender name="consoleAppender" class="ch.qos.logback.ext.spring.DelegatingLogbackAppender"/>

    <root level="INFO">
        <appender-ref ref="consoleAppender"/>
    </root>

</configuration>

LogbackConfig.java:

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.ext.spring.ApplicationContextHolder;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LogbackConfig {

    @Autowired
    private Environment environment;

    @Bean 
    public static ApplicationContextHolder applicationContextHolder() {
        return new ApplicationContextHolder();
    }

    @Bean 
    public static LoggerContext loggerContext() {
        return (LoggerContext) LoggerFactory.getILoggerFactory();
    }

    @Bean (initMethod = "start", destroyMethod = "stop")
    public static PatternLayoutEncoder encoder (LoggerContext ctx) {
        PatternLayoutEncoder encoder = new PatternLayoutEncoder();
        encoder.setContext(ctx);
        encoder.setPattern(environment.getProperty("consolePattern");
        return encoder;
    }

    @Bean (initMethod = "start", destroyMethod = "stop")
    public static ConsoleAppender consoleAppender (LoggerContext ctx, PatternLayoutEncoder encoder) {
        ConsoleAppender appender = new ConsoleAppender();
        appender.setContext(ctx);
        appender.setEncoder(encoder);
        return appender;
    }
}

Important
Please note that the spring bean name must match the name of the appender in the logback.xml, in the example "consoleAppender". Also don’t forget to specify the initMethod and destroyMethod.

Hope this helps you to find the way not to hardcode the path.

Questions:
Answers:

If you’re using spring boot to run your app you can do:

logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property file="${configuration.file.path}" />
    .......
</configuration>

You can change “configuration.file.path”, but you have to change that name after when you run your app.

You can use the properties defined on the property file like this:

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <layout class="ch.qos.logback.classic.PatternLayout">
        <Pattern>
            %d{yyyy-MM-dd HH:mm:ss} - ${property.defined.in.property.file} - %msg%n
        </Pattern>
    </layout>
</appender>

<root level="${level.defined.in.property.file}">
    <appender-ref ref="STDOUT" />
</root>

And add the param to the VM when you execute your app

-Dlogging.config=/some/path/logback.xml -Dconfiguration.file.path=/other/path/application.properties

logging.config is a spring-boot configuration, so you must to keep that name. You can change that file in every execution without change a configuration in your app.

if you execute manually your app by command line, should be like:

java -Dlogging.config=/some/path/logback.xml -Dconfiguration.file.path=/other/path/application.properties -jar app-1.0.0.jar .....

Doing this, all the logging configuration is dynamic, and you’re giving it when you run the app. You can have different instances with different configurations

Questions:
Answers:

We don’t use spring-boot but maybe this approach might be helpful:

We created this LogbackConfigurator. This class is executed on application startup by listening to the ContextRefreshedEvent:

public class LogbackConfigurator implements ApplicationListener<ContextRefreshedEvent>
{
    // configure your configuration file here
    private static final String         LOGBACK_CONFIGURATION   = "some/where/application.properties";


    @Override
    public void onApplicationEvent(ContextRefreshedEvent event)
    {
        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        try
        {
            // creating JoranConfigurator
            JoranConfigurator joranConfigurator = new JoranConfigurator();

            // setting logger context for JoranConfiguratior
            joranConfigurator.setContext(loggerContext);

            // resetting context, override default configuration
            loggerContext.reset(); 

            // configuring Logback with resource
            joranConfigurator.doConfigure(new Resource(LOGBACK_CONFIGURATION).getInputStream());
        }
        catch (Exception exception)
        {
            // exception handling
        }
    }
}

To have the bean executed, we added this line in our Java configuration:

@Bean
public LogbackTestConfigurator logbackConfigurator()
{
    return new LogbackTestConfigurator();
}

Leave a Reply

Your email address will not be published. Required fields are marked *