架構(gòu)師必備,帶你弄清混亂的JAVA日志體系

引言

還在為弄不清 commons-logging-xx.jar 、 log4j-xx.jar 、 sl4j-api-xx.jar 等日志框架之間復(fù)雜的關(guān)系而感到煩惱嗎?

還在為如何統(tǒng)一系統(tǒng)的日志輸出而感到不知所措嘛?

您是否依然存在這樣的煩惱。比如,要更改spring的日志輸出為log4j 2,卻不知該引哪些jar包,只知道去百度一下所謂的博客,照著人家復(fù)制,卻無法弄懂其中的原理?

不要急,不要方!本文帶你們弄懂其中的原理,只要你靜下心看本文,你就能隨心所欲更改你系統(tǒng)里的日志框架,統(tǒng)一日志輸出!

正文

日志框架發(fā)展史

早年,你工作的時候,在日志里使用了log4j框架來輸出,于是你代碼是這么寫的

import org.apache.log4j.Logger;

\\省略

Logger logger = Logger.getLogger(Test.class);

logger.trace("trace");

\\省略

但是,歲月流逝,sun公司對于log4j的出現(xiàn)內(nèi)心隱隱表示嫉妒。于是在jdk1.4版本后,增加了一個包為java.util.logging,簡稱為jul,用以對抗log4j。于是,你的領(lǐng)導(dǎo)要你把日志框架改為jul,這時候你只能一行行的將log4j的api改為jul的api,如下所示

import java.util.logging.Logger;

\\省略

Logger loggger = Logger.getLogger(Test.class.getName()); logger.finest("finest");

\\省略

可以看出,api完全是不同的。那有沒有辦法,將這些api抽象出接口,這樣以后調(diào)用的時候,就調(diào)用這些接口就好了呢?

這個時候jcl(Jakarta Commons Logging)出現(xiàn)了,說jcl可能大家有點陌生,講 commons-logging-xx.jar 組件,大家總有印象吧。JCL 只提供 log 接口,具體的實現(xiàn)則在運行時動態(tài)尋找。這樣一來組件開發(fā)者只需要針對 JCL 接口開發(fā),而調(diào)用組件的應(yīng)用程序則可以在運行時搭配自己喜好的日志實踐工具。JCL可以實現(xiàn)的集成方案如下圖所示

jcl默認的配置:如果能找到Log4j 則默認使用log4j 實現(xiàn),如果沒有則使用jul(jdk自帶的) 實現(xiàn),再沒有則使用jcl內(nèi)部提供的SimpleLog 實現(xiàn)。

于是,你在代碼里變成這么寫了

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

\\省略

Log log =LogFactory.getLog(Test.class);

log.trace('trace');

\\省略

至于這個Log具體的實現(xiàn)類,JCL會在ClassLoader中進行查找。這么做,有三個缺點,缺點一是效率較低,二是容易引發(fā)混亂,三是在使用了自定義ClassLoader的程序中,使用JCL會引發(fā)內(nèi)存泄露。

于是log4j的作者覺得jcl不好用,自己又寫了一個新的接口api,那么就是slf4j。關(guān)于slf4j的集成圖如下所示

如圖所示,應(yīng)用調(diào)了sl4j-api,即日志門面接口。日志門面接口本身通常并沒有實際的日志輸出能力,它底層還是需要去調(diào)用具體的日志框架API的,也就是實際上它需要跟具體的日志框架結(jié)合使用。由于具體日志框架比較多,而且互相也大都不兼容,日志門面接口要想實現(xiàn)與任意日志框架結(jié)合可能需要對應(yīng)的橋接器,上圖紅框中的組件即是對應(yīng)的各種橋接器!

我們在代碼中需要寫日志,變成下面這么寫

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

//省略

Logger logger = LoggerFactory.getLogger(Test.class);

// 省略

logger.info("info");

在代碼中,并不會出現(xiàn)具體日志框架的api。程序根據(jù)classpath中的橋接器類型,和日志框架類型,判斷出logger.info應(yīng)該以什么框架輸出!注意了,如果classpath中不小心引了兩個橋接器,那會直接報錯的!

