这篇文章你将了解到Spring生态中日志框架是如何演化集成的
Spring Boot 日志
众说周知,Spring Boot 统一了日志框架,统一使用Logback进行日志输出,不管内部依赖框架使用的何种日志,最终都以Logback输出,他为什么需要统一呢。
我们通过引入spring-boot-starter
依赖先来看看:
可见,通过spring-boot-starter引入了spring-boot、spring-boot-autoconfigure、spring-boot-starter-logging、jakarta.annotation-api、spring-core等;其中spring-boot-starter-logging主要引入日志相关的依赖,有logback-classic、log4j-to-slf4j、jul-to-slf4j,正因为有这些依赖spring boot 才能够统一日志框架,我们先看看这几个依赖的作用。
logback-classic: logback的主要日志依赖
log4j-to-slf4j:将lo4j日志桥接到了slf4j日志框架,spring 框架默认使用的,所以需要该依赖
jul-to-slf4j:将jul日志桥接到了slf4j日志框架,tomcat框架默认使用的,jul 即Java Util Logging
正因为Spring Boot 使用到了Spring、Tomcat,但两者都各自使用了不同的日志框架,后面可能还有其他依赖框架,混乱的日志框架势必会对开发人员造成困扰。
这里我们看到两个依赖都桥接到了slf4j,那什么是slf4j?大部分人应该都知道slf4j是一种抽象日志框架,要能够输出日志,slf4j还需要绑定到具体的日志框架,比如logback-classic。那为什么会出来slf4j呢。
到目前为止Java生态有很多的日志框架:logback、log4j、log4j2、simpleLog、JUL等。这么多的日志框架,你想象一下,如果开始的时候你使用的JUL打印日志,随着新技术的发展又出来了log4j,另一波开发人员又引入了log4j,后面又出现了log4j2,多种日志框架充斥着系统,改配置就得改不同的文件,给维护带来很多麻烦。
Spring 日志
后来出现了Commons Logging(JCL),其通过统一的写法统一了框架日志,不需要根据不同的日志框架,不同的日志编码。
private Log log = LogFactory.getLog(CLASS.class);
通过如上编码,JCL会去寻找合适的日志框架,通过找到的日志框架打印日志。
但是随着时间的推移,JCL 在2014年后就没更新(2023年JCL又开始更新了一版1.3.0),后面被越来越多的人弃用,但是就如上面说的Spring 还是一直使用的JCL作为默认日志框架。
在Spring框架中有一个spring-jcl依赖,其中LogAdapter
有一个静态方法块,用来选择要加载的Log API
private static final String LOG4J_SPI = "org.apache.logging.log4j.spi.ExtendedLogger";
private static final String LOG4J_SLF4J_PROVIDER = "org.apache.logging.slf4j.SLF4JProvider";
private static final String SLF4J_SPI = "org.slf4j.spi.LocationAwareLogger";
private static final String SLF4J_API = "org.slf4j.Logger";
private static final LogApi logApi;
static {
if (isPresent(LOG4J_SPI)) {
if (isPresent(LOG4J_SLF4J_PROVIDER) && isPresent(SLF4J_SPI)) {
// log4j-to-slf4j bridge -> we'll rather go with the SLF4J SPI;
// however, we still prefer Log4j over the plain SLF4J API since
// the latter does not have location awareness support.
logApi = LogApi.SLF4J_LAL;
}
else {
// Use Log4j 2.x directly, including location awareness support
logApi = LogApi.LOG4J;
}
}
else if (isPresent(SLF4J_SPI)) {
// Full SLF4J SPI including location awareness support
logApi = LogApi.SLF4J_LAL;
}
else if (isPresent(SLF4J_API)) {
// Minimal SLF4J API without location awareness support
logApi = LogApi.SLF4J;
}
else {
// java.util.logging as default
logApi = LogApi.JUL;
}
}
如上,有一句话很有意思,
log4j-to-slf4j bridge -> we'll rather go with the SLF4J SPI; however, we still prefer Log4j over the plain SLF4J API since the latter does not have location awareness support.
如果存在log4j-to-slf4j 桥接器,那么就选择SLF4J ,但是我们还是更喜欢使用Log4j而不是SLF4J API,因为它没有位置感知支持。
LOG4J_SPI 存在于 log4j包中
LOG4J_SLF4J_PROVIDER 存在于log4j-to-slf4j中
SLF4J_SPI 存在于slf4j中
SLF4J_API存在于slf4j中
什么是位置感知?网上没有什么资料说这个问题?但我们可以问下大模型,看看怎么回答的。
好像也没错,SLF4J只是一个日志的抽象,没有具体实现,确实不能记录日志的发生位置~。
SLF4J
说回SLF4J,使用方式也跟JCL一样,只需要同样的代码,就能使用不同的日志框架。
Logger logger = LoggerFactory.getLogger(Wombat.class);
但是它比JCL更先进,只需要更换不同的绑定器,而不是将日志实现硬编码在代码中
SLF4J里面有还有两个概念,绑定器和桥接器。
绑定器:SLF4J绑定具体的日志实现框架,比如logback-classic是logback的绑定器https://slf4j.org/manual.html
桥接器:将以前的日志框架桥接到SLF4J中,使用SLF4J来确定具体的日志框架,更多的桥接器可以到官网中查看。https://slf4j.org/legacy.html
SLF4J起到了一个桥梁的工作,如同一个交通指挥员,指挥不同的日志框架如何进行转换,比JCL更灵活。
作者其他文章推荐:
基于Spring Boot 3.1.0 系列文章
- Spring Boot 源码阅读初始化环境搭建
- Spring Boot 框架整体启动流程详解
- Spring Boot 系统初始化器详解
- Spring Boot 监听器详解
- Spring Boot banner详解
- Spring Boot 属性配置解析
- Spring Boot 属性加载原理解析
- Spring Boot 异常报告器解析
- Spring Boot 3.x 自动配置详解