超赞!20年以来Java日志Log4j2发展史!
你知道的越多,不知道的就越多,业余的像一棵小草!
你来,我们一起精进!你不来,我和你的竞争对手一起精进!
编辑:业余草
segmentfault.com/u/imango
推荐:https://www.xttblog.com/?p=5300
这两天Log4j经常霸占头条,继前两天的漏洞修复和浮现的文章后,今天我们再来聊一聊 Log4j 的发展史!
前言
最早开始撸码当时就遇到几次日志jar
包冲突的问题,当时也是很烦躁,毕竟了解的也不多,什么那里4j
,这里4j
,还有什么桥接包,而且在我感觉他们的包名都还差不多!!我当时是比较懵逼的,上网搜了下,随便看到一个类似这种桥接过去桥接过来的图我就懵,都是些啥玩意儿。
最近项目中突然发现我们自己的工程对于日志的记录也不是很好,很多各种日志的注解大家用的也不一样,有@Log4j
,也有@Slf4j
的,也有@Log4j2
的,索性干脆彻底去了解一下日志系统。
由于个人一直觉得现在学习技术是很好的年代,毕竟我们在前人已经踩过的坑,总结过的经验之上来学习,相当于站在了巨人的肩膀上,但同时也是最坏的时代,我们对于新技术可能还是很了解,但是这些新技术到底为什么出现,它们到底解决了什么问题,时代选择了这些技术一定是有原因的,我们不清楚技术发展的历史,就像国外不清楚我们中华民族的历史,所以会曲解我们一样。
言归正传,虽然看着日志系统很乱,很难梳理清楚,可能名字都不是很好记,但我们要是从日志系统本身的发展历史去了解一次,可能就会更加明白为什么会出现现在这种情况了。
发展历史
System.out和System.err
这应该是最早的日志记录方式吧,但是不灵活也不可配置,要么就是全部打印,要么就是全部不打印,没有一个统一的日志级别。另外性能也不高,还让程序变成串行化,具体可以参考:都 9102 了,你还不知道 System.out.println 的危害!
Log4j
在1996年初,E.U.SEMPER
(欧洲安全电子市场)项目决定编写自己的跟踪API
,最后该API
演变为Log4j
,Log4j
日志软件包一经推出就备受欢迎,当然这里必须要提到一个人,就是Log4j
的主要贡献者,这个大佬:Ceki Gülcü
。
Ceki Gülcü
可能应该叫巨佬了。。。后面你就明白了,后来Log4j
成为了Apache
基金会项目中的一员,同时Log4j
的火爆,让Log4j
一度成为业内日志标杆。(据说Apache
基金会还曾经建议Sun
引入Log4j
到java
的标准库中,但是sun
拒绝了)。
JUL(Java Util Logging)
果然Sun
有自己的考虑,2002年2月Java1.4
发布,Sun
竟然推出了自己的日志库Java Util Logging
,其实很多日志的实现思想也都是仿照Log4j
,毕竟Log4j
先出来很多年了,已经很成熟了此时,这两个日志工具打架,显然Log4j
是更胜一筹。
它们打架感觉就是互相竞争,Sun
心里可能在想,不就是做个日志工具嘛,谁不会!当然好景不长。
JCL(Jakarta Commons Logging)
Apache
: 玩编程,谁玩的过我!你不让我成为JDK
标准,我就自己成为日志标准,哼!(完全个人意淫。。。)
于是JUL
刚出来不久,2002年8月Apache
又推出了日志接口Jakarta Commons Logging
,也就是日志抽象层,当然也提供了一个默认实现Simple Log
,这野心很大,想一统日志抽象(就像以前的JDBC
一统数据库访问层),让日志产品去实现它的抽象,这样只要你的日志代码依赖的是JCL
的接口,你就可以很方便的在Log4j
和JUL
之间做切换,当时日志领域大概是这样的结构,当然也还是方便理解的,也很优雅。
但是好景不长,在使用过程中,虽然现在日志系统在JCL
的统一下很优雅,很美好,但大家发现了JCL
还不够好,有些人甚至认为JCL
造成的问题比解决的问题还多...emmm
❝http://tapestryjava.blogspot.com/2007/08/so-long-commons-logging-hello-slf4j.html
❞
Slf4j(Simple Logging Facade for Java)
所以大佬粗线,「Ceki Gülcü」(也就是Log4j
的作者)由于一些原因离开了Apache
,之后觉得JCL
不好,于是于2005年自己撸出一个新工程,也就是一套新日志接口(有得也叫日志门面):Slf4j(Simple Logging Facade for Java)
,感觉粗来了么。。。这战争的硝烟,明显这个Slf4j
是直指JCL
啊,但是后面确实也证明了Slf4j
是要比JCL
在很多地方更优秀。
「Ceki Gülcü」:玩接口,我一个人就是一支军队!
但是由于Slf4j
出来的较晚,而且还只是一个日志接口,所以之前已经出现的日志产品,如JUL
和Log4j
都是没有实现这个接口的,所以尴尬的是光有一个接口,没有实现的产品也是很憋屈啊,就算开发者想用Slf4j
也是用不了,这时候,大佬发话了。
「Ceki Gülcü」:别急,我早帮你们想好了,要让Sun
或者Apache
这两个庞然大物来实现我的接口,太南啦,老铁,但。。。我帮你们实现,不就完了么。。。
于是大佬「Ceki Gülcü」撸出了之前提到的桥接包,也就是这种类似适配器模式。
好了,大佬提供了桥接包,于是日志系统现在有了这样的结构。
但是其实之前很多Java
应用应该依赖的JCL
,所以光有日志产品桥接包,好像还不够
「Ceki Gülcü」:没问题,不就是不够桥接包么,我写,我来证明Slf4j是最完美的
于是有了JCL
的桥接包。
相当于此时的桥接包就是分了两种场景
之前 Java
应用用的日志接口(如JCL
)之前 Java
应用用的日志产品(如Log4j
)
那好,那我们如果再考虑一下这种场景呢?
假设哈,你的Java
应用使用了Spring
的第三方的框架,但是假设Spring
默认用JCL
,并且最终用的JUL
打印的日志,但是你的系统使用了Slf4j
作为日志接口,日志产品使用了Log4j
,那。。。不出意外的话。。。你将有两种日志输出,两种日志的打印方式不统一,到时候解决bug的时候就很恼火,而且配置日志的配置文件还需要两份。
所以为了方便统一应用中的所有日志,大佬发话了。
「Ceki Gülcü」:没事,大家都选择用Slf4j统一吧,我来帮大家统一,没有事是桥接包解决不了的,有的话,那就再来个。
当然此时这种场景也是符合之前说的两种情况的,因此现在日志系统大体应该是这样的。
总结一句话就是:大佬撸完。。。
但好景又不长,大佬毕竟是大佬,Log4j
不就是自己写的么,所以最清楚Log4j
缺点的人也正是他。
Logback
由于使用Slf4j
,需要一次桥接包,也就是之前的日志产品都不是正统的Slf4j
的实现,因此,2006年,出自「Ceki Gülcü」之手的日志产品Logback
应运而生,而且大佬还专门写了一篇:
❝http://logback.qos.ch/reasonsToSwitch.html
❞
是不是这太针对了。。。哈哈哈哈,就是这么无情,当然都是他写的,他肯定是最清楚这两者实现的区别。
肯定的,Logback
是完美实现了Slf4j
,于是现在日志系统变成了。
ok了,现在咱们有了2个日志接口,3个日志产品,大家也都看起来相安无事。。。但。。。Slf4j+Logback
的模式,显然很冲击JCL+Log4j
,并且本身Logback
确实比Log4j
性能更优,设计更为合理,所以。。。老东家Apache
可就坐不住了。
Log4j2
在2012年,Apache
直接推出新项目,不是Log4j1.x
升级,而是新项目Log4j2
,因为Log4j2
是完全不兼容Log4j1.x
的。
并且很微妙的,Log4j2
几乎涵盖Logback
所有的特性(这不是对着干是啥~而且还有抄袭的嫌疑。。。哈哈哈),更甚者的Log4j2
也搞了分离的设计,分化成log4j-api
和log4j-core
,这个log4j-api
也是日志接口,log4j-core
才是日志产品。。。
emmm,我看到这,我都有点崩溃了。
现在我们可有了3个日志接口,以及4个日志产品。。。当然Apache
也知道该做啥,为了让大家可以接入自己的Log4j2
,那不就是桥嘛,不就是桥嘛,Apache
也麻溜的推出了它的桥接包,所以。。。
唉,所以我都不敢画图了。。。
前方高能!!!
前方高能!!!
前方高能!!!
总结到这,我们可以吸取什么经验么?
不写接口的坏处 没有什么问题是加一个层适配器(接口)解决不了的,如果有,那就再加一层
最佳实践
了解了日志的发展历史,那现在我们再回过头来看看如果,你的系统在选择日志方案的时候,如何抉择呢?毕竟我们3个日志接口,以及4个日志产品。
显然第一点是使用日志接口的
API
而不是直接使用日志产品的API
这一条也是必须的,也是符合依赖倒置原则的,我们应该依赖日志的抽象,而不是日志的实现
日志产品的依赖只添加一个
当然也这个也是必须的,依赖多个日志产品,只会让自己的应用处理日志显得更复杂,不可统一控制
把日志产品的依赖设置为
Optional
和runtime scope
其中
Optional
是为了依赖不会被传递,比如别的人引用你这个jar
,就会被迫使用不想用的日志依赖
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>${log4j.version}version>
<optional>trueoptional>
dependency>
而scope
设置为runtime
,是可以保证日志的产品的依赖只有在运行时需要,编译时不需要,这样,开发人员就不会在编写代码的过程中使用到日志产品的API
了
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-slf4j-implartifactId>
<version>${log4j.version}version>
<scope>runtimescope>
dependency>
谢谢观看o( ̄▽ ̄)d
最后留一个悬念,我们的大佬「Ceki Gülcü」为啥离开apache
?