因此,在阿里的開發(fā)手冊上才有這么一條

強制:應(yīng)用中不可直接使用日志系統(tǒng)(log4j、logback)中的 API ,而應(yīng)依賴使用日志框架 SLF4J 中的 API 。使用門面模式的日志框架,有利于維護和各個類的日志處理方式的統(tǒng)一。

ok,至此,基礎(chǔ)知識完畢,下面是實戰(zhàn)!

項目實戰(zhàn)

案例一

一個項目,一個模塊用log4j,另一個模塊用slf4j+log4j2,如何統(tǒng)一輸出?

其實在某些中小型公司,這種情況很常見。我曾經(jīng)見過某公司的項目,因為研發(fā)不懂底層的日志原理,日志文件里頭既有l(wèi)og4j.properties,又有l(wèi)og4j2.xml,各種API混用,慘不忍睹!

還有人用著jul的API,然后拿著log4j.properties,跑來問我,為什么配置不生效!簡直是一言難盡!

OK,回到我們的問題,如何統(tǒng)一輸出!OK,這里就要用上slf4j的適配器,slf4j提供了各種各樣的適配器,用來將某種日志框架委托給slf4j。其最明顯的集成工作方式有如下:

進行選擇填空,將我們的案例里的條件填入,顯然應(yīng)該選log4j-over-slf4j適配器,就變成下面這張圖

就可以實現(xiàn)日志統(tǒng)一為log4j2來輸出!

ps :根據(jù)適配器工作原理的不同,被適配的日志框架并不是一定要刪除!以上圖為例,log4j這個日志框架刪不刪都可以,你只要能保證log4j的加載順序在log4j-over-slf4j后即可。因為log4j-over-slf4j這個適配器的工作原理是,內(nèi)部提供了和log4j一模一樣的api接口,因此你在程序中調(diào)用log4j的api的時候,你必須想辦法讓其走適配器的api。如果你刪了log4j這個框架,那你程序里肯定是走log4j-over-slf4j這個組件里的api。如果不刪log4j,只要保證其在classpth里的順序比log4j前即可!

案例二

如何讓spring以log4j2的形式輸出?

spring默認使用的是jcl輸出日志,由于你此時并沒有引入Log4j的日志框架,jcl會以jul做為日志框架。此時集成圖如下

而你的應(yīng)用中,采用了slf4j+log4j-core,即log4j2進行日志記錄,那么此時集成圖如下

那我們現(xiàn)在需要讓spring以log4j2的形式輸出?怎么辦?

OK,第一種方案,走jcl-over-slf4j適配器,此時集成圖就變成下面這樣了

在這種方案下,spring框架中遇到日志輸出的語句,就會如上圖紅線流程一樣,最終以log4J2的形式輸出!

OK,有第二種方案么?

有,走jul-to-slf4j適配器,此時集成圖如下

ps :這種情況下,記得在代碼中執(zhí)行

SLF4JBridgeHandler.removeHandlersForRootLogger();

SLF4JBridgeHandler.install();

這樣jul-to-slf4j適配器才能正常工作。

天啦嚕!要死循環(huán)

假設(shè),我們在應(yīng)用中調(diào)用了sl4j-api,但是呢,你引了四個jar包,slf4j-api-xx.jar,slf4j-log4j12-xx.jar,log4j-xx.jar,log4j-over-slf4j-xx.jar,于是你就會出現(xiàn)如下尷尬的場面

如上圖所示,在這種情況下,你調(diào)用了slf4j-api,就會陷入死循環(huán)中!slf4j-api去調(diào)了slf4j-log4j12,slf4j-log4j12又去調(diào)用了log4j,log4j去調(diào)用了log4j-over-slf4j。最終,log4j-over-slf4j又調(diào)了slf4j-api,陷入死循環(huán)!

歡迎工作一到五年的Java工程師朋友們加入Java架構(gòu)開發(fā): 855835163

群內(nèi)提供免費的Java架構(gòu)學(xué)習資料(里面有高可用、高并發(fā)、高性能及分布式、Jvm性能調(diào)優(yōu)、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構(gòu)資料)合理利用自己每一分每一秒的時間來學(xué)習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容