标准——log4j
最开始出现的是log4j,也是应用最广泛的日志系统,它的出现使Java日志系统的标准基本确定了。
它提出的一些基本理念,一直沿用至今:
- Appender: 实际的输出是通过Appender(输出源)。有许多可用的Appender,比如FileAppender、ConsoleAppender、SocketAppender、SyslogAppender、NTEventLogAppender,甚至SMTPAppender。多个Appender可以被关联到任何Logger上,所以可以到多个输出文件上记录相同的信息。
- Logger:一个允许应用记录日志的对象。每个Logger相互独立,通过名字或标识符来区分
- Level : 日志级别,通过配置不同的日志界别来打印不同的日志信息。
混乱——jul
在java1.4版本,SUN引入了一个新的API,叫java.util.logging,
规整—— commons-logging (jcl)
log4j与jul的共存,各个日志系统互相没有关联,没有统一和规整化API,开发使用严重依赖于具体实现,毫无扩展可言。
为了解决这个问题,Apache弄出了一个commons-logging的框架。
commons-logging并非一个日志打印框架,而是一个API bridge, 它起到一个连接和沟通的作用,开发者可以使用它来兼容jul和log4j,第三方库可以使用commons-logging来做一个中间层,去灵活选择jul或者log4j,而不必强加依赖。
但jcl对jul和log4j的配置问题兼容得并不好,更糟糕的是,由于commons-logging使用动态绑定,可能会遇到类加载问题,导致NoClassDefFoundError的错误出现。因此又出现了slf4j
比如:子加载器加载 JCL 接口,JCL 接口使用当前或父类加载器加载接口实现,这就导致我们的子加载器的类不能访问 JCL 实现。而slf4j使用了编译时强绑定,只会加载依赖的一个实现,所有的 API 绑定都是在编译时完成的,slf4j-xxx.jar 或 logback-classic.jar 所有的依赖文件都打包在了一起。
使用 Jakarta Commons Logging 时遇到的类加载器问题的分类 - 文章 - 代码饭 (daimafans.com)
发展期——slf4j,logback
slf4j
slf4j(Simple logging facade for Java)是log4j作者的又一力作,slf4j的设计相对较为精巧,作者将接口以及实现分开,其中slf4j-api中定义了接口,开发者需要关心的就是这个接口,无需再关心下层如何实现,同时各个是slf4j接口的实现者只要遵循这个接口,就能够做到日志系统间的无缝兼容。
SLF4J是简单的日志外观模式框架,抽象了各种日志框架例如Logback、Log4j、Commons-logging和JDK自带的logging实现接口。
有各种桥接包,它使得用户可以在部署时使用自己想要的日志框架。
SLF4J是轻量级的,在性能方面几乎是零消耗的。虽然sl4j没有限定具体的日志实现方式,但作者更推荐logback,因为其具有更强大的功能与性能。
SFL4j在编译时静态绑定日志框架避免了JCL动态绑定带来的一系列问题,SLF4j+Logback在设计和性能上都优于Commons-logging+Log4j。
使用slf4j,要注意里面的几个概念
- slf4j本身的api:slf4j-api 。
- 绑定器:例如slf4j-#日志实现#-version.jar,通过绑定器绑定到真正的日志实现。一个应用中,只可以存在一个有效的绑定器和对应的日志实现。
-
- 桥接器:例如#日志实现#-over-slf4j,通过桥接器,将原先的日志框架或实现代理到slf4j-api上。这样,应用无需修改原先的日志代码。
logback
Logback是由log4j创始人设计的又一个开源日志组件。
logback当前分成三个模块:logback-core,logback- classic和logback-access。Logback是Log4j的改进版本,而且原生支持SLF4J,性能也更好。
未来——log4j2?
Log4j1.x已经被广泛应用到各个系统及框架中。然后,1.x毕竟太古老,代码很久没有更新。目前,Log4j 1.x的代码已经很难维护,因为它依赖于很多Jdk老版本的api。
作为Log4j 1.x的替代品,SLF4J/Logback已经对日志系统做了很大的改进,主要是由于其异步输出性能优越,尤其是在分布式系统中,性能是logback的10倍以上,而且支持动态配置等。
常见的日志使用方式
commons-logging + log4j
应用中使用jcl接口编码(见代码片段1-4),实际输出使用log4j配置文件,这是大多数应用使用的方式,尤其是较老的一些应用。所需要的jar包比较简单
+ commons-logging
+ log4j
如果代码使用的jul日志系统,那么则只需要commons-logging这个jar即可。
slf4j + logback
slf4j最佳的使用方式是用logback作为日志系统输出,应用中使用slf4j接口编码,所需要的包为
+ slf4j-api
+ logback-core
+ logback-classic(已含跟slf4j绑定的包)
这只是最恰到的使用方式,实际情况由于应用的跨度比较长,可能老的代码或者依赖的二分库使用的jcl,log4j或者jul编码,而想升级到logback,则需要使用桥接包。
slf4j+log4j
虽然使用slf4j的推荐日志系统是logback,但可能也会有些应用会采用log4j作为日志系统进行输出。
应用代码使用slf4j接口编码,使用log4j作为实际输出系统,需要引入如下jar包:
+ slf4j-api
+ slf4j-log4j12 将slf4j绑定到了log4j输出
+ log4j
jcl+slf4j+log4j
这种情况下,应用日志代码使用commons-logging和slf4j 混合编码,需要将jcl桥接到了slf4j上,并使用log4j作为日志输出。所需要的jar包如下:
+ jcl-over-slf4j
+ slf4j-api
+ slf4j-log4j12
+ log4j
桥接器允许有多个,绑定器(logback(logback-classic), slf4j-log4j12, slf4j-jdk14三种只能存在其一)只能有一个。
依赖空包的原理
maven在解析依赖的时候,有两个原则:
- 第一原则是路径最短有限原则,例如A-->B-->C-1.0(A依赖B,B依赖C的1.0版本),同时A-->D-->E-->C-2.0,那么从A来看,最终会依赖C的1.0版本进来,因为路径最短,最可信,这个例子也推翻了“高版本覆盖低版本”的错误言论。
- 第二原则是优先声明原则(pom中的声明顺序),这是对第一原则的补充,就是路径长度相同(第一原则好无力)的情况下,第二原则开始决策微调。
尽量使用dependencyManagement:
- 它表示子项目 不强制依赖(关联)里面的依赖包。
- 在父项目里可以这么用引入依赖,在具体实现的子项目里用这个没有意义,因为它不去关联jar,这里面的jar不会被关联和打包,子项目的依赖不指定版本默认会找他的版本。
依赖空包:
- 可以实现全局排掉这个包。
排除jcl的影响:如果应用程序使用slf4j,但是依赖的组件引入jcl,如何排除组件使用jcl的影响?
- 依赖jcl的一个空包。
- 依赖jcl桥接到slf4j的包 jcl-over-slf4j。
- 原理:桥接包jcl-over-slf4j的类路径和jcl里面的保持一致,但是逻辑是让slf4j来做具体的日志代理。
排除系统间接引入具体的日志实现造成的影响:如果应用程序使用slf4j+logback,但是依赖的组件直接使用log4j。
- 依赖log4j的一个空包。
- 依赖log4j桥接到slf4j的包 log4j-over-slf4j。防止应用使用log4j的方式。
jcl +log4j 替换成slf4j+logback实践
若要将老的应用的日志系统从 jcl + log4j,替换成slf4j+logback,主要是依赖的jar包需要修改,再配置logback的配置文件。
- 依赖slf-api和具体的日志实现logback-core。
- 去除 jcl,log4j的依赖:将其依赖版本修改为一个空包,避免其他jar包间接引入。
- 添加 jcl和log4j 到 slf4j的连接器:log4j-over-slf4j,jcl-over-slf4j。
具体jar包依赖如下:
<!--for log start-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.2</version>
</dependency>
<!--强制使用 logback的绑定-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.2</version>
</dependency>
<!--slf4j 的桥接器,将第三方类库对 log4j 的调用 delegate 到 slf api 上-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.13</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.13</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>1.7.25</version>
</dependency>
<!--桥接器end -->
<!--强制排除 log4j 的依赖,全部 delegate 到 log4j-over-slf4j 上-->
<!--连接器只能有一个:slf4j-log4j12与logback-basic不能共存 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>99.0-does-not-exist</version>
</dependency>
<!--强制使用 logback的绑定,这里去除对log4j 的绑定-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>99.0-does-not-exist</version>
</dependency>
<dependency>
<groupId>apache-log4j</groupId>
<artifactId>log4j</artifactId>
<version>999-not-exist</version>
</dependency>
<!-- 使用slf4j框架,就要排除jcl框架-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>99.0-does-not-exist</version>
</dependency>
<!--for log end-->
系统应用日志治理
目前状况
- 应用两个门面使用混乱包含JCL和slf4j,具体实现只有log4j,但是没有预防其他包在不知情状况下引入另外的实现logback,导致应用打不出来日志且没有日志文件。
- 之前通过手动排除logback包,日志已经恢复,但未来完全不可控。
目标
- 保证日志系统持续稳定运行。
处理方案
- 日志实现使用log4j,按步不动。
- 门面全部桥接到slf4j,因为JCL本身兼容性不好会出很多问题。
- 避免其他jar包间接引入 其他日志实现包 导致日志系统崩溃。
具体实现
- 1,依赖门面slf4j、jcl 和 具体的日志实现log4j。
- 2,去除 logback 的依赖,不能去除JCL的依赖(目前线上部分在用):将其依赖版本修改为一个空包,避免其他jar包间接引入。
- 3,添加jcl和jul 到 slf4j的桥接器:jul-over-slf4j,jcl-over-slf4j。
<!--for log start-->
<!--依赖门面slf4j、jcl-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!--强制使用 log4j的绑定-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--slf4j 的桥接器-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>1.7.25</version>
</dependency>
<!--桥接器end -->
<!--强制排除 logback 的依赖-->
<!--连接器只能有一个:slf4j-log4j12与logback-basic不能共存 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>999-not-exist</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>999-not-exist-v1</version>
</dependency>
<!--for log end--