日誌SLF4J解惑

妖怪來了發表於2018-04-14

0x00 前言

作為後端開發,日誌可能是我們最常用的功能之一了。平時大家也可能經常遇見日誌衝突,常見的overflow報錯,今天為大家詳解一下,這其中的原理以及問題所在。本文涉及 jar 包有:log4j,log4j-over-slf4j,slf4j-api,slf4j-log4j12 等等。

0x01 背景

不知道大家在平時開發中,是否經常遇見以下幾個問題:

  • log4j,logback等等日誌包衝突,然後再慢慢排除,不勝其煩。
  • 使用了 slf4j ,但是又遇到 StackOverflwError 錯誤。
    日誌SLF4J解惑
  • 好不容易解決了StackOverflwError,可能又遇到了類似如圖所示的錯誤:
    日誌SLF4J解惑
  • ......

0x02 概述

什麼是 SLF4J 呢?簡而言之,他就是一個日誌的門面,市場上的日誌系統非常多, SLF4J 想做的一件事情,就是將這多種的日誌系統包裝起來,提供統一的 API 供呼叫,從而解決日誌的列國爭霸的情況。

0x03 SLF4J 呼叫流程

在解決以上問題之前,讓我們先對日誌系統做一個大概的理解。到底什麼是 log4j,什麼是 slf4j-api,為什麼有一個 slf4j-log4j12,又怎麼有個log4j-over-slf4j,看上去頭暈眼花,繞來繞去。先祭上一張官網圖:

日誌SLF4J解惑
相信大家都見過這張圖,但是未必全都理解圖上說的是什麼意思。所以呢,我們先不講圖,先看看最上面的一排文字,SLF4J bound to xxxx,xxxx總共涉及:

  1. null
  2. logback-classic
  3. log4j
  4. java.util.logging
  5. simple
  6. no-operation

第1個顧名思義,不繫結,就是沒有日誌實現。第5,6可以看出來,是 SLF4J 自己的實現,這裡就忽略不講。所以著重講一下2、3、4。他們有個共同的特點,就是他們都是日誌的真正實現庫,他們和SLF4J沒有關係,你可以使用 logback 列印日誌,也可以使用 log4j 打日誌。

第一層, application 就是應用層。

看看圖中第二層,統統都是 SLF4J API。那麼這一層是幹什麼用的呢?這一層就是一個門面層,而和門面層息息相關的 jar 包是什麼呢?對,就是圖中的 slf4j-api.jar 。這個jar包中,提供了日誌呼叫的所有介面,應用都是直接呼叫這個 jar 包中的介面進行日誌呼叫。

第三層有點不同。第一列,無實現,第2,5,6都是原生的一個實現,而3,4都有一個適配層。這裡說說 logback。logback 他為什麼不需要適配層呢,因為他就是按照了 SLF4J 介面去實現的一個日誌庫,相當於親兒子,自然不需要適配層。所以,logback-classic.jar 和 logback-core.jar 就是 logback 的底層實現。而3,4就不同了,他們的介面或多或少會有差異,呼叫方式也各不相同,所以,需要一個適配層。所以 slf4j-log412.jar 和 slf4j-jdk14.jar 包的作用就是一個適配,這裡是一個橋接,應用通過 slf4j-api 的介面呼叫過來時,橋接類實際會呼叫其底層的實現,達到一個橋接的過程。所以,slf4j-log412.jar,slf4j-jdk14.jar,slf4j-simple.jar,slf4j-nop.jar 是同一類,就是把-右邊的實現進行一個橋接。

第四層,可以看到第2,3對應的實現,log4j.jar 和 jvm 就是最後的實現。

至此,做一個小總結:

  1. slf4j-api.jar 是上層的門面,裡面提供介面供呼叫。
  2. logback-classic.jar, logback-core.jar, log4j.jar, 是同一類別,屬於底層實現庫。
  3. slf4j-log412.jar,slf4j-jdk14.jar,slf4j-simple.jar,slf4j-nop.jar,可以看成 slf4j-xxxx.jar,屬於同一個類別,就是對 - 後面的庫做一個橋接,更簡單的理解,從左到右讀:把 - 左邊的呼叫用右邊的庫實現。

