天天看點

走進JavaWeb技術世界9:Java日志系統的誕生與發展

本系列文章将整理到我在GitHub上的《Java面試指南》倉庫,更多精彩内容請到我的倉庫裡檢視

https://github.com/h2pl/Java-Tutorial

喜歡的話麻煩點下Star哈

文章首發于我的個人部落格:

www.how2playlife.com

本文是微信公衆号【Java技術江湖】的《走進JavaWeb技術世界》其中一篇,本文部分内容來源于網絡,為了把本文主題講得清晰透徹,也整合了很多我認為不錯的技術部落格内容,引用其中了一些比較好的部落格文章,如有侵權,請聯系作者。

該系列博文會告訴你如何從入門到進階,從servlet到架構,從ssm再到SpringBoot,一步步地學習JavaWeb基礎知識,并上手進行實戰,接着了解JavaWeb項目中經常要使用的技術群組件,包括日志元件、Maven、Junit,等等内容,以便讓你更完整地了解整個JavaWeb技術體系,形成自己的知識架構。

如果對本系列文章有什麼建議,或者是有什麼疑問的話,也可以關注公衆号【Java技術江湖】聯系作者,歡迎你參與本系列博文的創作和修訂。

Java日志系統的演變史

我們先看一個故事。項目經理A帶着一幫兄弟開發了一套複雜的企業ERP系統,這個系統一連開發了好幾年,開發人員也換了好幾撥。

階段一

最開始的時候,項目經理A安排小B在系統中添加日志功能,在控制台上列印一些必要的資訊。最開始的時候,由于項目的功能比較少,于是小B就是用System.out.println的方式列印日志資訊。經理A感覺這樣使用比較友善,也便于項目小組人員的使用,于是就沿用了下來。

階段二

此時小B被借調到其他項目,小C加入到了項目組中。此時項目經理A要求改造日志系統,要求能把日志寫到一個檔案中,友善以後分析使用者行為。小C在檢視了以前的日志方式之後,感覺特别low,于是自己寫了一個日志架構,命名為xiaoC-logging.jar,此舉收到了項目經理A的好評。

階段三

項目組中加入了一個大牛老D,老D發現xiaoC-logging.jar這個日志架構雖然可以滿足基本的日志要求,但是還不夠高大上,沒有一些諸如自動歸檔,異步寫入檔案,把日志檔案寫入NoSQL資料庫中等功能。于是老D開發了一個更進階的日志架構叫oldD-logging.jar。

階段四

oldD-logging.jar開發完成之後,需要把原來的xiaoC-logging.jar中的日志API做修改,把之前的日志實作寫下來,換上高大上的oldD-logging.jar。

階段五

在這個解除安裝與上新的過程中,老D的工作量陡增,他感覺很累。不過姜還是老的辣,他參考了JDBC和spring中面向接口的程式設計方式,制定了一個日志的門面(一系列的接口),以後所有的日志的記錄,都隻面向接口程式設計,至于今後怎麼去實作,都要遵循這個接口就可以了。 

那麼在JAVA開發中,這正的日志系統是怎麼演變的呢?簡短地描述下日志發展,最先出現的是apache開源社群的log4j,這個日志确實是應用最廣泛的日志工具,成為了java日志的事實上的标準。然而,當時Sun公司在jdk1.4中增加了JUL日志實作,企圖對抗log4j,但是卻造成了混亂,這個也是被人诟病的一點。當然也有其他日志工具的出現,這樣必然造成開發者的混亂,因為這些日志系統互相沒有關聯,替換和統一也就變成了比較棘手的一件事。想象下你的應用使用log4j,然後使用了一個其他團隊的庫,他們使用了JUL,你的應用就得使用兩個日志系統了,然後又有第二個庫出現了,使用了simplelog。

這個時候估計讓你崩潰了,這是要鬧哪樣?這個狀況交給你來想想辦法,你該如何解決呢?進行抽象,抽象出一個接口層,對每個日志實作都适配或者轉接,這樣這些提供給别人的庫都直接使用抽象層即可。不錯,開源社群提供了commons-logging抽象,被稱為JCL,也就是日志架構了,确實出色地完成了相容主流的日志實作(log4j、JUL、simplelog),基本一統江湖,就連頂頂大名的spring也是依賴了JCL。

