Java的日志框架梳理

對于Java的日志框架,你也許會經常看到這些名詞:

  • Log4j、Log4j2
  • Logback
  • Slf4j
  • JCL (Jakarta Commons Logging),也叫 Apache Common logging
  • J.U.L (java.util.logging)

初次接觸這些,可能有種云霧繚繞不知所云的感覺。本文就來好好梳理下它們的關系。

發展歷程

要搞清楚它們的關系,就要從它們是在什么情況下產生的說起。我們按照時間的先后順序來介紹。

Log4j

在JDK 1.3及以前,Java打日志依賴System.out.println(), System.err.println()或者e.printStackTrace(),Debug日志被寫到STDOUT流,錯誤日志被寫到STDERR流。這樣打日志有一個非常大的缺陷,即無法定制化,且日志粒度不夠細。
于是, Gülcü 于2001年發布了Log4j,后來成為Apache 基金會的頂級項目。Log4j 在設計上非常優秀,對后續的 Java Log 框架有長久而深遠的影響,它定義的Logger、Appender、Level等概念如今已經被廣泛使用。Log4j 的短板在于性能,在Logback 和 Log4j2 出來之后,Log4j的使用也減少了。

J.U.L

受Logj啟發,Sun在Java1.4版本中引入了java.util.logging,但是j.u.l功能遠不如log4j完善,開發者需要自己編寫Appenders(Sun稱之為Handlers),且只有兩個Handlers可用(Console和File),j.u.l在Java1.5以后性能和可用性才有所提升。

JCL(commons-logging)

由于項目的日志打印必然選擇兩個框架中至少一個,這時候,Apache的JCL(commons-logging)誕生了。JCL 是一個Log Facade,只提供 Log API,不提供實現,然后有 Adapter 來使用 Log4j 或者 JUL 作為Log Implementation。
在程序中日志創建和記錄都是用JCL中的接口,在真正運行時,會看當前ClassPath中有什么實現,如果有Log4j 就是用 Log4j, 如果啥都沒有就是用 JDK 的 JUL。
這樣,在你的項目中,還有第三方的項目中,大家記錄日志都使用 JCL 的接口,然后最終運行程序時,可以按照自己的需求(或者喜好)來選擇使用合適的Log Implementation。如果用Log4j, 就添加 Log4j 的jar包進去,然后寫一個 Log4j 的配置文件;如果喜歡用JUL,就只需要寫個 JUL 的配置文件。如果有其他的新的日志庫出現,也只需要它提供一個Adapter,運行的時候把這個日志庫的 jar 包加進去。
不過,commons-logging對Log4j和j.u.l的配置問題兼容的并不好,使用commons-loggings還可能會遇到類加載問題,導致NoClassDefFoundError的錯誤出現。


到這個時候一切看起來都很簡單,很美好。接口和實現做了良好的分離,在統一的JCL之下,不改變任何代碼,就可以通過配置就換用功能更強大,或者性能更好的日志庫實現。

這種簡單美好一直持續到SLF4J出現。

SLF4J & Logback

SLF4J(Simple Logging Facade for Java)和 Logback 也是Gülcü 創立的項目,目的是為了提供更高性能的實現。
從設計模式的角度說,SLF4J 是用來在log和代碼層之間起到門面作用,類似于 JCL 的 Log Facade。對于用戶來說只要使用SLF4J提供的接口,即可隱藏日志的具體實現,SLF4J提供的核心API是一些接口和一個LoggerFactory的工廠類,用戶只需按照它提供的統一紀錄日志接口,最終日志的格式、紀錄級別、輸出方式等可通過具體日志系統的配置來實現,因此可以靈活的切換日志系統。

Logback是log4j的升級版,當前分為三個目標模塊:

  • logback-core:核心模塊,是其它兩個模塊的基礎模塊
  • logback-classic:是log4j的一個改良版本,同時完整實現 SLF4J API 使你可以很方便地更換成其它日記系統如log4j 或 JDK14 Logging
  • logback-access:訪問模塊與Servlet容器集成提供通過Http來訪問日記的功能,是logback不可或缺的組成部分

