前言使用了这么久的日志框架,你是不是还会遇到配置了日志还是不见日志的情况?快来接受以下8连发的灵魂拷问吧!Q1:有没有遇到配置了logback,但是启动时提示log4j错误的情况?像下面这样:log4j:WARNNoappendercouldbefoundforlogger(org.example.App).log4j:WARNPleaseinitializethelog4jsystemproperly.log4j:WARNSeehttp://logging.apache.org/log4j/1.2/faq.html#noconfigfororeinfo.Q2:有没有遇到过SLF4J的这种错误?SLF4J:ClasspathcontainsmultipleSLF4Jbindings.SLF4J:Foundbindingin[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:Foundbindingin[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:见http://www.slf4j.org/codes.html#multiple_bindingsforanexplanation.SLF4J:Actualbindingisoftype[ch.qos.logback.classic.util.ContextSelectorStaticBinder]Q3:你遇到过DUBBO日志打印不正常的情况吗?Q4:你遇到过MybatisSQL日志无法打印的情况吗?Q5:你遇到过无法打印JPA/HibernateSQL日志的情况吗?Q6:您是否遇到过复杂的项目,很多框架内部日志打印不出来?Q7:有没有遇到Tomcat项目,log文件打印多份,catalina.out等文件?Q8:在SpringBoot项目中遇到过打印多个log文件的问题吗?什么!你也遇到过其他各种日志配置问题...##日志框架冲突这些问题基本都是多套日志框架共存或配置错误引起的>为什么会共存或冲突?一般有以下几种原因:项目手动引用各种日志框架的包,比如log4j/log4j2/logback/jboss-logging/jcl等包管理工具的传递依赖(TransitiveDependencies),比如依赖dubbo,但是dubbo依赖zkclient,但是zkclient也依赖log4j。这时候,如果你的项目中还有其他的日志框架存在并被使用,那么多套同一个日志框架和同一个日志框架的多个版本就会并存。在日志框架正式介绍冲突和解决之前,有必要简单说一下Java中的各种日志框架:Java中的日志框架分为两种,分别是日志抽象/门面,和日志实现日志抽象/门面不负责具体的Log打印,比如输出到文件,配置日志内容格式等。这只是一套日志抽象,定义了一套统一的日志打印标准,比如Logger对象,Level对象.slf4j(SimpleLoggingFacadeforJava)和jcl(ApacheCommonsLogging)这两个日志框架是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-启动依赖。这时候想整合dubbo,使用zookeeper作为注册中心。此时我的依赖配置如下:>org.apache.dubbodubbo-spring-boot-starter2.7.9org.apache.dubbodubbo-registry-zookeeper2.7.9现在启动这个spring-boot项目,你会发现一堆红色错误:SLF4J:ClasspathcontainsmultipleSLF4Jbindings.SLF4J:Foundbindingin[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:Foundbbindingin[jar:file:/C:/Users/jiang/.m2/repository/org/slf4j/slf4j-log4j12/1.7.30/slf4j-log4j12-1.7.30.jar!/org/slf4j/impl/StaticLoggerBinder。类]SLF4J:参见http://www.slf4j.org/codes.html#multiple_bindingsforanexplanation.SLF4J:Actualbindingisoftype[ch.qos.logback.classic.util.ContextSelectorStaticBinder]--------------------------------我是分界线------------------------------------log4j:WARNNoappendercouldbefoundforlogger(org.apache.dubbo.common.logger.LoggerFactory).log4j:WARNPleaseinitializethelog4jsystemproperly.log4j:WARNSeehttp://logging.apache.org/log4j/1.2/faq.html#noconfigfororeinfo。从错误提示来看,错误内容分为两部分:slf4j报错,提示findmultipleslf4jlogsboundlog4j报错,提示log4j没有appender配置。出现这个错误是因为dubbo传输的依赖包含了log4j,但是spring-boot默认配置的是slf4j+logback。依赖dubbo相关包后,现在项目/log4j-to-slf4j中有logback/jcl(apachecommons-logging)/log4j/jul-to-slf4j/slf4j-log4j。我们看一下依赖图:这时候乱七八糟的,slf4j-log4j是log4j的slf4j实现,其作用是调用slf4japi时使用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错误,说明有代码调用了log4japi。但是我们不想使用log4j,所以需要先解决log4j的问题。既然有引用log4j的代码,那么直接去掉log4j肯定是行不通的。slf4j提供了一个log4j-over-slf4j包,里面复制了一个log4j1的接口类(Logger等),将实现类修改为slf4j。所以排除log4j的(传递)依赖,同时引用log4j-over-slf4j就解决了log4j的问题。现在我们来修改一下pom中的依赖(可以使用maven命令查看依赖图,或者IDEA自带的MavenDependenciesDiagram,或者MavenHelper等插件)。org.apache.dubbodubbo-registry-zookeeper2.7.9compilelog4jlog4jorg.slf4jlog4j-over-slf4j1.7.30解决log4j的问题后,现在还有slf4j两个实现问题,这个问题比较好处理。由于我们计划使用logback,所以我们只需要排除/移除对slf4j-log4j实现的依赖。org.apache.dubbodubbo-registry-zookeeper2.7.9编译log4jlog4jslf4j-log4j12org.slf4j/Exclusions>修改完成,重启后不会报错,轻松解决问题日志适配百科上面只介绍了一种转换方法,但是日志框架那么多,可以转换成各个其他。最终的目的是统一一套日志框架,让最终的日志实现只有一套那么多的日志适配/转换方式,想全部记住起来肯定有点吃力。请看下图搭配记忆。如果遇到冲突,需要将一个日志框架转换成另一个,只需要按照图中的路径,导入相关的依赖包即可。例如,如果你想将slf4j适配/转换为log4j2。根据图中的路径,只需要引用log4j-slf4j-impl即可。如果想适配/转换jcl为slf4j,只需要删除jcl包,然后参考jcl-over-slf4j即可。图片上的箭头有的是有文字标注的,需要额外的包进行转换,有的是没有文字标注的,都是内置的适配实现。其实内置的实现会比较麻烦,因为如果遇到共存,基本上需要通过配置环境变量/配置附加属性的方式来指定日志实现。目前slf4j是适配方案中的核心框架,是这个图的中心枢纽。只要围绕slf4j进行适配/改造,就不会有处理不了的冲突。综上所述,解决日志框架共存/冲突问题其实很简单,只要遵循几个原则即可:统一使用一套日志,删除多余无用的日志依赖。如果有必须共存的引用,则去掉原来的包,使用“over”类型的包(over类型的包复制一份原来的接口,重新实现)如果不能over,使用指定方法提供的方法日志抽象。比如在jboss-logging中,可以通过org.jboss.logging.provider环境变量来指定具体的日志,在框架实现项目中统一了日志框架之后,无论使用哪个日志框架打印,最终都会在我们的传输/适配之后转到唯一的日志框架。解决共存/冲突后,项目中就只剩下一个日志框架了。