主要处理多个应用程序运行在同一个web容器,并且每个应用使用各自独立的日志环境。
——想想一个tomcat运行了多个项目——也就是“一个JVM,多个ClassLoader,每个ClassLoader拥有自己独立的Logger Context”的问题。
Context Selectors
调用LoggerFactory.getLogger("foo")的时候,会要求SLF4J绑定一个ILoggerFactory,如果SLF4J使用的是logback,那么“绑定ILoggerFactory”的方法,会代理给一个ContextSelector实例,该实例会根据情况返回最合适的LoggerContext对象——这个对象实现了ILoggerFactory接口。
默认总是返回DefaultContextSelector,可以在系统属性里通过logback.ContextSelector属性指定自己的类。
ContextJNDISelector
根据JNDI查找Selector——这样可以在各个项目的web.xml里配置自己的JNDI环境变量。
为了在单个web项目重启和关闭的时候,对应的logger Context可以被回收,最好配置ContextDetachingSCL监听器
不过每次获取logger都查找JNDI太慢,可以配置LoggerContextFilter过滤器来避免——会在每个http请求到来的时候把logger context放在线程内部,从而允许之后跳过JNDI搜索。
此时最好把logger全声明成static的,减少logger的获取总次数。
Taming static references in shared libraries
如果有一个类属于共享包,被两个web项目同时引用
1,当这个类使用非静态logger时,一切良好,来自两个web项目的调用,会返回各自的logger context。
2,当这个类使用静态logger时,会错误的返回首次调用它的web项目的logger context——对这个问题ContextJNDISelector无能为力(eluded a solution for eons... 哈哈哈哈)
对于上述的“来自共类的静态logger,获取不同的logger context”的问题,有几个的办法:
1,共享类改成非静态logger——一般不现实,因为共享类的源码往往不受项目控制。
2,把共享类挪到web项目里——也够呛,一个两个类还好,那么多第三方依赖咋弄?
3,所以还有一种取巧的办法:用SiftingAppender,根据JNDI信息把不同的日志分别输出到不同的文件里去(logback提供了JNDIBasedContextDiscriminator帮助做这事儿)——也就是说,两个项目分享同一个logger,等到其关联的appender需要把event输出到文件时,再临时决定输出到哪个文件。
上述第3种方法有一个问题:
假如项目A和项目B都使用共享包S。
项目A启动并调用S的时候,就已经把S的logger context设置为A了,日志会写入A.log中。
B启动并调用S的时候logger contex依然是A(不过因为配置了SiftingAppender的存在,B的日志依然可以写到正确的B.log中)
——也就是说,共享logger的问题并没有实质解决,这个logger还是只能属于A或者B其中之一,只不过logger在写日志前根据MDC里的JNDI可以将日志写到正确的文件中去。
而初始化了两个不同的logger context都指向B.log,存在多个线程写同一个文件的不安全问题——需要对appender配置prudent参数来解决(会影响性能)。
(正常使用都是一个web container部署一个web项目,很少遇到上述情况。这部分文档看的很蛋疼……)