Logback相較于log4j有更多的優點:

  • 更快的執行速度
  • 更充分的測試
  • logback-classic 非常自然的實現了SLF4J
  • 使用XML配置文件或者Groovy
  • 自動重新載入配置文件
  • 優雅地從I/O錯誤中恢復
  • 自動清除舊的日志歸檔文件
  • 自動壓縮歸檔日志文件
  • 謹慎模式
  • Lilith
  • 配置文件中的條件處理
  • 更豐富的過濾

更詳細的解釋參見官網:https://logback.qos.ch/reasonsToSwitch.html

到這里,你可能會問:Apache 已經有了個JCL,用來做各種Log lib統一的接口,如果 Gülcü 要搞一個更好的 Log 實現的話,直接寫一個實現就好了,為啥還要搞一個和SLF4J呢?

原因是Gülcü 認為 JCL 的 API 設計得不好,容易讓使用者寫出性能有問題的代碼。關于這點,你可以參考這篇文章獲得更詳細的介紹:https://zhuanlan.zhihu.com/p/24272450

現在事情就變復雜了。我們有了兩個流行的 Log Facade,以及三個流行的 Log Implementation。Gülcü 是個追求完美的人,他決定讓這些Log之間都能夠方便的互相替換,所以做了各種 Adapter 和 Bridge 來連接:

可以看到甚至 Log4j 和 JUL 都可以橋接到SLF4J,再通過 SLF4J 適配到到 Logback!需要注意的是不能有循環的橋接,比如下面這些依賴就不能同時存在:

  • jcl-over-slf4j 和 slf4j-jcl
  • log4j-over-slf4j 和 slf4j-log4j12
  • jul-to-slf4j 和 slf4j-jdk14

然而,事情在變得更麻煩!

Log4j2

現在有了更好的 SLF4J 和 Logback,慢慢取代JCL 和 Log4j ,事情到這里總該大統一圓滿結束了吧。然而維護 Log4j 的人不這樣想,他們不想坐視用戶一點點被 SLF4J / Logback 蠶食,繼而搞出了 Log4j2。

Log4j2 和 Log4j1.x 并不兼容,設計上很大程度上模仿了 SLF4J/Logback,性能上也獲得了很大的提升。Log4j2 也做了 Facade/Implementation 分離的設計,分成了 log4j-api 和 log4j-core。

現在好了,我們有了三個流行的Log 接口和四個流行的Log實現,如果畫出橋接關系的圖來回事什么樣子呢?

看到這里是不是感覺有點暈呢?是的,我也有這種感覺。同樣,在添加依賴的時候,要小心不要有循環依賴。

小結

常見的Java日志框架

  • log4j
  • logback
  • j.u.l (java.util.logging)

常見的Java日志門面

  • SLF4J
  • commons-logging

日志框架的綁定

如果使用 SLF4J 接口,SLF4J 允許終端用戶在部署的時候插入自己想要的日志框架。SLF4J 發行包中自帶幾個 jar 文件作為 "SLF4J bindings" 的參考:

  • slf4j-nop:綁定到 NOP,沉默的忽略掉所有的日志
  • slf4j-simple:綁定到 Simple 實現,輸出所有的事件到 System.err,只有 INFO 或更高級別的信息才會被打印出來
  • slf4j-log4j12:綁定 1.2 版的 log4j,需要你把 log4j.jar 放置到你的類路徑下
  • slf4j-jdk14:綁定 java.util.logging
  • slf4j-jcl:綁定 JCL,這種綁定會把所有的 SLF4J 日志代理到 JCL

實際上,SLF4J 不依賴于任何的類加載器機制。每一個 SLF4J 綁定都會在編譯時使用一種也只能使用一種特定日志框架。想要切換日志框架的話,僅僅是替換掉類路徑下的 SLF4J 綁定。例如,將要從 J.U.L 切換到 log4j,只需要用 slf4j-log4j12.jar 替換掉 slf4j-jdk14.jar。下圖概述了這個過程。

