说到 Logger 日志的动态绑定, 主要归功与 Slf4j, 在之前的文章也说过, Slf4j 是类似于 Apache Common-Logging, 英文为 Simple Logging Facade, 是一个简单的日志门面适配器, 所有的日志代码都可以用 slf4j 方式, 它会根据项目具体依赖的日志实现包进行日志操作, 只需修改 pom.xml 文件中的日志实现依赖, 对于 Log4j,Log4j2 和 Logback 等都有相应的桥接包, 对相应的 slf4j-API 接口实现.
那么 slf4j 是如何自动绑定上的呢.
多个框架是如何选择的呢.
Slf4j 框架架构原理
日志的绑定原理主要是依赖 Slf4j 的静态加载, 从相应的实现框架中获取 Logger. 需要自己动手一步步跟踪下去才会感触良多.
Slf4j 的实现流程:
Logger 日志打印调用流程:
在 slf4j 中主要是实现 LoggerFactoryBinder 的 StaticLoggerBinder 采用单例模式, 编译时期静态加载方式, 得到不同的 ILoggerFactory 工厂的实现类, 最终拿到相应框架所匹配的 Logger.
对于 Slf4j 的源码分析我这边就不再过多的赘余, 关键得自己去理解的看看, 推荐一个: Java 日志体系(slf4j)
介绍一下 slf4j 中主要的几个类的作用:
Logger:slf4j 日志接口类, 提供了 trace <debug < info < warn < error 这 5 个级别对应的方法, 主要提供了占位符 {} 的日志打印方式;
Log4jLoggerAdapter:Logger 适配器, 主要对 org.apache.log4j.Logger 对象的封装, 占位符 {} 日志打印的方式在此类中实现;
LoggerFactory: 日志工厂类, 获取实际的日志工厂类, 获取相应的日志实现对象;
lLoggerFactory: 底层日志框架中日志工厂的中介, 再其实现类中, 通过底层日志框架中的日志工厂获取对应的日志对象;
StaticLoggerBinder: 静态日志对象绑定, 在编译期确定底层日志框架, 获取实际的日志工厂, 也就是 lLoggerFactory 的实现类;
项目中有多种日志框架时会存在多个 StaticLoggerBinder 时候获取第一个, 主要是采用类的顺序加载机制, 与 Pom 文件中依赖的填写顺序有关(个人实践得到).
每一种日志框架都有相应的桥接包.
首先先看 Slf4j-API 包结构: 主要定义了一些接口做相应的适配, 其包结构主要是 org.slf4j 其他实现框架都是根据其进行相应的桥接
Log4j 的桥接包形式: 相同包名, StaticLoggerBinder 采用构造器形式获取 Log4jLoggerFactory 工厂
Logback 的桥接包形式: 相同包名, 在 StaticLoggerBinder 调用时就已经静态初始化 LoggerContext 工厂
Logback 的详细配置:
Log4j2 的桥接包形式: 相同包名, 与 Log4j 类似采用构造器形式获取 Log4jLoggerFactory 工厂
Log4j2 的详细配置: Java 日志体系(log4j2)
在官方文档中 slf4j 与其他日志框架如何绑定的示意图(大家都放我也放一下~)
以下是个人收集一些比较简单好理解的绑定关系图, 体现了 StaticLoggerBinder 绑定的重要性
绑定过程中所遇到的问题
1,Log4j 和 Log4j2 需要配置好 log4j.xml 和 log4j2.xml 文件, 不然无法正常使用, Logback 可以正常使用
2, 与 SpringBoot 的 spring-boot-starter 中的依赖 spring-boot-starter-logging 内部的依赖 Logback 产生冲突(可以自己点击查看一番)
- Caused by: java.lang.IllegalArgumentException: LoggerFactory is not a Logback LoggerContext but Logback is on the classpath. Either remove Logback or the competing implementation (class org.slf4j.impl.Log4jLoggerFactory loaded from file:/Users/zhouguanglin/.m2/repository/org/slf4j/slf4j-log4j12/1.7.29/slf4j-log4j12-1.7.29.jar). If you are using webLogic you will need to add 'org.slf4j' to prefer-application-packages in Web-INF/weblogic.xml: org.slf4j.impl.Log4jLoggerFactory
- at org.springframework.util.Assert.instanceCheckFailed(Assert.java:699)
- at org.springframework.util.Assert.isInstanceOf(Assert.java:599)
- at org.springframework.boot.logging.logback.LogbackLoggingSystem.getLoggerContext(LogbackLoggingSystem.java:284)
- at org.springframework.boot.logging.logback.LogbackLoggingSystem.beforeInitialize(LogbackLoggingSystem.java:104)
- at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationStartingEvent(LoggingApplicationListener.java:232)
- at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEvent(LoggingApplicationListener.java:213)
主要原因是: SpringBoot 所使用的搭配日志框架是 Logback
若是项目使用 SpringBoot 但搭配的是其他日志框架 A, 由 StaticLoggerBinder.getSingleton().getLoggerFactory()获取的是搭配框架 A 的实现, 导致与 SpringBoot 内部自身搭配的 Logback 产生冲突, 可以进入报错地方查看.
从代码逻辑中可以发现, Assert.isInstanceOf 判断获取得到的 ILoggerFactory 的实现类必须是 LoggerContext 才 OK, 硬性规定不然就会报错.
解决方案: 只要在 SpringBoot 中排除 Logback 的依赖就 OK, 如下:
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter</artifactId>
- <exclusions>
- <exclusion>
- <artifactId>logback-classic</artifactId>
- <groupId>ch.qos.logback</groupId>
- </exclusion>
- </exclusions>
- </dependency>
来源: https://www.cnblogs.com/zhouguanglin/p/13804013.html