Java日志框架生态

近期Log4j2的安全漏洞影响了全世界Java技术生态的大部分软件系统,在之后的几天发布了若干个版本修复安全漏洞。公司内部的框架近期也在逐步升级,供各业务团队直接使用,在这个过程中顺便梳理了下Java日志框架生态。很多同学分不清楚Log4j,Logback,SLF4j,Log4j2等框架,对于相关的依赖包简直是一头雾水,本文结合之前的一些记录,完整梳理了当前流行的日志框架及推荐使用方式。

Java日志简史

时间 事件
2002年2月 JDK1.4发布,新增java.util.logging默认日志实现
2005年11月 Log4j发布1.1.3版本
2006年08月 Logback发布0.2.5版本
2006年12月 SLF4J发布正式版1.1.0版本
2011年7月 JBoss Logging发布3.0.0.GA版本
2014年7月 Log4j发布2.0版本
2017年09月 JDK9发布,新增System.Logger类

统计时间 2021年12月,其中版本数据来源于mvnrepository

日志框架设计

不难想象,让进程输出自身内部状态的方法就是通过操作系统进程的标准输出与错误输出。为了更好的扩展日志的特性,如日志级别,分类存储,文件滚动等,在初期没有成熟的日志框架出现时大家纷纷各自实现,这在后续统一标准时带来了很多问题。接下来梳理当前Java生态的各类日志框架。

接口门面

像SLF4j和Apache Commons Logging这类型属于日志门面,只定义了日志打印规范,相当于接口。其中Log4j2中的log4j-api也是这个作用,比较特别的是其定义了接口规范后,自己也做了相对应的实现,即log4j-core。

门面的作用太重要了,这是SOLID六大设计原则之一接口隔离的典型代表。Java日志框架发展历史悠久,衍生了多种日志框架实现,有统一的日志框架接口,使得在应用在引用各类第三方依赖时不必纠结其使用了什么日志实现。

当前来看,SLF4J是接口门面的最好选择,大部分生态对对其进行了适配,选它就行了。

具体实现

接口有了就需要日志实现,Logback就是其中一个广泛使用的日志实现,它是与SLF4J同时期出现的产物,也来自于同一作者之手,目前也是Spring Boot生态默认的日志实现。

2014年Log4j2诞生,成为了日志实现的又一选择,其最大的亮点是性能优势,Log4j2采用LMAX Disruptor无锁内存队列实现。下图是来自于官方的性能测试的其中一张截图,在异步打印场景下其吞吐量特别高,吊打其他日志框架。为什么性能如此重要?在一些高QPS的服务,如果日志框架的性能不够强,会限制业务服务吞吐,还有可能影响业务应用,一个业务无关的日志记录器影响了业务服务稳定性,是非常不值得的。所以在后来,有很多Java应用开始尝试使用Log4j2作为其日志实现。

除了Logback和Log4j2,还有想没有实现SLF4J的早期日志框架,如JDK内置的java.util.logging与Log4j,这些SLF4J额外做了一些适配包来适配,在下一部分会介绍到,不过不用太关注,因为不推荐这些作为日志实现。

尽管这次Log4j2安全漏洞席卷全球,但是如果再次选型日志实现,我还是推荐高性能的Log4j2。

image-20220105223228662

日志适配

当我们在开发应用时,会引用很多第三方组件,这些第三方组件来自不同的开发人员,他们使用不同的日志框架来打印日志。为了使得自己编写的代码中的日志打印与第三方组件的日志打印能够统一控制,我们可以选定某一种日志框架作为实现,而剩余的所有日志框架只需要在打印时适配转发路由到我们指定的日志框架就可以了。

简单来说,就是你选定某个日志框架后,剩余的其他日志框架适配工作就交给各个适配的依赖包来做就行了,你只需要选好正确的日志依赖即可。举个例子,比如你的项目确定使用Logback,而应用的一个X组件内部使用的是log4j,此时你可以考虑添加log4j-over-slf4j这个依赖包,让这个X组件用log4j打印的逻辑适配SLF4J,最终由Logback输出日志。

日志框架依赖

为了方便了解各个日志框架包之间的依赖关系,我梳理了一下这张图方便大家理解。

  • 图中红色字体的是日志门面的框架包,这类型的包在应用的classpath下无影响,其只是个接口。
  • 具体的日志实现包选择好其中一个就好,如图中四条红色的线对应了日志实现,建议选择Log4j2或Logback。
  • 其他的线就是一些日志适配包,选型日志实现后挑选一些需要的适配包依赖放到classpath下,做日志适配使用。

logging-framework

最佳实践

日志框架不去了解清楚的话,确实会一头雾水,希望上文能帮助到你了解日志框架。这里罗列几个最佳实践:

  • 开发代码打印日志时,推荐使用SLF4J的api来打印日志,使得代码与具体的日志实现解耦
  • 开发SDK供他人使用时,不用传递依赖具体的日志实现,如log4j,logback,log4j-core等
  • 对于高性能场景,推荐使用SLF4J + Log4j2组合作为日志框架使用,并开启异步打印特性
  • 不要用多种日志实现,控制好日志适配包的使用避免循环依赖,使得启动时死循环导致栈溢出
  • 不要滥用日志打印,高频率打印日志落盘文件在日志归档压缩时会消耗cpu,影响业务系统运行
  • 不要滥用日志打印,在有日志收集的场景下,存储成本是昂贵的,只打印有价值的日志
  • 建议有统一的日志打印规范,可以参考下[Java开发手册](https://g ithub.com/alibaba/p3c/blob/master/Java开发手册(嵩山版).pdf)日志规约部分,建立自己团队内部的使用规范

总结

以上介绍了Java日志框架的生态,相关历史大家可以自行了解下。最主要还是梳理好日志框架之间的关系,在开发过程中用好日志框架,避免依赖混乱给自己挖坑,开发SDK时也避免给别人挖坑。这些只是开头,日志框架看似简单,在高吞吐的服务打印的每一行日志都要认真思考,要保障服务的稳定性,你必须认真对待每一个细节。

References