<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          可能是全網(wǎng)最全的 Java 日志框架適配、沖突解決方案

          共 7783字,需瀏覽 16分鐘

           ·

          2021-05-15 22:18

          前言


          你是否遇到過配置了日志,但打印不出來的情況?


          你是否遇到過配置了logback,啟動時卻提示log4j錯誤的情況?像下面這樣:


          log4j:WARN No appenders could be found for logger (org.example.App).
          log4j:WARN Please initialize the log4j system properly.
          log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.


          你是否遇到過SLF4J的這種報錯?


          SLF4J: Class path contains multiple SLF4J bindings.
          SLF4J: Found binding in [jar:file:/C:/Users/jiang/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
          SLF4J: Found binding in [jar:file:/C:/Users/jiang/.m2/repository/org/slf4j/slf4j-log4j12/1.7.30/slf4j-log4j12-1.7.30.jar!/org/slf4j/impl/StaticLoggerBinder.class]
          SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
          SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]


          你是否遇到過DUBBO日志打印不正常的情況?


          你是否遇到過Mybatis SQL日志打印不出來的情況?


          你是否遇到過JPA/Hibernate SQL日志無法打印的情況?


          你是否遇到過復(fù)雜項目中,很多框架內(nèi)部日志無法打印的情況?


          你是否遇到過Tomcat工程,日志文件打印了多份,catalina.out和其他文件?


          你是否遇到過SpringBoot項目,日志文件打印了多份的問題?


          你是否遇到過各種日志配置問題……


          日志框架的沖突


          上面的這些問題,基本都是由于多套日志框架共存或配置錯誤導(dǎo)致的。那么為什么會出現(xiàn)共存或者沖突呢?


          一般是以下幾種原因:


          • 項目手動引用了各種日志框架的包 - 比如同時引用了log4j/log4j2/logback/jboss-logging/jcl等

          • 包管理工具的傳遞依賴(Transitive Dependencies)導(dǎo)致,比如依賴了dubbo,但是dubbo依賴了zkclient,可zkclient又依賴了log4j,此時如果你的項目中還有其他日志框架存在并有使用,那么就會導(dǎo)致多套共存

          • 同一個日志框架多版本共存


          JAVA里的各種日志框架


          在正式介紹沖突和解決之前,需要先簡單的說一下Java中的各種日志框架:


          Java 中的日志框架分為兩種,分別為日志抽象/門面,日志實現(xiàn)


          日志抽象/門面


          日志抽象/門面,他們不負責(zé)具體的日志打印,如輸出到文件、配置日志內(nèi)容格式等。他們只是一套日志抽象,定義了一套統(tǒng)一的日志打印標準,如Logger對象,Level對象。


          slf4j(Simple Logging Facade for Java)和jcl(Apache Commons Logging)這兩個日志框架就是JAVA中最主流的日志抽象了。還有一個jboss-logging,主要用于jboss系列軟件,比如hibernate之類。


          像 jcl已經(jīng)多年不更新了(上一次更新時間還是14年),目前最推薦的是使用 slf4j


          日志實現(xiàn)


          Java 中的日志實現(xiàn)框架,主流的有以下幾種:


          • log4j - Apache(老牌日志框架,不過多年不更新了,新版本為log4j2)

          • log4j2 - Apache(log4j 的新版本,目前異步IO性能最強,配置也較簡單)

          • logback - QOS(slf4j就是這家公司的產(chǎn)品)

          • jul(java.util.logging) - jdk內(nèi)置


          在程序中,可以直接使用日志框架,也可以使用日志抽象+日志實現(xiàn)搭配的方案。不過一般都是用日志抽象+日志實現(xiàn),這樣更靈活,適配起來更簡單。


          目前最主流的方案是slf4j+logback/log4j2,不過如果是jboss系列的產(chǎn)品,可能用的更多的還是jboss-logging,畢竟親兒子嘛。像JPA/Hibernate這種框架里,內(nèi)置的就是jboss-logging

          SpringBoot + Dubbo 日志框架沖突的例子


          舉個例子來說個最常見的傳遞依賴導(dǎo)致的共存沖突:


          比如我有一個“干凈的”spring-boot項目,干凈到只有一個spring-boot-starter依賴,此時我想集成dubbo,使用zookeeper作為注冊中心,此時我的依賴配置是這樣:


          <dependencies>
            <dependency>
              <groupId>org.springframework.bootgroupId>
              <artifactId>spring-boot-starterartifactId>
            dependency>
            <dependency>
              <groupId>org.apache.dubbogroupId>
              <artifactId>dubbo-spring-boot-starterartifactId>
              <version>2.7.9version>
            dependency>
            <dependency>
              <groupId>org.apache.dubbogroupId>
              <artifactId>dubbo-registry-zookeeperartifactId>
              <version>2.7.9version>
            dependency>
          dependencies>


          現(xiàn)在啟動這個spring-boot項目,會發(fā)現(xiàn)一堆紅色錯誤:


          SLF4J: Class path contains multiple SLF4J bindings.
          SLF4J: Found binding in [jar:file:/C:/Users/jiang/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
          SLF4J: Found binding in [jar:file:/C:/Users/jiang/.m2/repository/org/slf4j/slf4j-log4j12/1.7.30/slf4j-log4j12-1.7.30.jar!/org/slf4j/impl/StaticLoggerBinder.class]
          SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
          SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]
          ----------------------------------人肉分割線----------------------------------------
          log4j:WARN No appenders could be found for logger (org.apache.dubbo.common.logger.LoggerFactory).
          log4j:WARN Please initialize the log4j system properly.
          log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.


          從錯誤提示上看,錯誤內(nèi)容分為兩個部分:


          • slf4j報錯,提示找到多個slf4j的日志綁定

          • log4j報錯,提示log4j沒有appender配置


          出現(xiàn)這個錯誤,就是因為dubbo的傳遞依賴中含有l(wèi)og4j,但是spring-boot的默認配置是slf4j+logback。在依賴了dubbo相關(guān)包之后,現(xiàn)在項目中同時存在logback/jcl(apache commons-logging)/log4j/jul-to-slf4j/slf4j-log4j/log4j-to-slf4j


          來看一下依賴圖:



          這個時候就亂套了,slf4j-log4j是log4j的slf4j實現(xiàn),作用是調(diào)用slf4j api的時候使用log4j輸出;而log4j-to-slf4j的作用是將log4j的實現(xiàn)替換為log4j,這樣一來不是死循環(huán)了


          而且還有l(wèi)ogback的存在,logback默認實現(xiàn)了slf4j的抽象,而slf4j-log4j也是一樣實現(xiàn)了slf4j的抽象,logback,項目里共存了兩套slf4j的實現(xiàn),那么在使用slf4j接口打印的時候會使用哪個實現(xiàn)呢?


          答案是“第一個”,也就是第一個被加載的Slf4j的實現(xiàn)類,但這種依靠ClassLoader加載順序來保證的日志配置順序是非常不靠譜的


          如果想正常使用日志,讓這個項目里所有的框架都正常打印日志,必須將日志框架統(tǒng)一。不過這里的統(tǒng)一并不是至強行修改,而是用“適配/中轉(zhuǎn)”的方式。


          現(xiàn)在項目里雖然有slf4j-log4j的配置,但這個配置是適配log4j2用的,而我們的依賴了只有l(wèi)og4j1,實際上這個中轉(zhuǎn)是無效的。但logback是有效的,而且是spring-boot項目的默認配置,這次就選擇logback作為項目的統(tǒng)一日志框架吧。


          現(xiàn)在項目里存在log4j(1)的包,而且啟動時又報log4j的錯誤,說明某些代碼調(diào)用了log4j的api。但我們又不想用log4j,所以需要先解決log4j的問題。


          由于有l(wèi)og4j代碼的引用,所以直接刪除log4j一定是不可行的。slf4j提供了一個log4j-over-slf4j的包,這個包復(fù)制了一份log4j1的接口類(Logger等),同時將實現(xiàn)類修改為slf4j了。


          所以將log4j的(傳遞)依賴排除,同時引用log4j-over-slf4j,就解決了這個log4j的問題?,F(xiàn)在來修改下pom中的依賴(查看依賴圖可以使用maven的命令,或者是IDEA自帶的Maven Dependencies Diagram,再或者Maven Helper之類的插件)


          <dependency>
            <groupId>org.apache.dubbogroupId>
            <artifactId>dubbo-registry-zookeeperartifactId>
            <version>2.7.9version>
            <scope>compilescope>
            
            <exclusions>
              <exclusion>
                <artifactId>log4jartifactId>
                <groupId>log4jgroupId>
              exclusion>
            exclusions>
          dependency>

          <dependency>
              <groupId>org.slf4jgroupId>
              <artifactId>log4j-over-slf4jartifactId>
              <version>1.7.30version>
          dependency>


          解決了log4j的問題之后,現(xiàn)在還有slf4j有兩個實現(xiàn)的問題,這個問題處理就更簡單了。由于我們計劃使用logback,那么只需要排除/刪除slf4j-log4j這個實現(xiàn)的依賴即可


          <dependency>
            <groupId>org.apache.dubbogroupId>
            <artifactId>dubbo-registry-zookeeperartifactId>
            <version>2.7.9version>
            <scope>compilescope>
            <exclusions>
              <exclusion>
                <artifactId>log4jartifactId>
                <groupId>log4jgroupId>
              exclusion>
              <exclusion>
                <artifactId>slf4j-log4j12artifactId>
                <groupId>org.slf4jgroupId>
              exclusion>
            exclusions>
          dependency>


          修改完成,再次啟動就沒有錯誤了,輕松解決問題


          日志適配大全


          上面只是介紹了一種轉(zhuǎn)換的方式,但這么多日志框架,他們之間是可以互相轉(zhuǎn)換的。不過最終目的都是統(tǒng)一一套日志框架,讓最終的日志實現(xiàn)只有一套


          這么多的日志適配/轉(zhuǎn)換方式,全記住肯定是有點難。為此我畫了一張可能是全網(wǎng)最全的日志框架適配圖(原圖尺寸較大,請點擊放大查看),如果再遇到?jīng)_突,需要將一個日志框架轉(zhuǎn)換到另一款的時候,只需要按照圖上的路徑,引入相關(guān)的依賴包即可。



          比如想把slf4j,適配/轉(zhuǎn)換到log4j2。按照圖上的路徑,只需要引用log4j-slf4j-impl即可。


          如果想把jcl,適配/轉(zhuǎn)換到slf4j,只需要刪除jcl包,然后引用jcl-over-slf4j即可。


          圖上的箭頭,有些標了文字的,是需要額外包進行轉(zhuǎn)換的,有些沒有標文字的,是內(nèi)置了適配的實現(xiàn)。其實內(nèi)置實現(xiàn)的這種會更麻煩,因為如果遇到共存基本都需要通過配置環(huán)境變量/配置額外屬性的方式來指定一款日志實現(xiàn)。


          目前slf4j是適配方案中,最核心的那個框架,算是這個圖的中心樞紐。只要圍繞slf4j做適配/轉(zhuǎn)化,就沒有處理不了的沖突


          總結(jié)


          解決日志框架共存/沖突問題其實很簡單,只要遵循幾個原則:


          • 統(tǒng)一使用一套日志實現(xiàn)

          • 刪除多余的無用日志依賴

          • 如果有引用必須共存的話,那么就移除原始包,使用“over”類型的包(over類型的包復(fù)制了一份原始接口,重新實現(xiàn))

          • 不能over的,使用日志抽象提供的指定方式,例如jboss-logging中,可以通過org.jboss.logging.provider環(huán)境變量指定一個具體的日志框架實現(xiàn)


          項目里統(tǒng)一了日志框架之后,無論用那種日志框架打印,最終還是走向我們中轉(zhuǎn)/適配后的唯一一個日志框架。


          解決了共存/沖突之后,項目里就只剩一款日志框架。再也不會出現(xiàn)“日志打不出”,“日志配置不生效”之類的各種惡心問題,下班都能早點了!

          瀏覽 34
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  9毛片| 大香蕉美女在线 | 大香伊人 | 免费亚洲高清视频 | 午夜激情操逼网 |