深入理解Logger日誌——框架繫結原理

堅持到底gl發表於2020-10-13

深入理解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>

 

 

相關文章