Spring Boot 日志

众说周知,Spring Boot 统一了日志框架,统一使用Logback进行日志输出,不管内部依赖框架使用的何种日志,最终都以Logback输出,他为什么需要统一呢。

我们通过引入spring-boot-starter 依赖先来看看:

认识Spring 中的日志-LMLPHP
可见,通过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中

什么是位置感知?网上没有什么资料说这个问题?但我们可以问下大模型,看看怎么回答的。
认识Spring 中的日志-LMLPHP
好像也没错,SLF4J只是一个日志的抽象,没有具体实现,确实不能记录日志的发生位置~。

SLF4J

说回SLF4J,使用方式也跟JCL一样,只需要同样的代码,就能使用不同的日志框架。
Logger logger = LoggerFactory.getLogger(Wombat.class);
但是它比JCL更先进,只需要更换不同的绑定器,而不是将日志实现硬编码在代码中

SLF4J里面有还有两个概念,绑定器和桥接器。
绑定器:SLF4J绑定具体的日志实现框架,比如logback-classic是logback的绑定器https://slf4j.org/manual.html

认识Spring 中的日志-LMLPHP

桥接器:将以前的日志框架桥接到SLF4J中,使用SLF4J来确定具体的日志框架,更多的桥接器可以到官网中查看。https://slf4j.org/legacy.html

认识Spring 中的日志-LMLPHP
SLF4J起到了一个桥梁的工作,如同一个交通指挥员,指挥不同的日志框架如何进行转换,比JCL更灵活。


02-01 07:04