文章目录
- 1 楔子
- 2 jcl原理分析
- 2.1 依赖坐标
- 2.2 API调用
- 2.3 源码分析
- 3 slf4j原理分析
- 3.1 依赖坐标
- 3.2 API调用
- 3.3 源码分析
- 4 spring是如何选择日志技术的?
- 4.1 场景一:通过log4j2打印日志
- 4.1.1 引入maven依赖
- 4.1.2 编写配置文件
- 4.1.3 执行测试方法
- 4.1.4 测试结果
- 4.2 场景二:通过logback打印日志
- 4.2.1 引入maven依赖
- 4.2.2 编写配置文件
- 4.2.3 执行测试方法
- 4.2.4 测试结果
- 4.3 情景三:log4j2与logback共存,但不将log4j2绑定到slf4j
- 4.3.1 引入maven依赖
- 4.3.2 编写配置文件
- 4.3.3 执行测试方法
- 4.3.4 测试结果
- 4.3.5 源码分析
- 5 整合日志
- 5.1 引入maven依赖
- 5.2 编写配置文件
- 5.3 执行测试方法
- 5.4 测试结果
- 6 思考
- 6.1 springboot的日志体系是怎么实现的呢?
✨这里是第七人格的博客✨小七,欢迎您的到来~✨
🍅系列专栏:【Spring源码解析】🍅
✈️本篇内容: Spring日志原理✈️
🍱本篇收录完整代码地址:https://gitee.com/diqirenge/spring-book/tree/master/spring-log🍱
1 楔子
阅读spring源码,难免会在源码上进行改动,如添加日志,增加注释等,为了可以更好的学习spring源码,理解spring日志原理是非常有必要的。
目前市面上常见的日志框架有jul、log4j、log4j2以及logback,常用的日志整合技术有jcl以及slf4j。
2 jcl原理分析
2.1 依赖坐标
<!-- jcl -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
2.2 API调用
org.apache.commons.logging.Log logger = org.apache.commons.logging.LogFactory.getLog("jcl");
logger.info("jcl");
2.3 源码分析
跟进LogFactory.getLog源码,我们会发现,代码会调用LogFactoryImpl的newInstance方法,而newInstance方法内部会调用discoverLogImplementation方法,
if (isDiagnosticsEnabled()) {
logDiagnostic(
"No user-specified Log implementation; performing discovery" +
" using the standard supported logging implementations...");
}
for(int i=0; i<classesToDiscover.length && result == null; ++i) {
result = createLogFromClass(classesToDiscover[i], logCategory, true);
}
分析discoverLogImplementation方法我们可以知道,JCL内置了一个字符串数组classesToDiscover,这个数组当中保存了类名。
private static final String[] classesToDiscover = {
LOGGING_IMPL_LOG4J_LOGGER,
"org.apache.commons.logging.impl.Jdk14Logger",
"org.apache.commons.logging.impl.Jdk13LumberjackLogger",
"org.apache.commons.logging.impl.SimpleLog"
};
也就是说jcl在调用getLogger方法去实例化一个logger对象的时候,会遍历一个保存了日志类名的数组,如果能够通过class.forName加载到,那么就使用该日志技术。
jcl作为日志整合技术,可以根据用户提供的具体日志技术去实例化具体的对象,但是由于它已经不更新了,所以它的内置数组,目前最多只有4个。
注:spring5.x有个spring-jcl的模块,就是为了解决jcl不更新了的问题。
3 slf4j原理分析
slf4j的核心思想和jcl是一致的,只是它是通过将具体的日志技术和绑定器绑在一起来工作的,假设有新的日志出现可以发布新的绑定器来实现扩展。官网地址:https://www.slf4j.org/
3.1 依赖坐标
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.32</version>
</dependency>
3.2 API调用
org.slf4j.Logger slf4j = LoggerFactory.getLogger("slf4j");
slf4j.info("slf4j");
3.3 源码分析
private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
public static String REQUESTED_API_VERSION = "1.6.99";
private static final String loggerFactoryClassStr =
Log4jLoggerFactory.class.getName();
private final ILoggerFactory loggerFactory = new Log4jLoggerFactory();
slf4j绑定器解决了,新日志技术的拓展适配问题,但是它还是没有解决以前日志技术的历史编码问题。
为了解决历史编码问题,slf4j引入了桥接器的概念,顾名思义,就是将某个日志技术,桥接到slf4j,再由slf4j的绑定器决定具体使用哪一个日志技术。
接下来,我们通过以下几个场景来分析一下spring是如何选择自己想用的日志技术的。
4 spring是如何选择日志技术的?
4.1 场景一:通过log4j2打印日志
4.1.1 引入maven依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- log4j2依赖-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
</dependency>
<!-- log4j2-slf4j-impl 绑定器 绑定log4j2 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
</dependency>
</dependencies>
4.1.2 编写配置文件
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="log4j2 ===> %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="org.springframework.beans.factory" level="DEBUG"/>
<Logger name="com.sheep.log.Log4j2Log" level="DEBUG"/>
<Root level="debug">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
4.1.3 执行测试方法
@Slf4j
public class Log4j2Log {
public static void main(String[] args) {
AnnotationConfigApplicationContext
context = new AnnotationConfigApplicationContext();
context.refresh();
log.info("我是log4j2在项目中打印的日志");
}
}
4.1.4 测试结果
4.2 场景二:通过logback打印日志
4.2.1 引入maven依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- logback依赖 start-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<!-- logback依赖 end-->
</dependencies>
4.2.2 编写配置文件
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>logback===> %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{35}[%line] - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<logger name="e" level="debug" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>
<root level="debug">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
4.2.3 执行测试方法
@Slf4j
public class LogbackLog {
public static void main(String[] args) {
AnnotationConfigApplicationContext
context = new AnnotationConfigApplicationContext();
context.refresh();
log.info("我是logback在项目中打印的日志");
}
}
4.2.4 测试结果
4.3 情景三:log4j2与logback共存,但不将log4j2绑定到slf4j
4.3.1 引入maven依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- log4j2依赖 start-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
</dependency>
<!-- logback依赖 start-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<!-- logback依赖 end-->
</dependencies>
4.3.2 编写配置文件
配置文件不变,同情景一和二
4.3.3 执行测试方法
我们执行LogbackLog类的main方法
4.3.4 测试结果
4.3.5 源码分析
项目日志是通过logback打印的,而spring框架日志是通过log4j2打印的,也就是说spring没有采用我们所配置的logback。
通过查询源码可知,logger对象是通过LogFactory生成的
而LogFactory是通过调用LogAdapter.createLog(name);方法获取到logger的
createLog方法是一个简单的分支语句,通过logApi对象判断,所以我们接下来需要看看这个logApi是怎么初始化的,所幸我们在LogAdapter的静态块中找到了logApi的初始化代码
通过以上代码我们可以知道,Spring是优先使用log4j2的日志技术的,除非用户强行要求使用slf4j技术( 如果有log4j2到slf4j的桥接器,并且还有slf4j,那么就用slf4j)。
5 整合日志
基于前文的分析,我们可以通过使用slf4j的桥接器和绑定器,以及具体日志技术,完成日志技术的统一。思路如下:
将所用到的日志技术桥接到 slf4j,再将slf4j与具体的日志技术绑定,这里我们绑定到logback。
5.1 引入maven依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- log4j2依赖 start-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
</dependency>
<!-- logback依赖 start-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<!-- logback依赖 end-->
<!-- log4j2桥接器-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>2.9.1</version>
</dependency>
<!-- jul桥接器-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>1.7.26</version>
</dependency>
</dependencies>
5.2 编写配置文件
同情景三
5.3 执行测试方法
@Slf4j
public class AllLogToBackLog {
public static void main(String[] args) {
Logger logger = Logger.getLogger(String.valueOf(JucLog.class));
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
logger.info("我是juc在项目中打印的日志");
Logger log4j2 = Logger.getLogger ( AllLogToBackLog.class.getName () );
log4j2.info("我是log4j2在项目中打印的日志");
AnnotationConfigApplicationContext
context = new AnnotationConfigApplicationContext();
context.refresh();
log.info("我是logback在项目中打印的日志");
}
}
5.4 测试结果
logback===>2023-04-17 23:36:07.221 [main] INFO class com.sheep.log.JucLog[23] - 我是juc在项目中打印的日志
logback===>2023-04-17 23:36:07.223 [main] INFO com.sheep.log.AllLogToBackLog[27] - 我是log4j2在项目中打印的日志
logback===>2023-04-17 23:36:07.282 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext[596] - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@246ae04d
logback===>2023-04-17 23:36:07.288 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory[225] - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
logback===>2023-04-17 23:36:07.297 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory[225] - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
logback===>2023-04-17 23:36:07.298 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory[225] - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
logback===>2023-04-17 23:36:07.299 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory[225] - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
logback===>2023-04-17 23:36:07.300 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory[225] - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
logback===>2023-04-17 23:36:07.318 [main] INFO com.sheep.log.AllLogToBackLog[33] - 我是logback在项目中打印的日志
6 思考
6.1 springboot的日志体系是怎么实现的呢?
提示