此時再看看上面那張圖,是否已經全部理解了呢?

0x04 SLF4J 轉換流程

如果看完上面,不覺得 SLF4J 有什麼好處,就來看看下面這張圖:

日誌SLF4J解惑
那這張圖又是什麼意思呢?這就要說到 SLF4J 的一個強大之處了。設想一下以下一種情況:新建了一個工程,引入的第一個庫使用的是 java.util.logging 日誌庫,引入的第二個庫使用的是 log4j 日誌庫,而你自己的工程,老闆規定,必須要用 logback 。你怎麼辦呢?這個時候, SLF4J 就出場了。他能幫你把所有日誌歸攏到你所指定的一種日誌實現。就是說,他可以把 jul 日誌實現轉成 logback,還能把 log4j 實現轉成 logback。那他是怎麼做的呢?回過頭來看圖吧。

著重講解左上角這一部分,其他的類似。

先看 application,這個是應用,可以看到,他也遇到了我說到的問題。他的依賴裡面有使用 log4j 的,有用 commons logging 的,有用 java.util.logging 的。所以此時需要做一個替換,分別是通過 jcl-over-slf4j.jar 替換掉 commons-logging.jar,log4j-over-slf4j.jar 替換掉 log4j.jar, jul-to-slf4j.jar 包中安裝 SLF4JBrindgeHandler 解決。替換掉之後,就把所有日誌呼叫轉接到 slf4j-api 上了,然後 api 介面再呼叫底層實現,圖上是 logback。文中說的替換是什麼意思呢?就是把原日誌實現庫排除掉,引入 xxx-over-slf4j.jar 。

那麼,xxx-over-slf4j.jar 是什麼原理呢?先給大家看這張圖:

日誌SLF4J解惑
左邊是 log4j.jar 的包結構,右邊是 log4j-over-slf4j.jar 的包結構。發現貓膩了嗎?他們的目錄結構一模一樣!所以用 log4j-over-slf4j 可以替換掉 log4j!且編譯不會出錯。log4j-over-slf4j.jar 實現了基本上所有 log4j 會被呼叫的 api 介面。所以替換之後,不會報錯,編譯也能通過,而底層實現卻全轉到 slf4j 這裡去了。這裡就是一個狸貓換太子的把戲。

再看其他兩個圖,底層分別是 log4j, jvm 實現。都是講其他庫 over 一下到 slf4j 。

特別注意一下, logback 不需要轉,為什麼?因為他是親兒子。天生就帶這些。

所以再小總結一下:

  • jcl-over-slf4j.jar, log4j-over-slf4j.jar, jul-to-slf4j.jar,這種形式類似 xxx-over-slf4j.jar 的,就是將 - 前的太子用 slf4j 的狸貓代替。而 xxx-to-slf4j.jar 比較特殊,這是因為 xxx 這個包無法被替換掉,比如 java.util.logging,系統的庫,無法替換,所以只能採用別的手段。此類別的實現讀者有興趣可以去看看,本文不再分析。

0x05 常見問題解決

StackOverflow 錯誤

為什麼會出現這個問題呢?控制檯輸出上一般會比較清楚,就是你既使用了橋接庫,又使用了over庫(狸貓)。比如:

日誌SLF4J解惑
試想一下:你先用 over 庫把 log4j 轉成了 slfj4 呼叫。緊接著,你又把 slf4j 適配到 log4j 上。這就構成了一個死迴圈,肯定是會出現堆疊溢位的問題。所以,一個工程裡面,只能保留一個日誌實現庫,還有配套的橋接庫,加上其他日誌的 over 庫,才是正確之道。

異常引數錯誤

上面提到的這個錯誤:

日誌SLF4J解惑
大致可以看出來吧,他缺少一個真正的實現。看你選擇使用什麼,就把什麼庫補充上去。加上 logback,去掉 slf4j-log4j 或者加上 log4j 都可以解決這個問題。

其他問題都比較類似,如果看懂了上文的介紹,應該可以著手解決此類問題了。

0x06 不算總結的總結

瞭解了日誌的原理,以後媽媽再也不擔心日誌衝突了!

相關文章