JAVA日志是很成熟的组件,再加上其作用单一,所以大家一般都是拿来就用,对其研究其实并不多。
但有些场景需要对日志组件做些改造,如某项目制的软件,因为jar包太多项目更新时上传、下载会耗费大量时间,对于完全不做更新的一些jar包确实没必要一直堆在war包里,所以就有了tomcat的共享jar包shared.loader方案来改进这个问题。当我们把所有公共jar一股脑共享后造成一个新的问题,不同war的日志居然输出到了同一个文件,这样日志就没法看了。
什么原因?这个不看源码是解决不了了,从LoggerFactory.getLogger切入源码,首先是寻找LoggerFactory,这个过程主要是找出slf4j的实现,最终在找一个StaticLoggerBinder的类。我们顺藤摸瓜找到了AbstractLoggerAdapter适配器,此时我们会注意到一个日志上下文,这里记录了每个war包输出日志的路径,我们注意到官方用到了享元模式来优化性能,这里说明了为什么要传入不同的name或者class,因为依据这个来给不同内容分类输出日志。当我看到上下文的具体获取逻辑(如图),共享包遇到的问题我们瞬间就明白了
protected LoggerContext getContext(final Class<?> callerClass) {
ClassLoader cl = null;
if (callerClass != null) {
cl = callerClass.getClassLoader();
}
if (cl == null) {
cl = LoaderUtil.getThreadContextClassLoader();
}
return LogManager.getContext(cl, false);
}
真相只有一个:又是classloader的作妖(大多数场景classloader逻辑是优秀的,可以详细去了解双亲委派机制,但少数业务场景,我们又比较烦classloader)
好在官方同样提供了从线程获取上下文的逻辑,那对于源码改造我们就变得简单了,直接从线程获取即可。
以上就是我们对log4J源码的一次学习。根据官方提供的线程获取上下文的思路,我们同样可以使用线程来获取applicationContext,这种方式对解决共享包问题尤为有效。