最近项目中整合Log4j的时候,始终是解决不了。问题的表象如下:
1. log4j.properties已经做了屏蔽控制台的输出,在本地时,Log4j日志文件确实没有再打印到了控制台了,但是发布到服务器上面时依旧会打印到控制台上面,如此,日志重定向加上本来就输出出来的log文件,导致最终采集到了双份的日志,做了大量的没有必要的工作。
2. log4j本身的日志文件project.log、SpringBoot使用Slf4j + Logback打印出来的spring.log整合不到一个文件里面去。
3. 根据网上的博客,专门配置了logback.xml文件,依旧没有彻底的解决log4j日志整合到slf4j体系中去。
最终通过了下面的步骤解决了,今天通过博客记录下大体的过程,以供后续参考。
1. 解决外挂log4j.properties不生效的作用。
因为本地的log4j.properties是在项目内部的,而在服务器Tomcat中,配置文件是外挂在shared.loader=${catalina.base}/shared/config目录下的。检查服务器日志发现,我们引用的一个三方库(项目组已有源码)内包含了log4j.properties,导致每次启动的时候,先加载了jar包中的配置文件,而不是我们外挂配置出来配置文件。
通过查看Log4j的源码得知,LogManager类110行使用到了Loader.getResource()方法来加载配置文件,继续想下看,发现它的加载顺序如下:
1. Trying to find [" + resource + "] using context classloader " + classLoader + "." 本工程中的配置文件
2. Trying to find [" + resource + "] using " + classLoader + " class loader. 项目jar包中的配置文件
3. Trying to find [" + resource + "] using ClassLoader.getSystemResource(). 系统类路径的配置文件
搞清楚这个问题的所在之后,我们将三方库的log4j配置文件去掉后,重新打包引入。重新启动项目,可以加载到我们自定义出来的log4j配置文件,问题解决。
2. 第2个问题和第3个问题,实质上是一个问题,就是为什么在spring-boot-starter-logging中引入了slf4j到log4j的桥接包,我自己也写了简单的demo,里面都可以直接打印log4j日志。但是为什么没有在这个项目中就加载到和使用到。
打印Gradle的依赖树 gradle dependencies 查到,项目中log4j相关的jar包一共引入了2个。一个是通过jxl导入的log4j.jar,另一个则是通过spring-boot-starter-logging导入的log4j-over-slf4j.jar桥包。两个jar包的package都是一模一样的。所以出现了类加载的冲突。查了类加载的资料以及slf4j的手册,得到如下结论:
1. 由ClassLoader的双亲委托模式加载机制我们可以知道,假设两个包名和类名完全相同的class文件不再同一个jar包,如果一个class文件已经被加载java虚拟机里了,那么后面的相同的class文件就不会被加载了。
2. Slf4j的手册中,也明确写了引入log4j-over-slf4j的第一步就是使用其覆盖原有的log4j.jar。
To use log4j-over-slf4j in your own application, the first step is to locate and then to replace log4j.jar with log4j-over-slf4j.jar. Note that you still need an SLF4J binding and its dependencies for log4j-over-slf4j to work properly.
exclude掉jxl中的log4j.jar后,重新启动项目,log4j的日志输出已经到了slf4j的日志中了。问题已解决了一大半,剩下的就是修改自定义的logback配置文件。
3. 附一份自定义的logback.xml配置文件
在application.properties中配置logging.config=classpath:logback-spring-${spring.profiles.active}.xml,其他配置文件如下:
文件:console-appender.xml
<?xml version="1.0" encoding="UTF-8"?> <included> <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <!--encoder 默认配置为PatternLayoutEncoder--> <encoder> <pattern>${CONSOLE_LOG_PATTERN}</pattern> <charset>UTF-8</charset> </encoder> <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息--> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>DEBUG</level> </filter> </appender> </included>
文件:file-appender.xml
<?xml version="1.0" encoding="UTF-8"?> <included> <property name="LOG_ABSOLUTE_PATH" value="/applog/project.log" /> <property name="FILE_LOG_PATTERN" value="${FILE_LOG_PATTERN:-%d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/> <!-- 日志记录器,日期滚动记录 --> <appender name="FILEOUT" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 正在记录的日志文件的路径及文件名 --> <file>${LOG_ABSOLUTE_PATH}</file> <!-- 日志记录器的滚动策略 --> <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"> <fileNamePattern>${LOG_ABSOLUTE_PATH}.%i</fileNamePattern> <minIndex>1</minIndex> <maxIndex>10</maxIndex> </rollingPolicy> <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> <maxFileSize>100MB</maxFileSize> </triggeringPolicy> <!-- 追加方式记录日志 --> <append>true</append> <!-- 日志文件的格式 --> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${FILE_LOG_PATTERN}</pattern> <charset>UTF-8</charset> </encoder> <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息--> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>INFO</level> </filter> </appender> </included>
文件:logback-spring-develop.xml
<?xml version="1.0" encoding="UTF-8"?> <configuration> <contextName>my-project</contextName> <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" /> <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" /> <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" /> <include resource="file-appender.xml" /> <!-- 生产环境下,将此级别配置为适合的级别,以免日志文件太多或影响程序性能 --> <root level="INFO"> <appender-ref ref="FILEOUT" /> </root> </configuration>
文件:logback-spring-local.xml
<?xml version="1.0" encoding="UTF-8"?> <configuration> <contextName>my-project</contextName> <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" /> <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" /> <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" /> <include resource="file-appender.xml" /> <include resource="console-appender.xml" /> <!-- 生产环境下,将此级别配置为适合的级别,以免日志文件太多或影响程序性能 --> <root level="INFO"> <appender-ref ref="FILEOUT" /> <appender-ref ref="STDOUT" /> </root> </configuration>
4. 总结:
在《阿里巴巴Java开发手册(正式版)》中,日志规约一项第一条就强制要求使用SLF4J,如此可以很容易的从一个日志框架迁移到另一个日志框架上去。
【强制】应用中不可直接使用日志系统(Log4j、Logback)中的API,而应依赖使用日志框架SLF4J中的API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
参考资料: