开发者

Spring: Different logging behaviour for different ApplicationContext

开发者 https://www.devze.com 2023-01-30 21:21 出处:网络
I\'m working on an application that uses Spring and slf4j. That application uses more ApplicationContext parallelly.

I'm working on an application that uses Spring and slf4j. That application uses more ApplicationContext parallelly. Is there any way to these different ApplicationContexts use different logging properties? So the first AC could log into "x.txt" while the second to "y.txt".

I wouldn't like to use more properties file. The appropriate way would be to define a Logger Bean in Spring XML configuration file where I could set different output target for the appropriate property.

For example:

<bean id="LoggerBean" class="???">
     <property name="target" value="${target}" />
</bean>

Here I could manipulate the target variable from source, which would be very handy.

private static final Logger log = LoggerFactory.getLogger(MyClass.class);

So LoggerFactory.getLogger would use the LoggerBean bean configuration to instantiate a Lo开发者_StackOverflow社区gger class.

I need a method where each ApplicationContext has an own LoggerFactory object with different properties (like different target output). So I wouldn't have to rewrite the current code.

I use ApplicationContexts configured by the same xml config file. So these ApplicationContexts use the same classes. Because of that, all Logger are instantiated from LoggerFactory with the same class names they used in. All Logger are instantiated by LoggerFactory.getLogger(MyClass.class) form, as those classes are same in all ApplicationContext ("MyClass"), I can't define differently named Loggers.

Thanks for any reply.


You can define a Spring-managed bean that will configure the logger. For example, assuming you are using logback to implement the slf4j API, this class will load a specified logging configuration file into logback after Spring sets its properties:

public class LogBackConfigurer implements InitializingBean {
    private Resource location;

    public void setLocation(Resource location) {
        this.location = location;
    }

    public void afterPropertiesSet() throws Exception {
        JoranConfigurator configurator = new JoranConfigurator();
        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        configurator.setContext(loggerContext);
        configurator.doConfigure(location.getInputStream());
    }
}

In each Spring configuration file you want to have a different logging configuration, define a bean like the following with a different logging configuration file location.

<bean class="com.example.log.LogBackConfigurer">
  <property name="location" value="classpath:a-logback.xml"/>
</bean>

The class modifies the single application-wide logging context, which is necessary because you want to call the static Logger factory method in your application code. To ensure the logging configuration files don't step on each other, they each have to define differently named loggers.


The final solution was the following:

SLF4j and Logback support MDC which contains key/value pairs on per thread basis. Although the main advantages for our problem is that a child thread automatically inherits key/value pairs of its parent, so if a new Thread is created during initialization of ApplicationContext, that Thread will inherit those pairs from the calling thread. After that you can include these stored values in log message pattern. So I put a special ApplicationContext identifier in MDC before loading the ApplicationContext. When classes are instantiated with a Logger field, these fields obtain their unique identifier which is included in the log message pattern.

<Pattern>[%X{contextID}] - [%thread] - %date{dd/MM/yyyy HH:mm:ss} %level %msg%n</Pattern>

LoggerSeparator.java

public class LoggerSeparator implements InitializingBean{
    private Integer contextID;

    public LoggerSeparator() {}

    public void setContextID(int id) {
        this.contextID = id;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if ( contextID != null )
            MDC.put("contextID", contextID.toString());
    }
}

This bean is the first defined Spring Bean in main.xml.

<bean class="com.myproblem.LoggerSeparator">
    <property name="contextID" value="${contextID}" />
</bean>
...

That class set the contextID in MD. The contextID comes from source code.

...
Properties props = new Properties();
props.put("contextID", contextID);
PropertyPlaceholderConfigurer conf = new PropertyPlaceholderConfigurer();
conf.setProperties(props);
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
context.addBeanFactoryPostProcessor(conf);
context.setConfigLocation("beans/main.xml");
context.refresh();
...

The log messages are logged into one file, but now I can separate them by their unique identifier.


You can use custom FactoryBean to add the logger into the context:

public class Slf4jLoggerFactoryBean implements FactoryBean<Logger> {

    private String loggerName;

    public Logger getObject() throws Exception {
        return LoggerFactory.getLogger(loggerName);
    }

    public Class<?> getObjectType() {
        return Logger.class;
    }

    public boolean isSingleton() {
        return true;
    }

    public void setLoggerName(String loggerName) {
        this.loggerName = loggerName;
    }

}

And then the XML will look like:

<bean id="LoggerBean" class="com.example.Slf4jLoggerFactoryBean">
     <property name="target" value="${target}" />
</bean>
0

精彩评论

暂无评论...
验证码 换一张
取 消