深入理解Logger日誌——框架繫結原理
說到Logger日誌的動態繫結,主要歸功與Slf4j,在之前的文章也說過,Slf4j是類似於Apache Common-Logging,英文為Simple Logging Facade,是一個簡單的日誌門面介面卡,所有的日誌程式碼都可以用slf4j方式,它會根據專案具體依賴的日誌實現包進行日誌操作,只需修改pom.xml檔案中的日誌實現依賴,對於Log4j、Log4j2和Logback等都有相應的橋接包,對相應的slf4j-api介面實現。
那麼slf4j是如何自動繫結上的呢。
多個框架是如何選擇的呢。
Slf4j框架架構原理
日誌的繫結原理主要是依賴Slf4j的靜態載入,從相應的實現框架中獲取Logger。需要自己動手一步步跟蹤下去才會感觸良多。
Slf4j的實現流程:
Logger日誌列印呼叫流程:
在slf4j中主要是實現 LoggerFactoryBinder 的 StaticLoggerBinder 採用單例模式,編譯時期靜態載入方式,得到不同的ILoggerFactory工廠的實現類,最終拿到相應框架所匹配的Logger。
對於Slf4j的原始碼分析我這邊就不再過多的贅餘,關鍵得自己去理解的看看,推薦一個:Java日誌體系(slf4j)
介紹一下slf4j中主要的幾個類的作用:
- Logger:slf4j日誌介面類,提供了trace < debug < info < warn < error這5個級別對應的方法,主要提供了佔位符{}的日誌列印方式;
- Log4jLoggerAdapter:Logger介面卡,主要對org.apache.log4j.Logger物件的封裝,佔位符{}日誌列印的方式在此類中實現;
- LoggerFactory:日誌工廠類,獲取實際的日誌工廠類,獲取相應的日誌實現物件;
- lLoggerFactory:底層日誌框架中日誌工廠的中介,再其實現類中,通過底層日誌框架中的日誌工廠獲取對應的日誌物件;
- StaticLoggerBinder:靜態日誌物件繫結,在編譯期確定底層日誌框架,獲取實際的日誌工廠,也就是lLoggerFactory的實現類;
專案中有多種日誌框架時會存在多個StaticLoggerBinder時候獲取第一個,主要是採用類的順序載入機制,與Pom檔案中依賴的填寫順序有關(個人實踐得到)。
每一種日誌框架都有相應的橋接包。
首先先看Slf4j-api包結構:主要定義了一些介面做相應的適配,其包結構主要是 org.slf4j 其他實現框架都是根據其進行相應的橋接
Log4j的橋接包形式:相同包名,StaticLoggerBinder採用構造器形式獲取 Log4jLoggerFactory 工廠
Logback的橋接包形式:相同包名,在StaticLoggerBinder呼叫時就已經靜態初始化LoggerContext 工廠
Logback的詳細配置:https://cloud.tencent.com/developer/article/1445599
Log4j2的橋接包形式:相同包名,與Log4j類似採用構造器形式獲取 Log4jLoggerFactory 工廠
Log4j2的詳細配置:Java日誌體系(log4j2)
在官方文件中slf4j與其他日誌框架如何繫結的示意圖(大家都放我也放一下~)
以下是個人收集一些比較簡單好理解的繫結關係圖,體現了StaticLoggerBinder繫結的重要性
繫結過程中所遇到的問題
1、Log4j和Log4j2需要配置好log4j.xml和log4j2.xml檔案,不然無法正常使用,Logback可以正常使用
2、與SpringBoot的spring-boot-starter中的依賴spring-boot-starter-logging內部的依賴Logback產生衝突(可以自己點選檢視一番)
Caused by: java.lang.IllegalArgumentException: LoggerFactory is not a Logback LoggerContext but Logback is on the classpath. Either remove Logback or the competing implementation (class org.slf4j.impl.Log4jLoggerFactory loaded from file:/Users/zhouguanglin/.m2/repository/org/slf4j/slf4j-log4j12/1.7.29/slf4j-log4j12-1.7.29.jar). If you are using WebLogic you will need to add 'org.slf4j' to prefer-application-packages in WEB-INF/weblogic.xml: org.slf4j.impl.Log4jLoggerFactory at org.springframework.util.Assert.instanceCheckFailed(Assert.java:699) at org.springframework.util.Assert.isInstanceOf(Assert.java:599) at org.springframework.boot.logging.logback.LogbackLoggingSystem.getLoggerContext(LogbackLoggingSystem.java:284) at org.springframework.boot.logging.logback.LogbackLoggingSystem.beforeInitialize(LogbackLoggingSystem.java:104) at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationStartingEvent(LoggingApplicationListener.java:232) at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEvent(LoggingApplicationListener.java:213)
主要原因是:SpringBoot所使用的搭配日誌框架是Logback
若是專案使用SpringBoot但搭配的是其他日誌框架A,由StaticLoggerBinder.getSingleton().getLoggerFactory()獲取的是搭配框架A的實現,導致與SpringBoot內部自身搭配的Logback產生衝突,可以進入報錯地方檢視。
從程式碼邏輯中可以發現,Assert.isInstanceOf 判斷獲取得到的ILoggerFactory的實現類必須是LoggerContext 才OK,硬性規定不然就會報錯。
解決方案:只要在SpringBoot中排除Logback的依賴就OK,如下:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <exclusion> <artifactId>logback-classic</artifactId> <groupId>ch.qos.logback</groupId> </exclusion> </exclusions> </dependency>