通過 SLF4J 統一日志

上面說到,如果使用 SLF4J 作為日志門面接口,那么可以很方便地選擇一種日志框架(只能綁定一種)。然而,現實中我們遇到的情況卻要更復雜些:我們想使用 SLF4J 的API,但是系統中已經使用了 JCL、log4j 或者 J.U.L,我們該怎么辦呢?

SLF4J 為我們提供了從老接口切換到 SLF4J 的方法,如下圖所示。

我們以圖中的左上部分為例來說明下。目前的應用程序中已經使用了如下混雜方式的API來進行日志的編程:

  • commons-logging
  • log4j1
  • J.U.L

現在想統一將日志的輸出交給 logback。解決方法是:

  • 第一步,先將上述日志系統的API全部無縫切換到 SLF4J
    • 去掉 commons-logging(其實去不去都可以),使用 jcl-over-slf4j 將 commons-logging 的底層日志輸出切換到 SLF4J
    • 去掉 Log4j1(必須去掉),使用 log4j-over-slf4j,將 Log4j1 的日志輸出切換到 SLF4J
    • 使用 jul-to-slf4j,將 J.U.L 的日志輸出切換到 SLF4J
  • 第二步,讓 SLF4J 綁定 Logback 來作為底層日志輸出,加入以下 jar 包依賴
    • slf4j-api
    • logback-core
    • logback-classic

圖中剩下兩個部分和上面很類似,詳細說明可以參考官網:https://www.slf4j.org/legacy.html

最佳實踐

推薦使用 SLF4J + Logback。maven依賴如下,其中version字段用占位符代替,你應該根據項目的實際情況選擇合適的版本:

<!--  =================================================  -->
<!--  日志及相關依賴(用slf4j+logback代替jcl+log4j)  -->
<!--  =================================================  -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>${version}</version>
</dependency>
<!-- 強制使用 logback的綁定 -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>${version}</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>${version}</version>
</dependency>
<!-- 強制使用 logback的綁定,這里去除對log4j 的綁定 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>99.0-does-not-exist</version>
</dependency>
<!-- slf4j 的橋接器,將第三方類庫對 log4j 的調用 delegate 到 slf api 上 -->
<!-- 這個橋接器是自己做的,主要是我們依賴的類庫存在很多硬編碼的引用 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>${version}</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>${version}</version>
</dependency>
<!-- 強制排除 log4j 的依賴,全部 delegate 到 log4j-over-slf4j 上 -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>99.0-does-not-exist</version>
</dependency>
<dependency>
    <groupId>apache-log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>999-not-exist</version>
</dependency>
<!-- slf4j + logback 配置結束 -->

再提幾點最佳實踐:

  1. 總是使用 Log Facade,而不是具體的 Log Implementation
  2. 只添加一個 Log Implementation 依賴
  3. 具體的日志依賴應該設置為 optional,并使用 runtime scope
    設為optional,依賴不會傳遞,這樣如果你是個lib項目,然后別的項目使用了你這個lib,不會被引入不想要的Log Implementation 依賴;
    Scope設置為runtime,是為了防止開發人員在項目中直接使用Log Implementation中的類,而不使用Log Facade中的類。
  4. 如果有必要, 排除依賴的第三方庫中的Log Impementation依賴
    這是很常見的一個問題,第三方庫的開發者未必會把具體的日志實現或者橋接器的依賴設置為optional,然后你的項目繼承了這些依賴——具體的日志實現未必是你想使用的,比如他依賴了Log4j,你想使用Logback,這時就很尷尬。另外,如果不同的第三方依賴使用了不同的橋接器和Log實現,也極容易形成環。
    這種情況下,推薦的處理方法,是使用exclude來排除所有的這些Log實現和橋接器的依賴,只保留第三方庫里面對Log Facade的依賴。

參考資料

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,936評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,744評論 3 421
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,879評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,181評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,935評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,325評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,384評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,534評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,084評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,892評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,623評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,322評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,735評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,990評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,800評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,084評論 2 375

推薦閱讀更多精彩內容