看起來事物确實是美好,但是美好的日子不長,接下來另一個優秀的日志架構slf4j的加入導緻了更加混亂的場面。比較巧的是slf4j的作者(Ceki Gülcü)就是log4j的作者,他覺得JCL不夠優秀,是以他要自己搞一套更優雅的出來,于是slf4j日志體系誕生了,并為slf4j實作了一個親子——logback,确實更加優雅,但是由于之前很多代碼庫已經使用JCL,雖然出現slf4j和JCL之間的橋接轉換,但是內建的時候問題依然多多,對很多新手來說确實會很懊惱,因為比單獨的log4j時代“複雜”多了,抱怨聲确實很多。

到此本來應該完了,但是Ceki Gülcü覺得還是得回頭拯救下自己的“大阿哥”——log4j,于是log4j2誕生了,同樣log4j2也參與到了slf4j日志體系中,想必将來會更加混亂。接下來詳細解讀日志系統的配合使用問題。slf4j的設計确實比較優雅,采用比較熟悉的方式——接口和實作分離,有個純粹的接口層——slf4j-api工程,這個裡邊基本完全定義了日志的接口,是以對于開發來說,隻需要使用這個即可。

有接口就要有實作,比較推崇的實作是logback,logback完全實作了slf4j-api的接口,并且性能也比log4j更好,同時實作了變參占位符日志輸出方式等等新特性。剛剛也提到log4j的使用比較普遍,是以支援這批使用者依然是必須的,slf4j-log4j12也實作了slf4j-api,這個算是對log4j的擴充卡。同樣推理,也會有對JUL的擴充卡slf4j-jdk14等等。為了使使用JCL等等其他日志系統後者實作的使用者可以很簡單地切換到slf4j上來,給出了各種橋接工程,比如:jcl-over-slf4j會把對JCL的調用都橋接到slf4j上來,可以看出jcl-over-slf4j的api和JCL是相同的,是以這兩個jar是不能共存的。jul-to-slf4j是把對jul的調用橋接到slf4j上,log4j-over-slf4j是把對log4j的調用橋接到slf4j。

一、日志架構的分類

  • 門面型日志架構:
  1. JCL:  Apache基金會所屬的項目,是一套Java日志接口,之前叫Jakarta Commons Logging,後更名為Commons Logging
  2. SLF4J:  是一套簡易Java日志門面,本身并無日志的實作。(Simple Logging Facade for Java,縮寫Slf4j)
  • 記錄型日志架構:
  1. JUL:  JDK中的日志記錄工具,也常稱為JDKLog、jdk-logging,自Java1.4以來的官方日志實作。
  2. Log4j:  一個具體的日志實作架構。
  3. Log4j2:   一個具體的日志實作架構,是LOG4J1的下一個版本,與Log4j 1發生了很大的變化,Log4j 2不相容Log4j 1。
  4. Logback:一個具體的日志實作架構,和Slf4j是同一個作者,但其性能更好。

              

走進JavaWeb技術世界9:Java日志系統的誕生與發展

二、發展曆程

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

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的錯誤出現。

                        

走進JavaWeb技術世界9:Java日志系統的誕生與發展

到這個時候一切看起來都很簡單,很美好。接口和實作做了良好的分離,在統一的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 來連接配接:

走進JavaWeb技術世界9:Java日志系統的誕生與發展

可以看到甚至 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實作,如果畫出橋接關系的圖來回事什麼樣子呢?

            

走進JavaWeb技術世界9:Java日志系統的誕生與發展

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

參考文章

https://segmentfault.com/a/1190000009707894 https://www.cnblogs.com/hysum/p/7100874.html http://c.biancheng.net/view/939.html https://www.runoob.com/ https://blog.csdn.net/android_hl/article/details/53228348

微信公衆号

個人公衆号:程式員黃小斜

微信公衆号【程式員黃小斜】新生代青年聚集地,程式員成長充電站。作者黃小斜,職業是阿裡程式員,身份是斜杠青年,希望和更多的程式員交朋友,一起進步和成長!專注于分享技術、面試、職場等成長幹貨,這一次,我們一起出發。

關注公衆号後回複“2020”領取我這兩年整理的學習資料,涵蓋自學程式設計、求職面試、算法刷題、Java技術學習、計算機基礎和考研等8000G資料合集。

技術公衆号:Java技術江湖

微信公衆号【Java技術江湖】一位阿裡 Java 工程師的技術小站,專注于 Java 相關技術:SSM、SpringBoot、MySQL、分布式、中間件、叢集、Linux、網絡、多線程,偶爾講點Docker、ELK,同時也分享技術幹貨和學習經驗,緻力于Java全棧開發!

關注公衆号後回複“PDF”即可領取200+頁的《Java工程師面試指南》強烈推薦,幾乎涵蓋所有Java工程師必知必會的知識點。

繼續閱讀