转载:https://mp.weixin.qq.com/s/eIiu08fVk194E0BgGL5gow
一、 日志体系
日志发展到今天,被抽象成了三层:接口层、实现层、适配层:
- 接口层:或者叫日志门面(facade),就是interface,只定义接口,等着别人实现。
- 实现层:真正干活的、能够把日志内容记录下来的工具。但请注意它不是上边接口实现,因为它不感知也不直接实现接口,仅仅是独立的实现。
- 适配层:一般称为Adapter,它才是上边接口的implements。因为接口层和适配层并非都出自一家之手,它们之间无法直接匹配。而鲁迅曾经说过:「计算机科学领域的任何问题都可以通过增加一个中间层来解决」(All problems in computer science can be solved by another level of indirection. – David Wheeler[1]),所以就有了适配层。
适配层又可以分为绑定(Binding)和桥接(Bridging)两种能力:
- 绑定(Binding):将接口层绑定到某个实现层(实现一个接口层,并调用实现层的方法)
- 桥接(Bridging):将接口层桥接到另一个接口层(实现一个接口层,并调用另一个接口层的接口),主要作用是方便用户低成本的在各接口层和适配层之间迁移
二、日志框架与他们之间关系
Log4j
Log4j相比于System.out提供了更强大的能力,甚至很多思想到现在仍被广泛接受:
- 日志可以输出到控制台、文件、数据库,甚至远程服务器和电子邮件(被称做 Appender);
- 日志输出格式(被称做 Layout)允许定制,比如错误日志和普通日志使用不同的展现形式;
- 日志被分为5个级别(被称作Level),从低到高依次是debug, info, warn, error, fatal,输出前会校验配置的允许级别,小于此级别的日志将被忽略。除此之外还有all, off两个特殊级别,表示完全放开和完全关闭日志输出;
- 可以在工程中随时指定不同的记录器(被称做Logger),可以为之配置独立的记录位置、日志级别;
- 支持通过properties或者xml文件进行配置;
不过Log4j有比较明显的性能短板,在Logback和Log4j 2推出后逐渐式微,最终Apache在2015年宣布终止开发Log4j并全面迁移至Log4j 2[10](可参考【2.7 Log4j 2 (2012)】)。
JUL
ava官方的日志系统才随Java 1.4发布。这套系统称做Java Logging API,包路径是java.util.logging,简称JUL。它在Log4j面前仍无太多亮点,广大开发者并没有迁移的动力,导致JUL始终未成气候
JCL
对于独立且轻量的项目来说,开发者可以根据喜好使用某个日志方案即可。但更多情况是一套业务系统依赖了大量的三方工具,而众多三方工具会各自使用不同的日志实现,当它们被集成在一起时,必然导致日志记录混乱。
为此Apache在2002年推出了一套接口Jakarta Commons Logging[15],简称 JCL。这套接口主动支持了Log4j、JUL、Apache Avalon、Lumberjack等众多日志工具。开发者如果想打印日志,只需调用JCL的接口即可,至于最终使用的日志实现则由最上层的业务系统决定。我们可以看到,这其实就是典型的接口与实现分离设计;
但因为是先有的实现(Log4j、JUL)后有的接口(JCL),所以JCL配套提供了接口与实现的适配层(没有使用它的最新版,原因会在【1.2.7 Log4j2 (2012)】提到):
简单介绍一下JCL自带的几个适配层/实现层:
-
AvalonLogger/LogKitLogger:用于绑定Apache Avalon的适配层,因为Avalon 不同时期的日志包名不同,适配层也对应有两个
-
Jdk13LumberjackLogger:用于绑定Lumberjack的适配层
-
Jdk14Logger:用于绑定JUL(因为JUL从JDK 1.4开始提供)的适配层
-
Log4JLogger:用于绑定Log4j的适配层
-
NoOpLog:JCL自带的日志实现,但它是空实现,不做任何事情
-
SimpleLog:JCL自带的日志实现 ,让用户哪怕不依赖其他工具也能打印出日志来,只是功能非常简单
现在JCL作为Apache Commons[18]的子项目,叫 Apache Commons Logging,与我们常用的Commons Lang[19]、Commons Collections [20]等是师兄弟。但JCL的简写命名被保留了下来,并没有改为ACL;
Slf4j
Slf4j也是一个接口层,接口设计与JCL非常接近(毕竟有师承关系)。相比JCL有一个重要的区别是日志实现层的绑定方式:JCL是动态绑定,即在运行时执行日志记录时判定合适的日志实现;而Slf4j选择的是静态绑定,应用编译时已经确定日志实现,性能自然更好。这就是常被提到的classloader问题,更详细地讨论可以参考What is the issue with the runtime discovery algorithm of Apache Commons Logging[24]以及Ceki自己写的文章Taxonomy of class loader problems encountered when using Jakarta Commons Logging[25]。
在推出Slf4j的时候,市面上已经有了另一套接口层JCL,为了将选择权交给用户(我猜也为了挖JCL的墙角),Slf4j推出了两个桥接层:
- jcl-over-slf4j:作用是让已经在使用JCL的用户方便的迁移到Slf4j 上来,你以为调的是JCL接口,背后却又转到了Slf4j接口。我说这是在挖JCL的墙角不过分吧?
- slf4j-jcl:让在使用Slf4j的用户方便的迁移到JCL上,自己的墙角也挖,主打的就是一个公平公正公开。