提起日志,可能大家脑中会瞬间弹出很多关键字,比如log4j,jul,jcl,slf4j啊等等,但是在我们做一个系统架构时,在处理日志这块内容的时候,我们可能会面临具体的日志选用。而且在我们系统中难免会使用各种各样的第三方jar包,比如我们的spring,mybatis等等,由于这些第三方jar包都有自己的日志实现,以此如何在各种复杂的日志使用场景下,将我们整个系统的日志统一,便于我们统一管理呢?
当然如果你以后一直做这些crud的功能,可能这块内容你永远也不需要了解,因为这些你的架构师会给你处理好,当你有一天需要自己去架构一个项目的时候,这些东西是你不得不面临的问题,以此了解这些日志体系之间的关系,也是你不可或缺的。
1,首先我们来看下大家都耳熟能详的log4j(具体使用不做说明,大家可自行百度,这里意在说明格日志技术之间的关系)。如下,引入log4j的jar,控制台可以成功输出日志,说明我们的log4j是可以打印日志的。
2,然后我们看下jul,这个日志呢是jdk自带的一个日志技术(java.util.logging.Logger),如下实验,可见,我们的jul自己也是可以打印日志的(注意和log4j的日志风格有很大差异)
3,下面我们再来看下jcl,使用jcl我们需要引入commons-logging.jar,如果有spring4及以前版本使用经验的大兄弟,应该对这个jar包都不陌生的,因为我们的spring4的日志技术就是采用的jcl,这个到spring5被改变了。
发现我们jcl也是可以打印日志的,但是注意,这里的日志样式跟我们前面的log4j是一样的,那么现在就有两个问题了:
(1)jcl是自己打印的日志呢?还是用我们的log4j来打印日志的?
(2)jcl和log4j到底有什么关系呢?
下面,我们将maven中的log4j的依赖去掉试试,我们发现这次的风格又跟我的jul一样了,这里我大胆猜测,jcl打印日志可能和log4j和jul有关,可能也和其他的日志技术有关,具体和哪些技术有什么关系呢?源码,我那不妨来看看jcl的底层实现。
下图为核心代码的几个位置的截图。
private static final String[] classesToDiscover = new String[]{
"org.apache.commons.logging.impl.Log4JLogger",
"org.apache.commons.logging.impl.Jdk14Logger",
"org.apache.commons.logging.impl.Jdk13LumberjackLogger",
"org.apache.commons.logging.impl.SimpleLog"};
通过上面我们可以知道,在jcl中,定义了一个常量数组,顺序依次为:
"org.apache.commons.logging.impl.Log4JLogger",
"org.apache.commons.logging.impl.Jdk14Logger",
"org.apache.commons.logging.impl.Jdk13LumberjackLogger",
"org.apache.commons.logging.impl.SimpleLog"
也就是我们的:log4j,jul14,jul13,simplelog
(说明:jul在jdk3和4是一个分水岭,在jdk4版本以前的日志都是Jdk13LumberjackLogger,在4及以后的为Jdk14Logger,SimpleLog呢也就是我们的system.out输出了)
源代码也很好理解,首先在我们logFactory中getLog的时候,去循环这个常量数组,依次根据取到的类名去反射获取对象,如果类在工程中,反射可以获取class,则返回,没有则捕获异常,用下一个类名继续反射,直到获取到log对象返回。
可以知道如果我们项目中使用了log4j的jar,则项目jcl的logFactory获取到的log对象就是我们的log4j的log对象,然后利用我们的log4j去打印日志,否则就使用我们的jul来输出日志(后面两种基本可以忽略,因为现在的项目很少能见到jdk1.4及以前版本的了,要么就是你很牛逼,自己去把jdk的日志给改了,也没必要)。
4,从上可以知道我们jcl已经很牛了,但是由于其不思进取,很早以前就不更新了,现在也仅仅只是兼容我们的log4j和jul而已,因此很快就被另一种更强大的日志技术所替代了,也就是我们的slf4j。
关于slf4j呢,我们先来看下他的官网介绍
这里说呢,我们再使用slf4j呢,需要slf4j-api.jar这个依赖,然后还需要一个binder,我这里呢我翻译成绑定器。
当我们系统有一个其他的日志,当需要使用slf4j来输出日志的时候,需要使用一个SLF4J Migrator(桥接器)。
其实百度上很多资料都将桥接器和绑定器弄混淆了,下面我通过一个图来说明什么是绑定器,什么是桥接器:
如图,我们现在有个app,通过slf4j打印日志,slf4j呢需要通过一个绑定器(slf4j-jdk14-1.8.0-beta2)来绑定到我们的jul来输出日志,这如果这时候呢,项目经理说,需要在app中集成spring4,我们知道spring4是利用jcl来打印日志的,这时候呢,如果考虑到系统的日志统一,可以使用桥接器(jcl-over-slf4j)将jcl桥接到我们slf4j来,然后通过binding到jul来输出日志,保证系统日志风格统一。
(备注:当然这个有很多更好的方法,我只是通过这个例子来跟大家说明下,什么是绑定器,什么是桥接器,以防大家将概念混淆)
其实这里有一个问题,就是我们循环引用的问题,如图,也就是我们循环引用的问题,依赖设置如下
可以看到会报一个栈溢出的错误,这是由于循环引用导致的,其实这个问题也很好解释,我们绑定器绑定log4j的时候在log4j中又经过桥接器桥接到slf4j,然后又经过绑定器,依次循环,最后栈溢出。
当然如果你是一直写crud的,你看永远也不会遇到这个问题,还是那句话,如果又一天你成为了架构师(人总是要有梦想的),当你遇到这个问题的时候,希望你知道是因为这个问题引起的,从而可以找到合适的解决方法。
其实这个问题的实际场景也比较常见。
如图,我们的app使用slf4j通过binder绑定到log4j,最后通过log4j输出日志。
这时候有个jar包X1,使用slf4j通过binder绑定到jul输出日志,然后X1需要集成X2,X2使用log4j输出日志,X1在集成X2的时候,使用桥接器(log4j-over-slf4j),保证X1的日志最后都是通过jul输出的。这之后我们集成X1的时候,便会出现,log4j的桥接器和绑定器共存的情况,上诉错误便模拟出来了,当然这种情况一般情况下只能取改我们的app了。![在这里插入图
当然我们的日志技术还有很多很多,在我们日后自己架构系统的时候,不仅仅要考虑自己app日志系统风格统一,可用性以及扩展性,同时还需要考虑到和其他技术之间的兼容性等等,因此,对于各种日志技术之间的关系相关的知识也是我们不可或缺的。
希望上述内容可以对大家有所帮助谢谢!