今天来聊一聊 Java 日志框架,不管是在项目开发阶段的调试,还是项目上线后的运行,都离不开日志。日志具有处理历史数据、定位程序问题、理解程序运行过程等重要作用。在 Spring 项目开发过程中我们常见的日志框架可能就是 logback、log4j2 和 SLF4J。今天就一起系统地了解下 Java 日志框架。
发展史
所有的技术的出现都是有缘由的,了解某个技术出现的来龙去脉,对技术也能更好的理解。
日志框架出现顺序: log4j --> JUL --> JCL --> SLF4J --> logback --> log4j2
最开始没有日志框架,在代码中使用 System.out、System.err 打印日志信息,没有日志级别控制,使用不灵活。
1996年初,EU SEMPER 项目决定编写自己的项目跟踪 API,经过无数次增强,最终发展称为 log4j。后来 log4j 成了 Apache 基金会的一员,让 log4j 一度称为了业界的日志标杆。log4j 主要由瑞士程序员 Ceki Gülcü 贡献开发。
sun 公司在2002年2月推出了 java 1.4 发布,推出了自己的日志库 java.util.logging,其实很多日志思想也都仿照 log4j。
Apache 针对刚出来的 JUL,搞了一套 JCL,打算一统日志江湖,制定日志标准,JCL 是日志抽象层,让日志产品去实现它的抽象,只要你的日志代码实现 JCL 接口就可以很方便的在 log4j 和 JUL 之间切换。但是好景不长,随着 JCL 的应用,人们发现 JCL 带来的问题比它解决的问题还要多。
这个时候 SLF4J 应运而生,依然是 Ceki Gülcü ,在2005年自己搞了一个新东西,也是一套日志接口,也有称之为日志门面,Slf4j 诞生了,后来也证明了,Slf4j 比 JCL 要更加优秀。在 SLF4J 之后,Ceki 基于之前已有的日志框架(log4j、JUL)又开发出了对应的适配器,至此,SLF4J 统一了日志接口,兼容已有日志框架,后来又重新写了一套基于 SLF4J 的实现 logback, logback完全实现 SLF4J 接口。
2012年 Apache 推出了自己的新项目 log4j2,是对 log4j 的一次大升级,因为 log4j2 完全不兼容 log4j1.x,而且 log4j2 几乎涵盖了 logback 的全部新特性,log4j2 也搞了分离式设计,分化成 log4j-api 和 log4j-core,这个 log4j-api 也是日志接口,log4j-core才是日志产品。
目前主流的使用组合是 SLF4J + log4j2 / logback
日志门面
- JCL(Jakarta Commons Logging)(主要配合log4j, JUL 使用,如新增日志框架,需要修改源码)
- SLF4J (Simple Logging Facade for Java) (主流的日志规范接口)
日志实现
- JUL:java.util.logging Java原生日志框架,JDK自带,使用时不需要额外依赖包。
- Log4j:Apache 的开源项目,由于需要与非常旧的 Java 版本兼容,它变得更加难以维护,并 于 2015 年 8 月停止使用。
- Logbcak :由 Log4j 之父做的另一个开源项目,是一个可靠、通用且灵活的Java日志框架。
- Log4j2 :Log4j 的升级版,各个方面与 logback 及其相似。具有插件式结构、配置文件优化等特征。
- slf4j-simple: SLF4J 自带的简单日志实现。
- 其他
SLF4J
SLF4J 主要是为了给Java日志访问提供一套标准、规范的API框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架,例如 log4j 和 logback 等。当然 SLF4J 自己也提供了功能较为简单的实现 slf4j-simple,但是一般很少用到。
SLF4J 绑定日志框架
SLF4J 的日志绑定流程:
-
添加 slf4j-api 的依赖
-
使用 slf4j 的API在项目中进行统一的日志记录
-
绑定具体的日志实现框架
(1) 绑定已经实现了 SLF4J 的日志框架,直接添加对应依赖
(2) 绑定没有实现 SLF4J 的日志框架,先添加日志的适配器,再添加实现类的依赖
(3) slf4j有且仅有一个日志实现框架的绑定(如果出现多个默认使用第一个依赖日志实现)
SLF4J 不依赖于任何特殊的类加载器机制。事实上,每个 SLF4J 绑定在编译时都被硬连线 以使用一个且唯一一个特定的日志记录框架。例如,slf4j-log4j12-2.0.7.jar 绑定在编译时绑定以使用 log4j。在代码中,除了slf4j-api-2.0.7.jar之外,只需将一个且仅一个 日志实现jar 包放到适当的类路径位置即可。不要在类路径上放置多个绑定。
下图是官网提供的SLF4J 和日志实现的绑定,slf4j-api.jar是SLF4J定义的日志规范接口,下图对应的jar是在使用日志框架是需要依赖的jar包。
在项目使用 SLF4J 时,如果只引入 slf4j-api.jar,没有引入具体的日志实现,则不会绑定任何日志实现框架,也就不能输出日志。
logback 和 slf4j-simple ,由于是在 SLF4J 之后开发的,因此直接实现的 SLF4J 接口,可以直接导入实现包就可以自动绑定。
reload4j 和 JUL 由于在 SLF4J 规范出现之前已经有了,因此如果要使用SLF4J规范,需要引入适配器进行适配。(技术黑话:没有什么是加一层解决不了的)
绑定原理
-
SLF4J 通过 LoggerFactory 加载日志具体的实现对象。
-
LoggerFactory 在初始化的过程中,会通过 erformInitialization() 方法绑定具体的日志实现。
-
在绑定具体实现的时候,通过类加载器,加载 org/slf4j/impl/StaticLoggerBinder.class
-
所以,只要是一个日志实现框架,在 org.slf4j.impl 包中提供一个自己的 StaticLoggerBinder 类,在其中提供具体日志实现的LoggerFactory 就可以被 SLF4J 所加载
SLF4J日志级别
error > warn > info > debug > trace
为什么要使用日志门面?
1、面向接口编程,减少代码耦合,符合开闭原则。
2、使用日志门面,通过导入不同依赖可以灵活切换日志框架。
3、统一API,统一配置便于项目日志的使用和管理。
入门示例
1、引入依赖
<!--slf4j core 使用slf4j必須添加-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.27</version>
</dependency>
<!--slf4j 自带的简单日志实现 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.27</version>
</dependency>
2、编码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
logger.info("Hello World");
}
}
或使用 lombok 的注解 @Slf4j (编译后同样转化成上面的代码,仅简化了开发)
@Slf4j
public class HelloWorld {
public static void main(String[] args) {
log.info("Hello World");
}
}
小结:
- SLF4J 作为日志接口门面,规范并统一了日志接口。个人觉得未来在Java项目中很难有其替代者,很可能会有更好的日志实现框架出现。
- 技术更迭十分快速,如果理解技术及相关产品的发展历程及主要解决什么问题,对越来越多的技术名词也不再特别恐惧。
参考文档:
SLF4J官网手册
java日志发展史 log4j slf4j log4j2 jul jcl 日志和各种桥接包的关系
Java 日志体系(二)jcl 和 slf4j