日志到底是何方神圣煎殷?為什么要使用日志框架?
想必大家都有過使用 System.out 來進行輸出調(diào)試腿箩,開發(fā)開發(fā)環(huán)境下這樣做當(dāng)然很方便豪直,但是線上這樣做就有麻煩了:
系統(tǒng)一直運行,輸出越來越多珠移,磁盤空間逐漸被寫滿
不同的業(yè)務(wù)想要把日志輸出在不同的位置
有些場合為了更高性能弓乙,盡量控制減少日志輸出,需要動態(tài)調(diào)整日志輸出量
自動輸出日志相關(guān)信息钧惧,比如:日期暇韧、線程、方法名稱等等
顯然 System.out 解決不了我們的問題浓瞪,但是我們遇到的問題一定會有前人遇到過懈玻,日志也不例外,其中就有一個大牛?Ceki?乾颁,整個Java的日志體系幾乎都有Ceki參與或者受到了Ceki的深度影響涂乌。當(dāng)然Java日志體系的復(fù)雜度也有一部分原因是拜這位大牛所賜。
Java日志的恩怨情仇
1996年早期英岭,歐洲安全電子市場項目組決定編寫它自己的程序跟蹤API(Tracing API)湾盒。經(jīng)過不斷的完善,這個API終于成為一個十分受歡迎的Java日志軟件包诅妹,即Log4j(由Ceki創(chuàng)建)罚勾。
后來Log4j成為Apache基金會項目中的一員,Ceki也加入Apache組織。后來Log4j近乎成了Java社區(qū)的日志標(biāo)準(zhǔn)。據(jù)說Apache基金會還曾經(jīng)建議Sun引入Log4j到Java的標(biāo)準(zhǔn)庫中荧库,但Sun拒絕了堰塌。
2002年Java1.4發(fā)布,Sun推出了自己的日志庫JUL(Java Util Logging),其實現(xiàn)基本模仿了Log4j的實現(xiàn)分衫。在JUL出來以前场刑,Log4j就已經(jīng)成為一項成熟的技術(shù),使得Log4j在選擇上占據(jù)了一定的優(yōu)勢蚪战。
接著牵现,Apache推出了Jakarta Commons Logging,JCL只是定義了一套日志接口(其內(nèi)部也提供一個Simple Log的簡單實現(xiàn))邀桑,支持運行時動態(tài)加載日志組件的實現(xiàn)瞎疼,也就是說,在你應(yīng)用代碼里壁畸,只需調(diào)用Commons Logging的接口贼急,底層實現(xiàn)可以是Log4j,也可以是Java Util Logging捏萍。
后來(2006年)太抓,Ceki不適應(yīng)Apache的工作方式,離開了Apache令杈。然后先后創(chuàng)建了Slf4j(日志門面接口走敌,類似于Commons Logging)和Logback(Slf4j的實現(xiàn))兩個項目,并回瑞典創(chuàng)建了QOS公司逗噩,QOS官網(wǎng)上是這樣描述Logback的:The Generic掉丽,Reliable Fast&Flexible Logging Framework(一個通用,可靠异雁,快速且靈活的日志框架)捶障。
Java日志領(lǐng)域被劃分為兩大陣營:Commons Logging陣營和Slf4j陣營。
Commons Logging在Apache大樹的籠罩下片迅,有很大的用戶基數(shù)残邀。但有證據(jù)表明,形式正在發(fā)生變化柑蛇。2013年底有人分析了GitHub上30000個項目芥挣,統(tǒng)計出了最流行的100個Libraries,可以看出Slf4j的發(fā)展趨勢更好耻台。
Apache眼看有被Logback反超的勢頭空免,于2012-07重寫了Log4j 1.x,成立了新的項目Log4j 2, Log4j 2具有Logback的所有特性盆耽。
如今日志框架已經(jīng)發(fā)展為:Slf4j作為API蹋砚,實現(xiàn)分為logback與log4j(Commons Logging因為效率和API設(shè)計等問題扼菠,現(xiàn)在逐漸淡出舞臺了)
讓我們來瞻仰一下大神,哈哈:
那么如何在混亂的Java日志體系中如何優(yōu)雅的使用日志呢坝咐?
其實在Ceki設(shè)計的體系下循榆,日志如同Java的JDBC、Servelt等一樣墨坚,定義好標(biāo)準(zhǔn)后實現(xiàn)可以互相切換秧饮,問題在于定標(biāo)準(zhǔn)的人各自為政搞出來好多標(biāo)準(zhǔn),JCL泽篮、SLF4j等等盗尸,官方(Sun公司)又晚又不給力,發(fā)展到現(xiàn)在終于被SLF4j以一種巧妙的方式(橋接帽撑、綁定泼各,見下文)統(tǒng)一了,標(biāo)準(zhǔn)使用方式如下圖:
這個圖截取自?slf4j手冊?亏拉,簡化了多余部分扣蜻,很清晰的表示了使用方式:
應(yīng)用引用SLF4j-API(編碼時使用SLF4j的接口 org.slf4j.Logger ,而非logback或log4j的實現(xiàn))
logbak: slf4j會自動查找logback實現(xiàn)(logback默認實現(xiàn)了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溪烤。
可以看到味咳,log4j 多依賴了 log4j-slf4j-impl.jar,其實就是上圖所示的適配器層檬嘀,讀者可能好奇槽驶,為什么使用 log4j 會有?適配器?層?其原因在于鸳兽,slf4j 并不是官方規(guī)范掂铐,所以沒人遵守(也就是自己的日志框架中沒有原生實現(xiàn)?org.slf4j.Logger?接口,如 log4j )揍异,而綁定層( log4j-slf4j-impl.jar)的作用就是通過靜態(tài)查找的方式將使用log4j作為實現(xiàn)(具體原理請關(guān)注后續(xù)文章)全陨,這樣就是實現(xiàn)了不依賴log4j而使用log4j輸出日志(面向接口編程的最佳實踐,Ceki 大神就是用這套思想將 slf4j 做成了 Java 日志的標(biāo)準(zhǔn)衷掷,爛牌翻盤的典范)辱姨。
上面這一段講解了綁定(concrete-bindings)思想,是本文的精髓戚嗅,讀者一定要理解這里雨涛,后面還有橋接思想與之類似枢舶,請繼續(xù)閱讀。
小結(jié)
至此我們已經(jīng)完成了日志的整合替久,但是事情真的這么簡單嗎凉泄?
先梳理一下,如此混亂的日志體系下(slf4j蚯根,jul旧困,jcl,logback稼锅,log4j)會不會會產(chǎn)生什么問題吼具?答案是一定的,各種第三方庫使用了不同的日志框架矩距,如果我們依賴 Spring 拗盒,Spring(非boot)的默認日志實現(xiàn)是JCL、又或者我們已有項目已經(jīng)使用了Log4j锥债,想使用logback的話陡蝇,難道要逐個類改代碼嗎(官方有遷移工具)?我們能不能只用一種框架來處理JUL(java.util.logging)哮肚、JCL(Jakarta Commons Logging)登夫、Log4j1、Log4j2 呢允趟?
答案是肯定的恼策,Ceki 的 Slf4j 給出了解決方案,就是上文所說的橋接( Bridging legacy)潮剪,簡單來說就是劫持以上所以第三方日志輸出并重定向至 SLF4j涣楷,最終實現(xiàn)統(tǒng)一日志上層 API(編碼) 與下層實現(xiàn)(輸出日志位置、格式統(tǒng)一)抗碰。我們來看一下圖示
上圖左側(cè)就是前一張圖的 logback 日志實現(xiàn)狮斗,為了兼容其他日志,我們需要引用右側(cè)的橋接包:xxx-over/to-slf4j.jar 弧蝇,xxx對應(yīng)日志框架碳褒,使用 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官方實現(xiàn)专酗,另有l(wèi)og4j官方實現(xiàn),二選一即可 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作為底層實現(xiàn)祷肯,? ? 不能再橋接log4j沉填,否則會出現(xiàn)無限遞歸的情況(具體原因請關(guān)注后續(xù)文章) --><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作為內(nèi)置實現(xiàn)佑笋,使用相對簡單 --><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已經(jīng)寫好了一個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>
結(jié)束語
以上兩種才是項目中的最佳使用方式翼闹,其他筆者不推薦使用。
最后來看一下 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");
這樣使用我們就可以隨意切換日志實現(xiàn)而無需改動代碼蒋纬,操作起來也簡單猎荠,只需要按照上文切換依賴即可。至于其他使用細節(jié)本文不在贅述蜀备,關(guān)注后續(xù)文章(最佳實踐关摇、配置文件、原理碾阁、擴展等)输虱。