新接手一项目,在本地测试运行时,发现报如下错误:1 Detected both log4j-over-slf4j.jar AND slf4j-log4j12.jar on the class path , preempting StackOverflowError .
很明显,是项目的classpath中同时存在log4j-over-slf4j.jar和slf4j-log4j12.jar,引起死循环。但为什么项目在线上能好好运行呢? 原来线上运行的程序在打包时做了手脚,在最终的依赖包中,删除了log4j和slf4j-log4j12,如下:1 2 3 4 5 6 7 8 9 10 <dependencySets> <dependencySet> <outputDirectory>lib</outputDirectory> <excludes> <exclude>log4j:log4j</exclude> <exclude>org.slf4j:slf4j-log4j12</exclude> </excludes> <useProjectArtifact>false</useProjectArtifact> </dependencySet> </dependencySets>
让我们来重头捋一下Java日志框架及该问题的成因。
我们用的最多的几个日志框架有Log4j、Jakarta Commons Logging以及Logback-classic。尤其值得一提的是Logback-classic,它是Log4j作者写的又一个开源日志组件,由于实现了SLF4J’s的Logger接口,native,性能优越,可以直接作为SLF4J的实现。
SLF4J ,全称Simple Logging Facade for Java,是一个抽象的日志框架,支持程序动态绑定日志框架实现,如绑定到java.util.logging
、logback
以及 log4j
等,使用时需要在应用中添加依赖slf4j-api。
SLF4J对应用日志做了封装抽象,因此,业务可以针对SLF4J进行编程,再按需绑定具体的日志框架。无论是灵活性还是可扩展性,都有很多的提升。
为了支持动态绑定不同的日志框架,SLF4J提供了几个绑定工具,如下:
绑定
说明
slf4j-log4j12
Log4j绑定,Log4j是一个使用非常广泛的日志组件
slf4j-jcl
Jakarta Commons Logging绑定
slf4j-jdk14
java.util.logging, JDK绑定
slf4j-simple
输出会定向到System.err,只有INFO级别以上的才会输出
slf4j-nop
丢弃日志绑定
为了方便jcl、log4j、jul用户迁移到SLF4J,SLF4J提供了几个桥接模块。
模块
目标对象
使用说明
实现原理
log4j-over-slf4j
log4j用户
将log4j.jar替换为log4j-over-slf4j.jar
直接替换了log4j中的大量同名类,如Logger、Category、Level等
jcl-over-slf4j
jcl用户
将commons-logging.jar替换为jcl-over-slf4j.jar
保留了jcl的接口,但底层实现采用了slf4j
jul-to-slf4j
jul用户
直接依赖
由于jul在java.*namespace下,无法替换。该模块采用了翻译手段,将LogRecord转换为slf4j类似对象。 该方案存在一定的性能损失,不推荐使用
按上面的介绍,使用slf4j时就需要注意一下几个问题:
jcl-over-slf4j.jar
和slf4j-jcl.jar
不能同时依赖!
原因:jcl会将实现委托到slf4j,而slf4j又会绑定到jcl。
log4j-over-slf4j.jar
和slf4j-log4j12.jar
不能同时依赖!
原因: log4j会将实现委托到slf4j,而slf4j又会绑定到log4j实现。
jul-to-slf4j.jar
和slf4j-jdk14.jar
不能同时依赖!
原因:jul会将实现委托到slf4j,而slf4j又会绑定到jdk实现。
上述三个情况下,若同时依赖均会产生循环依赖问题,导致StackOverflowError!
回到开头的问题,即上述第2个注意问题,怎么解决也已经很明显了。
参考资料
SLF4J user manual
Bridging legacy APIs