前言
你是否遇到过配置了日志,但打印不进去的状况?
你是否遇到过配置了logback,启动时却提醒log4j谬误的状况?像上面这样:
log4j:WARN No appenders could be found for logger (org.example.App). log4j:WARN Please initialize the log4j system properly. log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
你是否遇到过SLF4J的这种报错?
SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/C:/Users/jiang/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/C:/Users/jiang/.m2/repository/org/slf4j/slf4j-log4j12/1.7.30/slf4j-log4j12-1.7.30.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation. SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]
你是否遇到过DUBBO日志打印不失常的状况?
你是否遇到过Mybatis SQL日志打印不进去的状况?
你是否遇到过JPA/Hibernate SQL日志无奈打印的状况?
你是否遇到过简单我的项目中,很多框架外部日志无奈打印的状况?
你是否遇到过Tomcat工程,日志文件打印了多份,catalina.out和其余文件?
你是否遇到过SpringBoot我的项目,日志文件打印了多份的问题?
你是否遇到过各种日志配置问题……
日志框架的抵触
下面的这些问题,根本都是因为多套日志框架共存或配置谬误导致的。那么为什么会呈现共存或者抵触呢?
个别是以下几种起因:
- 我的项目手动援用了各种日志框架的包 – 比方同时援用了log4j/log4j2/logback/jboss-logging/jcl等
- 包管理工具的传递依赖(Transitive Dependencies)导致,比方依赖了dubbo,然而dubbo依赖了zkclient,可zkclient又依赖了log4j,此时如果你的我的项目中还有其余日志框架存在并有应用,那么就会导致多套共存
- 同一个日志框架多版本共存
JAVA里的各种日志框架
在正式介绍抵触和解决之前,须要先简略的来源[email protected]搞@^&代*@码网说一下Java中的各种日志框架:
Java 中的日志框架分为两种,别离为日志形象/门面,日志实现
日志形象/门面
日志形象/门面,他们不负责具体的日志打印,如输入到文件、配置日志内容格局等。他们只是一套日志形象,定义了一套对立的日志打印规范,如Logger对象,Level对象。
slf4j(Simple Logging Facade for Java)和jcl(Apache Commons Logging)这两个日志框架就是JAVA中最支流的日志形象了。还有一个jboss-logging,次要用于jboss系列软件,比方hibernate之类。像 jcl曾经多年不更新了(上一次更新工夫还是14年),目前最举荐的是应用 slf4j
日志实现
Java 中的日志实现框架,支流的有以下几种:
- log4j – Apache(老牌日志框架,不过多年不更新了,新版本为log4j2)
- log4j2 – Apache(log4j 的新版本,目前异步IO性能最强,配置也较简略)
- logback – QOS(slf4j就是这家公司的产品)
- jul(java.util.logging) – jdk内置
在程序中,能够间接应用日志框架,也能够应用日志形象+日志实现搭配的计划。不过个别都是用日志形象+日志实现,这样更灵便,适配起来更简略。
目前最支流的计划是slf4j+logback/log4j2,不过如果是jboss系列的产品,可能用的更多的还是jboss-logging,毕竟亲儿子嘛。像JPA/Hibernate这种框架里,内置的就是jboss-logging
SpringBoot + Dubbo 日志框架抵触的例子
举个例子来说个最常见的传递依赖导致的共存抵触:
比方我有一个“洁净的”spring-boot我的项目,洁净到只有一个spring-boot-starter
依赖,此时我想集成dubbo,应用zookeeper作为注册核心,此时我的依赖配置是这样:
<code class="xml"><dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>2.7.9</version> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-registry-zookeeper</artifactId> <version>2.7.9</version> </dependency> </dependencies>
当初启动这个spring-boot我的项目,会发现一堆红色谬误:
SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/C:/Users/jiang/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/C:/Users/jiang/.m2/repository/org/slf4j/slf4j-log4j12/1.7.30/slf4j-log4j12-1.7.30.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation. SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder] ----------------------------------人肉分割线---------------------------------------- log4j:WARN No appenders could be found for logger (org.apache.dubbo.common.logger.LoggerFactory). log4j:WARN Please initialize the log4j system properly. log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
从谬误提醒上看,谬误内容分为两个局部:
- slf4j报错,提醒找到多个slf4j的日志绑定
- log4j报错,提醒log4j没有appender配置
呈现这个谬误,就是因为dubbo的传递依赖中含有log4j,然而spring-boot的默认配置是slf4j+logback。在依赖了dubbo相干包之后,当初我的项目中同时存在logback/jcl(apache commons-logging)/log4j/jul-to-slf4j/slf4j-log4j/log4j-to-slf4j
来看一下依赖图(思否过分压缩图片,请点击查看大图):
这个时候就乱套了,slf4j-log4j
是log4j的slf4j实现,作用是调用slf4j api的时候应用log4j输入;而log4j-to-slf4j
的作用是将log4j
的实现替换为log4j
,这样一来不是死循环了
而且还有logback的存在,logback默认实现了slf4j的形象,而slf4j-log4j
也是一样实现了slf4j的形象,logback
,我的项目里共存了两套slf4j
的实现,那么在应用slf4j
接口打印的时候会应用哪个实现呢?
答案是“第一个”,也就是第一个被加载的Slf4j的实现类,但这种依附ClassLoader加载程序来保障的日志配置程序是十分不靠谱的
如果想失常应用日志,让这个我的项目里所有的框架都失常打印日志,必须将日志框架对立。不过这里的对立并不是至强行批改,而是用“适配/直达”的形式。
当初我的项目里尽管有slf4j-log4j的配置,但这个配置是适配log4j2用的,而咱们的依赖了只有log4j1,实际上这个直达是有效的。但logback是无效的,而且是spring-boot我的项目的默认配置,这次就抉择logback作为我的项目的对立日志框架吧。
当初我的项目里存在log4j(1)的包,而且启动时又报log4j的谬误,阐明某些代码调用了log4j的api。但咱们又不想用log4j,所以须要先解决log4j的问题。
因为有log4j代码的援用,所以间接删除log4j肯定是不可行的。slf4j提供了一个log4j-over-slf4j
的包,这个包复制了一份log4j1的接口类(Logger等),同时将实现类批改为slf4j了。
所以将log4j的(传递)依赖排除,同时援用log4j-over-slf4j
,就解决了这个log4j的问题。当初来批改下pom中的依赖(查看依赖图能够应用maven的命令,或者是IDEA自带的Maven Dependencies Diagram,再或者Maven Helper之类的插件)
<code class="xml"><dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-registry-zookeeper</artifactId> <version>2.7.9</version> <scope>compile</scope> <!--排除log4j--> <exclusions> <exclusion> <artifactId>log4j</artifactId> <groupId>log4j</groupId> </exclusion> </exclusions> </dependency> <!--减少log4j-slf4j --> <dependency> <groupId>org.slf4j</groupId> <artifactId>log4j-over-slf4j</artifactId> <version>1.7.30</version> </dependency>
解决了log4j的问题之后,当初还有slf4j有两个实现的问题,这个问题解决就更简略了。因为咱们打算应用logback,那么只须要排除/删除slf4j-log4j
这个实现的依赖即可
<code class="xml"><dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-registry-zookeeper</artifactId> <version>2.7.9</version> <scope>compile</scope> <exclusions> <exclusion> <artifactId>log4j</artifactId> <groupId>log4j</groupId> </exclusion> <exclusion> <artifactId>slf4j-log4j12</artifactId> <groupId>org.slf4j</groupId> </exclusion> </exclusions> </dependency>
批改实现,再次启动就没有谬误了,轻松解决问题
日志适配大全
下面只是介绍了一种转换的形式,但这么多日志框架,他们之间是能够相互转换的。不过最终目标都是对立一套日志框架,让最终的日志实现只有一套
这么多的日志适配/转换形式,全记住必定是有点难。为此我画了一张可能是全网最全的日志框架适配图(原图尺寸较大,请点击放大查看),如果再遇到抵触,须要将一个日志框架转换到另一款的时候,只须要依照图上的门路,引入相干的依赖包即可(思否过分压缩图片,请点击查看大图)。
比方想把slf4j,适配/转换到log4j2。依照图上的门路,只须要援用log4j-slf4j-impl即可。
如果想把jcl,适配/转换到slf4j,只须要删除jcl包,而后援用jcl-over-slf4j即可。
图上的箭头,有些标了文字的,是须要额定包进行转换的,有些没有标文字的,是内置了适配的实现。其实内置实现的这种会更麻烦,因为如果遇到共存根本都须要通过配置环境变量/配置额定属性的形式来指定一款日志实现。
目前slf4j是适配计划中,最外围的那个框架,算是这个图的核心枢纽。只有围绕slf4j做适配/转化,就没有解决不了的抵触
总结
解决日志框架共存/抵触问题其实很简略,只有遵循几个准则:
- 对立应用一套日志实现
- 删除多余的无用日志依赖
- 如果有援用必须共存的话,那么就移除原始包,应用“over”类型的包(over类型的包复制了一份原始接口,从新实现)
- 不能over的,应用日志形象提供的指定形式,例如
jboss-logging
中,能够通过org.jboss.logging.provider
环境变量指定一个具体的日志框架实现
我的项目里对立了日志框架之后,无论用那种日志框架打印,最终还是走向咱们直达/适配后的惟一一个日志框架。
解决了共存/抵触之后,我的项目里就只剩一款日志框架。再也不会呈现“日志打不出”,“日志配置不失效”之类的各种恶心问题,上班都能早点了!
原创不易,转载请在结尾驰名文章起源和作者。如果我的文章对您有帮忙,请点赞/珍藏/关注激励反对。