Java日誌體系竟然這麼複雜?——架構篇

Java日誌體系竟然這麼複雜?——架構篇

本文是一個系列,歡迎關注

日誌究竟是何方神聖?爲何要使用日誌框架?

想必你們都有過使用System.out來進行輸出調試,開發開發環境下這樣作固然很方便,可是線上這樣作就有麻煩了:html

  1. 系統一直運行,輸出愈來愈多,磁盤空間逐漸被寫滿
  2. 不一樣的業務想要把日誌輸出在不一樣的位置
  3. 有些場合爲了更高性能,儘可能控制減小日誌輸出,須要動態調整日誌輸出量
  4. 自動輸出日誌相關信息,好比:日期、線程、方法名稱等等

顯然System.out解決不了咱們的問題,可是咱們遇到的問題必定會有前人遇到過,日誌也不例外,其中就有一個大牛 Ceki,整個Java的日誌體系幾乎都有Ceki參與或者受到了Ceki的深度影響。固然Java日誌體系的複雜度也有一部分緣由是拜這位大牛所賜。java

Java日誌的恩怨情仇

  • 1996年早期,歐洲安全電子市場項目組決定編寫它本身的程序跟蹤API(Tracing API)。通過不斷的完善,這個API終於成爲一個十分受歡迎的Java日誌軟件包,即Log4j(由Ceki建立)。
  • 後來Log4j成爲Apache基金會項目中的一員,Ceki也加入Apache組織。後來Log4j近乎成了Java社區的日誌標準。聽說Apache基金會還曾經建議Sun引入Log4j到Java的標準庫中,但Sun拒絕了。
  • 2002年Java1.4發佈,Sun推出了本身的日誌庫JUL(Java Util Logging),其實現基本模仿了Log4j的實現。在JUL出來之前,Log4j就已經成爲一項成熟的技術,使得Log4j在選擇上佔據了必定的優點。
  • 接着,Apache推出了Jakarta Commons Logging,JCL只是定義了一套日誌接口(其內部也提供一個Simple Log的簡單實現),支持運行時動態加載日誌組件的實現,也就是說,在你應用代碼裏,只需調用Commons Logging的接口,底層實現能夠是Log4j,也能夠是Java Util Logging。
  • 後來(2006年),Ceki不適應Apache的工做方式,離開了Apache。而後前後建立了Slf4j(日誌門面接口,相似於Commons Logging)和Logback(Slf4j的實現)兩個項目,並回瑞典建立了QOS公司,QOS官網上是這樣描述Logback的:The Generic,Reliable Fast&Flexible Logging Framework(一個通用,可靠,快速且靈活的日誌框架)。
  • Java日誌領域被劃分爲兩大陣營:Commons Logging陣營和Slf4j陣營。
  • Commons Logging在Apache大樹的籠罩下,有很大的用戶基數。但有證據代表,形式正在發生變化。2013年末有人分析了GitHub上30000個項目,統計出了最流行的100個Libraries,能夠看出Slf4j的發展趨勢更好。
  • Apache眼看有被Logback反超的勢頭,於2012-07重寫了Log4j 1.x,成立了新的項目Log4j 2, Log4j 2具備Logback的全部特性。
  • 現在日誌框架已經發展爲:Slf4j做爲API,實現分爲logback與log4j(Commons Logging由於效率和API設計等問題,如今逐漸淡出舞臺了)

讓咱們來瞻仰一下大神,哈哈:
Cekispring

那麼如何在混亂的Java日誌體系中如何優雅的使用日誌呢?

其實在Ceki設計的體系下,日誌如同Java的JDBC、Servelt等同樣,定義好標準後實現能夠互相切換,問題在於定標準的人各自爲政搞出來好多標準,JCL、SLF4j等等,官方(Sun公司)又晚又不給力,發展到如今終於被SLF4j以一種巧妙的方式(橋接、綁定,見下文)統一了,標準使用方式以下圖:
Slf4j
這個圖截取自slf4j手冊,簡化了多餘部分,很清晰的表示了使用方式:apache

應用引用SLF4j-API(編碼時使用SLF4j的接口org.slf4j.Logger,而非logback或log4j的實現)編程

  • logbak: slf4j會自動查找logback實現(logback默認實現了slf4j)
  • log4j:使用起來基本一致,只不過多了適配器層,引用了slf4j-log4j12.jar,官方稱爲綁定(concrete-bindings),就是將SLF4j-API綁定到log4j最終輸出日誌

具體依賴以下安全

logback架構

<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.2.3</version>
</dependency>

log4j2框架

<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-slf4j-impl</artifactId>
  <version>2.12.1</version>
</dependency>
<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-core</artifactId>
  <version>2.12.1</version>
</dependency>

