最近項目中整合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,使用門面模式的日志框架,有利於維護和各個類的日志處理方式統一。
參考資料: