目录
一. 前言
二. Log 日志体系
2.1. 背景/发展史
2.2. 关系/依赖
2.2.1. JCL(Jakarta Commons Logging)
2.2.2. SLF4J
2.2.3. SLF4J 的适配
2.2.4. Spring 统一输出
三. 总结
本文的目的是搞清楚 Java 中各种日志 Log 之间是怎样的关系,如何作用、依赖,好让我们平时在工作中如果遇到“日志打不出”或者“日志 jar 包冲突”等之类的问题知道该如何入手解决,以及在各种场景下如何调整项目中的各个框架的日志输出,使得输出统一。
在日常工作中我们可能看到项目中依赖的跟日志相关的 jar 包有很多:commons-logging.jar、log4j.jar、sl4j-api.jar、logback.jar 等等,眼花缭乱。我们要正确的配置,使用 jar 包相互作用生效之前,就先要理清它们之间的关系。
那就要从 Java Log 的发展历程开始说起。
1. log4j(作者 Ceki Gülcü)出来时就得到了广泛的应用(注意这里是直接使用),是 Java 日志事实上的标准,并成为了 Apache 的项目。
2. Apache 要求把 log4j 并入到 JDK,SUN 拒绝,并在 JDK1.4 版本后增加了 JUL(java.util.logging)。
3. 毕竟是 JDK 自带的,JUL 也有很多人用。同时还有其他日志组件,如 SimpleLog 等。这时如果有人想换成其他日志组件,如 log4j 换成 JUL,因为 api 完全不同,就需要改动代码。
4. Apache 见此,开发了 JCL(Jakarta Commons Logging),即 commons-logging-xx.jar。它只提供一套通用的日志接口 api,并不提供日志的实现。很好的设计原则嘛,依赖抽象而非实现。这样应用程序可以在运行时选择自己想要的日志实现组件。
5. 这样看上去也挺美好的,但是 log4j 的作者觉得 JCL 不好用,自己开发出 slf4j,它跟 JCL 类似,本身不提供日志具体实现,只对外提供接口或门面。目的就是为了替代 JCL。同时,还开发出logback,一个比 log4j 拥有更高性能的组件,目的是为了替代 log4j。
6. Apache 参考了 logback,并做了一系列优化,推出了 log4j2,性能远超 logbak。
大概了解心路历程后,再详细看看它们之间的关系、依赖。
commons-logging 已经停止更新,最后的状态如下所示:
JCL 支持日志组件不多,不过也还是有很人用的,只是现在用的人越来越少了,就不多讲了。
因为当时 Java 的日志组件比较混乱繁杂,Ceki Gülcü 推出 slf4j 后,也相应为行业中各个主流日志组件推出了 slf4j 的适配,图来源于官方文档:
slf4j 适配图的意思为,如果你想用 slf4j 作为日志门面的话,你如何去配合使用其他日志实现组件,这里说明一下(注意 jar 包名缺少了版本号,在找版本时也要注意版本之间是否兼容)。
slf4j 支持各种适配,无论你现在是用哪种日志组件,你都可以通过 slf4j 的适配器来使用上slf4j。只要你切换到了 slf4j,那么再通过 slf4j 用上实现组件,即上面说的。图来源于官方文档:
其实总的来说,无非就是以下几种情况:
给大家一个整体的依赖图:
这就是为了对 slf4j 的适配做一个例子说明。Spring 是用 JCL 作为日志门面的,那我们的应用是slf4j + logback,怎么让 Spring 也用到 logback 作为日志输出呢?这样的好处就是我们可以统一项目内的其他模块、框架的日志输出(日志格式,日志文件,存放路径等,以及其他 slf4j 支持的功能),很简单,就是加入 jcl-over-slf4j.jar 就好了。请看下图:
适配思路:
slf4j 的日志加载会在程序启动时把日志打出来,所以一定要注意,它会说明加载是否成功,加载了那个日志实现。slf4j 已经对错误作了说明,下面讲一下可能经常遇到的问题。
1. Failed to load class org.slf4j.impl.StaticLoggerBinder
没找到日志实现,如果你觉得你已经写上了对应的日志实现依赖了,那你要检查一下了,一般来说极有可能是版本不兼容。
2. Multiple bindings
找到多个日志实现,slf4j 会找其中一个作为日志实现。
3. 阿里对此的代码规范
【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架 SLF4J 中的 API,
使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class);