一、前言
在進行 Java 開發時,通常我們會選擇 Slf4j 作為日志門面,但日志實現卻不盡相同。如果系統運行中同時存在多個日志實現,就會出現類似下圖的 Warning。

二、問題原因
我們知道 SpringBoot 默認使用的日志實現是 Logback,因此我們嘗試在項目中引入 Log4j 的依賴時,就復現了上圖的報錯。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency>
上圖報錯告知我們存在多個 SLF4J bingdings,分別位於 logback 和 log4j 包中,有兩個 StaticLoggerBinder。
我們知道使用 Slf4j ,需要 LoggerFactory.getLogger() 方法獲取實例。
import org.slf4j.Logger; import org.slf4j.LoggerFactory; private final Logger logs = LoggerFactory.getLogger(xxx.class);
我們就可以通過這個作為入口,去看看源碼的實現。如下圖所示,我標注了需要關注的核心代碼。
- (1)調用 getILoggerFactory() 方法得到 LoggerFactory。
- (2)對於首次調用,INITIALIZATION_STATE 應該是 UNINITIALIZED,所以進入初始化的邏輯,調用方法 performInitialization()。
- (3)調用 bind() 方法。
- (4)如果不是 isAndroid(),調用 findPossibleStaticLoggerBinderPathSet() 方法,故名思意,查找可能的 staticLoggerBinder,注意這里返回的類型是 SET,即可能是多個。
- (5)在findPossibleStaticLoggerBinderPathSet() 這個方法內,首先通過 classLoader 加載了 org/slf4j/impl/StaticLoggerBinder.class 這個類的 path,它可能存在多個,因此使用了 while 獲取了所有的 path,並最終返回。

- (6)reportActualBinding() 方法會校驗 SET 的 size,如果大於 1,就會打印出一開始我們看見的 Warning 了。

三、問題解決
解決思路就是將你不想要的日志實現從依賴包中排除掉即可,通過 IDEA 提供的 Diagrams 能夠非常方便的查看項目中的依賴關系。
打開項目的 POM 文件,右鍵選擇 Diagrams -> Show Dependencies

假設我們想要排除 logback 依賴,使用 log4j。Ctrl + F 搜索 logback,可以找到引用該依賴的樹形結構。

點擊窗口左上角的下圖中的這個圖標,可以只看當前選中的這個依賴的關系。

選中后效果如下:

如上圖所示,logback 由 spring-boot-starter-logging 引入,最頂層是由 spring-boot-starter-web 和 spring-boot-starter-test 引入。
我們嘗試在 spring-boot-starter-web 中排除該依賴,應該就可以了。如果排出后重新搜索仍然存在 logback 依賴,則重復執行排除的操作。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency>`
四、總結
日志框架沖突特別對於新手來說處理起來比較頭疼,因為涉及到了日志接口和日志實現。
我們推崇的應該是面向接口編程,因此我們大到開源項目,小到公司的公共 jar 包,應當合理利用 Maven 的傳遞機制。具體的日志實現不應該傳遞出去,避免影響到調用的下游方。
<optional>true</optional>`