得益於maven的依賴傳遞機制,咱們不須要顯示聲明依賴SLF4j-API.jar。maven

能夠看到,log4j 多依賴了 log4j-slf4j-impl.jar,其實就是上圖所示的適配器層,讀者可能好奇,爲何使用 log4j 會有*_適配器*_層?其緣由在於,slf4j 並非官方規範,因此沒人遵照(也就是本身的日誌框架中沒有原生實現org.slf4j.Logger接口,如 log4j ),而綁定層( log4j-slf4j-impl.jar)的做用就是經過靜態查找的方式將使用log4j做爲實現(具體原理請關注後續文章),這樣就是實現了不依賴log4j而使用log4j輸出日誌(面向接口編程的最佳實踐,Ceki 大神就是用這套思想將 slf4j 作成了 Java 日誌的標準,爛牌翻盤的典範)。spring-boot

上面這一段講解了綁定(concrete-bindings)思想,是本文的精髓,讀者必定要理解這裏,後面還有橋接思想與之相似,請繼續閱讀。

小結

至此咱們已經完成了日誌的整合,可是事情真的這麼簡單嗎?

先梳理一下,如此混亂的日誌體系下(slf4j,jul,jcl,logback,log4j)會不會會產生什麼問題?答案是必定的,各類第三方庫使用了不一樣的日誌框架,若是咱們依賴 Spring ,Spring(非boot)的默認日誌實現是JCL、又或者咱們已有項目已經使用了Log4j,想使用logback的話,難道要逐個類改代碼嗎(官方有遷移工具)?咱們能不能只用一種框架來處理JUL(java.util.logging)、JCL(Jakarta Commons Logging)、Log4j一、Log4j2 呢?

答案是確定的,Ceki 的 Slf4j 給出瞭解決方案,就是上文所說的橋接( Bridging legacy),簡單來講就是劫持以上因此第三方日誌輸出並重定向至 SLF4j,最終實現統一日誌上層 API(編碼) 與下層實現(輸出日誌位置、格式統一)。咱們來看一下圖示:

橋接模式

上圖左側就是前一張圖的 logback 日誌實現,爲了兼容其餘日誌,咱們須要引用右側的橋接包:xxx-over/to-slf4j.jar ,xxx對應日誌框架,使用 logback 的狀況下,除了上文的 logback 依賴,還須要引入如下依賴才能保證全部日誌都被橋接至slf4j。

如何橋接?

logback 以下

<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.2.3</version>
</dependency>
<dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>jcl-over-slf4j</artifactId>
</dependency>
<dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>jul-to-slf4j</artifactId>
</dependency>
<!-- log4j 橋接包,slf4j官方實現,另有log4j官方實現,
     二選一便可 log4j-to-slf4j-->
<dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>log4j-over-slf4j</artifactId>
</dependency>

log4j2 以下

<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-slf4j-impl</artifactId>
  <version>2.12.1</version>
</dependency>
<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-core</artifactId>
  <version>2.12.1</version>
</dependency>
<!-- 如下是橋接包,使用了log4j做爲底層實現,
     不能再橋接log4j,不然會出現無限遞歸的狀況(具體緣由請關注後續文章) -->
<dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>jcl-over-slf4j</artifactId>
</dependency><dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>jul-to-slf4j</artifactId>
</dependency>

SpringBoot 項目引用了一部分依賴,因此使用起來略微有些不一樣:
logback 以下

<!-- logback做爲內置實現,使用相對簡單 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 引入缺乏的橋接包 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
</dependency>

log4j2 以下

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter</artifactId>
   <!-- 使用log4j2要排除logback依賴 -->
   <exclusions>
      <exclusion>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-logging</artifactId>
      </exclusion>
   </exclusions>
</dependency>
<!-- Spring已經寫好了一個log4j2-starter但缺乏橋接包 -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- 引入缺乏的橋接包 -->
<dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>jcl-over-slf4j</artifactId>
</dependency>

結束語

以上兩種纔是項目中的最佳使用方式,其餘筆者不推薦使用。

最後來看一下 slf4j 如何使用:

static final org.slf4j.Logger logger = LoggerFactory.getLogger(TestLog.class);
logger.trace("A TRACE Message");
logger.debug("A DEBUG Message");
logger.info("An INFO Message");
logger.warn("A WARN Message");
logger.error("An ERROR Message");

樣使用咱們就能夠隨意切換日誌實現而無需改動代碼,操做起來也簡單,只須要按照上文切換依賴便可。至於其餘使用細節本文不在贅述,關注後續文章(最佳實踐、配置文件、原理、擴展等)。

若是以爲寫的不錯,求關注、求點贊、求轉發,若是有問題或者文中有錯誤,歡迎留言討論。

掃描關注公衆號,第一時間得到更新
掃碼關注

相關文章
相關標籤/搜索