1. 問(wèn)題回顧
問(wèn)題背景是在進(jìn)行中臺(tái)應(yīng)用中間件遷移過(guò)程中,發(fā)現(xiàn)存在項(xiàng)目啟動(dòng)失敗或者項(xiàng)目正常啟動(dòng)(jsf正常掛載并正常運(yùn)行,mq正常發(fā)送和消費(fèi))但是無(wú)任何日志打印現(xiàn)象。更奇怪的是不打印日志竟然是偶發(fā)的,在測(cè)試環(huán)境中多次部署都未出現(xiàn)項(xiàng)目啟動(dòng)但無(wú)日志打印情況,而且玄學(xué)的是生產(chǎn)環(huán)境兩臺(tái)機(jī)器,其中一臺(tái)正常日志打印,另一臺(tái)無(wú)任何日志打印(應(yīng)用運(yùn)行正常)。
通過(guò)多次重啟無(wú)日志打印機(jī)器仍未恢復(fù)日志打印,最終通過(guò)排查發(fā)現(xiàn)問(wèn)題在于項(xiàng)目中引入的多個(gè)日志jar包沖突,進(jìn)而導(dǎo)致無(wú)日志打印現(xiàn)象。
圖1 場(chǎng)景1項(xiàng)目啟動(dòng)失敗和場(chǎng)景2項(xiàng)目目正常啟動(dòng)但是無(wú)日志打印
圖2 運(yùn)行項(xiàng)目所包含的日志jar包
2. 日志框架
日志框架通常分為兩大類:
?
日志門面
(Logging Facade):如SLF4J(Simple Logging Facade for Java)和JCL(Apache Commons Logging),它們提供了一層抽象接口,使得開(kāi)發(fā)者可以編寫與具體日志實(shí)現(xiàn)無(wú)關(guān)的代碼。這樣在不修改代碼的情況下,可以靈活地切換底層的日志實(shí)現(xiàn)框架。?
日志實(shí)現(xiàn)
(Logging Implementation):如Logback、Log4j、java.util.logging (JUL)等,它們是具體的日志庫(kù),負(fù)責(zé)實(shí)際的日志生成、處理和存儲(chǔ)工作。這些實(shí)現(xiàn)直接響應(yīng)門面層的請(qǐng)求,執(zhí)行日志操作。
圖3 日志門面和日志實(shí)現(xiàn)
日志門面使用到了一種設(shè)計(jì)模式:門面模式,接下來(lái)簡(jiǎn)單介紹下門面模式。下面是門面模式的一個(gè)典型調(diào)用過(guò)程,其核心為外部與一個(gè)子系統(tǒng)的通信必須通過(guò)一個(gè)統(tǒng)一的外觀對(duì)象進(jìn)行,使得子系統(tǒng)更易于使用。 下圖中客戶端不需要直接調(diào)用幾個(gè)子系統(tǒng),只需要與統(tǒng)一的門面進(jìn)行通信即可。
圖4 門面模式的一個(gè)典型調(diào)用過(guò)程
門面模式的核心為Facade即門面對(duì)象,核心為幾個(gè)點(diǎn):
?知道所有子角色的功能和責(zé)任。?將客戶端發(fā)來(lái)的請(qǐng)求委派到子系統(tǒng)中,沒(méi)有實(shí)際業(yè)務(wù)邏輯。?不參與子系統(tǒng)內(nèi)業(yè)務(wù)邏輯的實(shí)現(xiàn)。
舉個(gè)栗子
當(dāng)你通過(guò)電話給商店下達(dá)訂單時(shí), 接線員就是該商店的所有服務(wù)和部門的外觀。 接線員為你提供了一個(gè)同購(gòu)物系統(tǒng)、 支付網(wǎng)關(guān)和各種送貨服務(wù)進(jìn)行互動(dòng)的簡(jiǎn)單語(yǔ)音接口。
注:具體想要了解門面模式的可以參看這篇文章。?
2.1 為什么要引入日志門面?
回答這個(gè)問(wèn)題之前,我們先看看如果需要用上面幾個(gè)日志框架來(lái)打印日志,一般怎么做,具體代碼如下:
// 使用log4j,需要log4j.jar import org.apache.log4j.Logger; Logger logger_log4j = Logger.getLogger(Test.class); logger_log4j.info("Hello World!"); // 使用log4j2,需要log4j-api.jar、log4j-core.jar import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; Logger logger_log4j2 = LogManager.getLogger(Test.class); logger_log4j2.info("Hello World!"); // logback,需要logback-classic.jar、logback-core.jar import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; Logger logger_logback = new LoggerContext().getLogger(Test.class); logger_logback.info("Hello World!");
從上面不難看出,使用不同的日志框架需要要引入不同的jar包,使用不同的代碼獲取Logger。如果項(xiàng)目升級(jí)需要更換不同的框架,那么就需要修改所有的地方來(lái)獲取新的Logger,這將會(huì)產(chǎn)生巨大的工作量。
基于此,我們需要一種接口來(lái)將不同的日志框架的使用統(tǒng)一起來(lái),這也是為什么要使用SLF4J的原因。
日志門面——SLF4J
即簡(jiǎn)單日志門面(Simple Logging Facade for Java),不是具體的日志解決方案,它只服務(wù)于各種各樣的日志系統(tǒng)。按照官方的說(shuō)法,SLF4J是一個(gè)用于日志系統(tǒng)的簡(jiǎn)單Facade,允許最終用戶在部署其應(yīng)用時(shí)使用其所希望的日志系統(tǒng)。
另一個(gè)常用的日志門面——JCL
常見(jiàn)的日志門面還有一個(gè)叫JCL(Jakarta Common logging),這個(gè)是在2001年左右旨在解決日志實(shí)現(xiàn)多樣性的問(wèn)題,允許開(kāi)發(fā)者編寫與具體日志實(shí)現(xiàn)無(wú)關(guān)的代碼,并作為第一個(gè)廣泛使用的日志門面被提出了。其中SLF4J日志門面是在2006年,由Ceki Gülcü,同時(shí)也是Log4j的創(chuàng)始人,推出了SLF4J,這是一個(gè)更為先進(jìn)、設(shè)計(jì)更優(yōu)的日志門面,旨在克服JCL存在的問(wèn)題,如類加載沖突和運(yùn)行時(shí)綁定的不確定性。
2.2 SLF4J和JCL的主要區(qū)別?
1. 動(dòng)態(tài)與靜態(tài)綁定
?JCL:采用
動(dòng)態(tài)綁定
機(jī)制,意味著它在
運(yùn)行時(shí)
通過(guò)類加載器查找并決定使用哪個(gè)日志實(shí)現(xiàn)(如Log4j、JUL等)。這種方式
可能導(dǎo)致類加載順序
問(wèn)題,尤其是在類路徑復(fù)雜的應(yīng)用中,可能會(huì)引起
不確定性和潛在的類加載沖突
。?SLF4J:提倡
靜態(tài)綁
定,
即在編譯時(shí)就確定日志實(shí)現(xiàn)
。SLF4J要求
在類路徑中明確包含一個(gè)到具體日志實(shí)現(xiàn)的橋接器
(如slf4j-log4j12.jar),這樣在編譯時(shí)就能確切知道日志將如何被處理。這減少了運(yùn)行時(shí)的不確定性,提高了性能,并且在日志實(shí)現(xiàn)未正確配置時(shí)能給出更明確的錯(cuò)誤提示。
2. 錯(cuò)誤處理與診斷
?JCL:如果日志實(shí)現(xiàn)沒(méi)有正確配置,可能會(huì)導(dǎo)致難以診斷的錯(cuò)誤,比如
NoClassDefFoundError
或
ClassNotFoundException
,因?yàn)镴CL在
運(yùn)行時(shí)
才會(huì)發(fā)現(xiàn)日志實(shí)現(xiàn)不可用。?SLF4J:在
初始化
時(shí),如果發(fā)現(xiàn)
不兼容的或缺失
的日志實(shí)現(xiàn),SLF4J會(huì)立即拋出一個(gè)明確的
警告或錯(cuò)誤信息
,幫助開(kāi)發(fā)者快速定位問(wèn)題。
3. 性能
?SLF4J:通常被認(rèn)為比JCL有更高的性能,尤其是當(dāng)使用靜態(tài)綁定時(shí),因?yàn)闇p少了解析和查找日志實(shí)現(xiàn)的開(kāi)銷。
4. API設(shè)計(jì)
?SLF4J:提供了更簡(jiǎn)潔、更易用的API,支持更靈活的日志級(jí)別控制和參數(shù)化日志消息,有助于減少字符串拼接的開(kāi)銷。
5. 社區(qū)與支持、更新與活躍度
?SLF4J:隨著時(shí)間的推移,SLF4J因其設(shè)計(jì)優(yōu)勢(shì)獲得了更廣泛的社區(qū)支持和采納,許多現(xiàn)代的Java庫(kù)和框架直接支持或推薦使用SLF4J。?SLF4J:相比JCL,SLF4J持續(xù)得到維護(hù)和更新,提供了對(duì)新特性和日志實(shí)現(xiàn)更好的支持。
綜上所述,SLF4J在設(shè)計(jì)上克服了JCL的一些缺陷,提供了更穩(wěn)定、高效和易于使用的日志接口,因此在新項(xiàng)目中更受推崇。而JCL盡管仍在一些遺留系統(tǒng)中使用,但已逐漸被SLF4J取代。
2.3 常用的日志實(shí)現(xiàn)
一些趣聞
使用過(guò)Log4J和LogBack的同學(xué)肯定能發(fā)現(xiàn),這兩個(gè)框架的設(shè)計(jì)理念極為相似,使用方法也如出一轍。其實(shí)這個(gè)兩個(gè)框架的作者都是一個(gè)人,Ceki Gülcü,土耳其軟件工程師。 Log4J 最初是基于Java開(kāi)發(fā)的日志框架,發(fā)展一段時(shí)間后,作者Ceki Gülcü將Log4j捐獻(xiàn)給了Apache軟件基金會(huì),使之成為了Apache日志服務(wù)的一個(gè)子項(xiàng)目。 又由于Log4J出色的表現(xiàn),后續(xù)又被孵化出了支持C, C++, C#, Perl, Python, Ruby等語(yǔ)言的子框架。 然而,偉大的程序員好像都比較有個(gè)性。Ceki Gülcü由于不滿Apache對(duì)Log4J的管理,決定不再參加Log4J的開(kāi)發(fā)維護(hù)?!俺鲎摺焙蟮腃eki Gülcü另起爐灶,開(kāi)發(fā)出了LogBack這個(gè)框架(SLF4J是和LogBack一起開(kāi)發(fā)出來(lái)的)。LogBack改進(jìn)了很多Log4J的缺點(diǎn),在性能上有了很大的提升,同時(shí)使用方式幾乎和Log4J一樣,許多用戶開(kāi)始慢慢開(kāi)始使用LogBack。 由于受到LogBack的沖擊,Log4J開(kāi)始式微。終于,2015年9月,Apache軟件基金業(yè)宣布,Log4j不再維護(hù),建議所有相關(guān)項(xiàng)目升級(jí)到Log4j2。Log4J2是Apache開(kāi)發(fā)的一個(gè)新的日志框架,改進(jìn)了很多Log4J的缺點(diǎn),同時(shí)也借鑒了LogBack,號(hào)稱在性能上也是完勝LogBack。性能這塊后續(xù)我會(huì)仔細(xì)分析。
根據(jù)這些日志實(shí)現(xiàn)的出現(xiàn)順序及特點(diǎn)整理出了一條時(shí)間線如下:
1999年: Log4j 1.x:由Ceki Gülcü(土耳其裔美國(guó)軟件工程師)創(chuàng)建,成為Java社區(qū)廣泛采用的第一個(gè)流行日志框架。它的出現(xiàn)使得開(kāi)發(fā)者能夠更方便地控制日志記錄,包括日志級(jí)別、輸出格式和目的地。
2001年: JUL (Java Util Logging):隨著Java 1.4的發(fā)布,Oracle(當(dāng)時(shí)是Sun Microsystems)引入了JUL作為標(biāo)準(zhǔn)的日志庫(kù)。雖然它是一個(gè)內(nèi)置的解決方案,但由于API相對(duì)復(fù)雜,開(kāi)發(fā)者普遍認(rèn)為它不如Log4j好用。
2003年: JCL (Apache Commons Logging):Apache軟件基金會(huì)推出JCL,作為日志門面,旨在提供一個(gè)統(tǒng)一的API,使得開(kāi)發(fā)者可以編寫與具體日志實(shí)現(xiàn)無(wú)關(guān)的代碼。然而,JCL在運(yùn)行時(shí)動(dòng)態(tài)加載日志實(shí)現(xiàn)的方式導(dǎo)致了類加載問(wèn)題和性能問(wèn)題。
2006年: SLF4J (Simple Logging Facade for Java):Ceki Gülcü,也是Log4j的創(chuàng)建者,推出了SLF4J,作為對(duì)JCL的改進(jìn)。SLF4J強(qiáng)調(diào)靜態(tài)綁定,提高了性能和穩(wěn)定性,并且支持更多的日志實(shí)現(xiàn),如Logback、Log4j 1.x等。
2007年: Logback:Ceki Gülcü同時(shí)推出了Logback,作為L(zhǎng)og4j的替代,設(shè)計(jì)為SLF4J的首選實(shí)現(xiàn)。Logback提供了更高效、更靈活的日志記錄功能,包括異步日志記錄和豐富的配置選項(xiàng)。
2010年: Log4j 2.x:Apache Log4j項(xiàng)目在2010年代進(jìn)行了重大升級(jí),推出了Log4j 2,它修復(fù)了Log4j 1.x的一些問(wèn)題,提供了更好的性能和更多特性,如異步日志記錄和更強(qiáng)大的配置能力。
2010年至今: 微服務(wù)和云原生日志:隨著微服務(wù)和云原生應(yīng)用的興起,日志收集和分析的需求變得更加復(fù)雜。工具如Loggly、Logstash、Fluentd、Elasticsearch、Kibana等開(kāi)始流行,它們與各種日志實(shí)現(xiàn)配合,提供了日志的集中處理、搜索、分析和可視化。 現(xiàn)代輕量級(jí)日志框架:
TinyLog:針對(duì)簡(jiǎn)單應(yīng)用和資源受限環(huán)境,輕量級(jí)的日志框架如TinyLog應(yīng)運(yùn)而生,提供簡(jiǎn)單易用的API,注重效率和小巧。
注:對(duì)TinyLog感興趣可參考這篇文章。?
圖5 日志演變路線
3. 日志門面和日志實(shí)現(xiàn)結(jié)合
3.1 日志門面如何和日志實(shí)現(xiàn)結(jié)合使用呢?
以比較常用的SLF4J為例,并結(jié)合現(xiàn)有比較常用的日志實(shí)現(xiàn)可歸納出以下幾種組合依賴結(jié)構(gòu)(如圖6),即SLF4J綁定到具體日志實(shí)現(xiàn)時(shí)需要引入的jar包依賴。圖6最下方給出的不同顏色的含義,分別是抽象接口、原生支持SLF4J的實(shí)現(xiàn)、適配層、非原生支持SLF4J的實(shí)現(xiàn)。
1.抽象接口層都是slf4j-api,很好理解,因?yàn)閟lf4j主要就是做日志門面。2.原生支持SLF4J的實(shí)現(xiàn):有l(wèi)ogback、slf4j-simple.jar、slf4j-nop.jar。3.非原生支持SLF4J的實(shí)現(xiàn),有l(wèi)og4j和jul,因?yàn)檫@兩個(gè)在SLF4J之前就出現(xiàn)了,后面SLF4j出現(xiàn)后,大家覺(jué)得這個(gè)日志門面很優(yōu)秀,所以出現(xiàn)了適配SLF4J和log4j、jul的橋接包,也就是下圖中的slf4j-reload4j.jar和slf4j-jdk14.jar4.log4j2是最后出現(xiàn)的,可以說(shuō)吸取了前面一些日志框架的優(yōu)點(diǎn),自成一體,所以未在下面的圖中出現(xiàn)。當(dāng)然SLF4J和log4j2也可以搭配,使用log4j-slf4j-impl的橋接包。
注:logback、slf4j-simple.jar、slf4j-nop.jar之所以能天然支持SLF4J的接口是有原因的,slf4j-simple.jar、slf4j-nop.jar都是slf4j自帶的實(shí)現(xiàn)框架,本身就是按slf4j-api的接口開(kāi)發(fā)的。logback之所以也天然適配SLF4J,有兩個(gè)原因,一是出現(xiàn)的先后原因,log4j ->JUL->JCL-> SLF4J -> logback -> log4j2,logback在SLF4J后面出現(xiàn),第二個(gè)是因?yàn)檫@兩個(gè)都是同一個(gè)作者寫的。
圖6 SLF4J與日志實(shí)現(xiàn)結(jié)構(gòu)圖
總結(jié)來(lái)說(shuō)主要的日志門面和日志實(shí)現(xiàn)的依賴搭配如下:
?
slf4j + logback
: slf4j-api.jar + logback-classic.jar + logback-core.jar?
slf4j + log4j 1.
x : slf4j-api.jar +
slf4j-log412.jar
+ log4j.jar?
slf4j + jul
: slf4j-api.jar + slf4j-jdk14.jar?
slf4j無(wú)日志實(shí)現(xiàn)
:slf4j-api.jar + slf4j-nop.jar
(日志不會(huì)被記錄:適合調(diào)試和測(cè)試環(huán)境,避免不必要的輸出)
注意到這里沒(méi)有l(wèi)og4j2依賴jar的關(guān)系,和log4j2配合需要導(dǎo)入log4j2的log4j-api.jar、log4j-core.jar和橋接包log4j-slf4j-impl.jar。
?
slf4j + log4j 2.x
:slf4j-api.jar + log4j-api.jar + log4j-core.jar + log4j-slf4j-impl.jar?
log4j 2.x
: log4j-core + log4j-api
(log4j 2.x 可單獨(dú)使用)
3.2 什么是橋接包?
聊起橋接包,需要回顧下之前提到的SLF4J。SLF4J通過(guò)定義一套API,使得應(yīng)用程序可以在不依賴具體日志實(shí)現(xiàn)的情況下進(jìn)行日志記錄。為了實(shí)現(xiàn)這一目標(biāo),SLF4J引入了StaticLoggerBinder這個(gè)關(guān)鍵組件。
StaticLoggerBinder是SLF4J API與底層日志實(shí)現(xiàn)之間的一個(gè)接口,它是一個(gè)單例類,負(fù)責(zé)在運(yùn)行時(shí)返回日志實(shí)現(xiàn)的LoggerFactory實(shí)例。這個(gè)類的存在使得SLF4J能夠在不直接引用具體日志庫(kù)的情況下,依然能夠找到并使用正確的日志實(shí)現(xiàn)。
橋接包(Bridge Package)的作用是解決已有代碼依賴特定日志框架(如Log4j 1.x)與SLF4J之間的兼容性問(wèn)題。例如,slf4j-log4j12.jar橋接包包含了SLF4J的StaticLoggerBinder實(shí)現(xiàn),這個(gè)實(shí)現(xiàn)將SLF4J的調(diào)用適配到Log4j 1.x的API上。這意味著即使代碼中使用了SLF4J API,日志記錄仍然可以通過(guò)Log4j 1.x來(lái)完成。
對(duì)于支持SLF4J的日志實(shí)現(xiàn),如Logback和Log4j 2.x,它們自身就提供了StaticLoggerBinder的實(shí)現(xiàn)。例如,Logback的logback-classic.jar和Log4j 2.x的log4j-slf4j-impl.jar模塊,都包含了一個(gè)符合SLF4J規(guī)范的StaticLoggerBinder,使得它們可以直接作為SLF4J的實(shí)現(xiàn)。因此不需要額外的橋接包,SLF4J能夠識(shí)別并使用這些日志實(shí)現(xiàn)進(jìn)行日志記錄。
總之,橋接包確保了SLF4J與傳統(tǒng)日志框架之間的兼容性,而StaticLoggerBinder則是SLF4J實(shí)現(xiàn)其核心功能的關(guān)鍵,即在運(yùn)行時(shí)找到并使用正確的日志實(shí)現(xiàn)。
注:具體有關(guān)StaticLoggerBinder底層實(shí)現(xiàn)可參考這篇文章。?
常用的橋接包:如使用SLF4J的API進(jìn)行編程,底層想使用log4j1來(lái)進(jìn)行實(shí)際的日志輸出,這就是slf4j-log4j12干的事。
?
slf4j-jdk14
: 讓SLF4J使用Java內(nèi)置的日志系統(tǒng)(JUL)。?
slf4j-log4j12
: 將SLF4J與Log4j 1.x綁定。?
log4j-slf4j-impl
: 綁定SLF4J到Log4j 2。?
logback-classi
c: SLF4J的實(shí)現(xiàn),使用Logback作為日志引擎。?
slf4j-jcl
: 橋接SLF4J到Apache Commons Logging。
3.3 如何從其它日志實(shí)現(xiàn)/門面到SLF4J呢?
其實(shí)大致的實(shí)現(xiàn)就是兩步,一是選擇SLF4J和具體實(shí)現(xiàn),二是兼容舊的日志實(shí)現(xiàn)/門面到SLF4J。
例如項(xiàng)目之前是用的JCL的API,不可能因?yàn)橐獡Q一個(gè)日志框架,把原先的日志代碼都改掉吧(API的方法不一樣,入?yún)⒑褪褂梅椒ㄒ膊灰粯樱?,這個(gè)代價(jià)太大。 我們希望的是,原有的日志代碼可以不動(dòng),后續(xù)的代碼可以用新的SLF4J的API,橋接包就是為了達(dá)到這樣的效果。具體操作就三步:1、移除掉舊的日志依賴2、引入SLF4J提供的橋接依賴3、項(xiàng)目中引入SLF4J和新的日志實(shí)現(xiàn)。
圖7 SLF4J相關(guān)橋接包依賴
場(chǎng)景介紹:如 使用log4j1的API進(jìn)行編程,但是想最終通過(guò)logback來(lái)進(jìn)行輸出,所以就需要先將log4j1的日志輸出轉(zhuǎn)交給slf4j來(lái)輸出,slf4j 再交給logback來(lái)輸出。將log4j1的輸出轉(zhuǎn)給slf4j,這就是log4j-over-slf4j做的事。
?
jul-to-slf4j
:jdk-logging到slf4j的橋梁,將jul的日志輸出切換到slf4j。?
log4j-over-slf4
j:log4j1到slf4j的橋梁,將log4j1的日志輸出切換到slf4j。?
jcl-over-slf4
j:commons-logging到slf4j的橋梁,將commons-logging的底層日志輸出切換到slf4j。
注:更詳細(xì)的SLF4J和不同日志實(shí)現(xiàn)的搭配以及各個(gè)日志系統(tǒng)之間的切換所需引用的具體jar包可參考這篇文章。?
3.4 橋接包導(dǎo)致的沖突
場(chǎng)景1:jcl-over-slf4j 與 slf4j-jcl 沖突
?
jcl-over-slf4j
: 這個(gè)橋接器的作用是將Apache Commons Logging(JCL)的日志調(diào)用轉(zhuǎn)換為SLF4J API。
如果你的代碼或依賴項(xiàng)使用了JCL API,但你希望統(tǒng)一日志處理并利用SLF4J的靈活性,可以引入jcl-over-slf4j。這將使得JCL的日志記錄調(diào)用被重定向到SLF4J,從而可以選擇和配置任何SLF4J兼容的日志實(shí)現(xiàn)。
?
slf4j-jcl
: 這個(gè)橋接器則是將SLF4J API的調(diào)用橋接到Apache Commons Logging。
如果你的項(xiàng)目中使用了SLF4J,但希望日志輸出通過(guò)Commons Logging處理,可以使用slf4j-jcl。這將SLF4J的日志調(diào)用映射到JCL,使得你的日志記錄通過(guò)Commons Logging的實(shí)現(xiàn)進(jìn)行。
如果這兩者共存的話,必然造成相互委托,造成內(nèi)存溢出
場(chǎng)景2:log4j-over-slf4j 與 slf4j-log4j12 沖突
?l
og4j-over-slf4j
: 這個(gè)庫(kù)的目的是將Log4j 1.x的日志API調(diào)用重定向到SLF4J API。
如果你的應(yīng)用程序原本使用Log4j 1.x進(jìn)行日志記錄,但你想利用SLF4J的靈活性,可以選擇使用log4j-over-slf4j。它會(huì)模擬Log4j的API,使得Log4j的配置和調(diào)用能夠透明地轉(zhuǎn)換為SLF4J,這樣你就可以在運(yùn)行時(shí)使用任何SLF4J兼容的日志實(shí)現(xiàn),如Logback或Log4j 2。
?
slf4j-log4j12
: 這個(gè)橋接器將SLF4J API的調(diào)用綁定到Log4j 1.x實(shí)現(xiàn)。
如果你的項(xiàng)目使用了SLF4J API,但希望日志輸出通過(guò)Log4j 1.x處理,那么可以引入slf4j-log4j12。這樣,所有的SLF4J調(diào)用都會(huì)被轉(zhuǎn)換為L(zhǎng)og4j的具體操作。
如果這兩者共存的話,理論上必然造成相互委托,造成內(nèi)存溢出。但是log4j-over-slf4內(nèi)部做了一個(gè)判斷,可以防止造成內(nèi)存溢出。
注意:log4j-over-slf4j庫(kù)在啟動(dòng)時(shí)會(huì)進(jìn)行內(nèi)部檢查,以確保它不會(huì)與Log4j 1.x直接使用或者其他SLF4J綁定(如slf4j-log4j12)沖突。它會(huì)檢查是否存在多個(gè)SLF4J綁定,特別是org.slf4j.impl.StaticLoggerBinder的實(shí)例,因?yàn)檫@個(gè)類是SLF4J用來(lái)確定實(shí)際日志實(shí)現(xiàn)的標(biāo)志。如果發(fā)現(xiàn)多個(gè)這樣的綁定,log4j-over-slf4j會(huì)拋出一個(gè)警告或異常,指出類路徑中存在沖突,并建議用戶清理類路徑以避免循環(huán)引用或日志記錄的不正確行為。 這個(gè)檢查通常在類加載時(shí)執(zhí)行,即當(dāng)應(yīng)用程序啟動(dòng)并嘗試加載log4j-over-slf4j時(shí)。如果檢測(cè)到類路徑中有其他SLF4J綁定,它會(huì)通過(guò)org.slf4j.LoggerFactory的靜態(tài)初始化來(lái)拋出錯(cuò)誤信息,而不是在運(yùn)行時(shí)導(dǎo)致內(nèi)存溢出。這種檢查機(jī)制有助于防止?jié)撛诘膯?wèn)題,并指導(dǎo)開(kāi)發(fā)者如何解決日志庫(kù)的沖突。
場(chǎng)景3:jul-to-slf4j 與 slf4j-jdk14 沖突
?
jul-to-slf4j
: 這個(gè)橋接器的作用是將Java內(nèi)置的日志框架java.util.logging(JUL)的日志記錄調(diào)用轉(zhuǎn)換為SLF4J API。
如果你的Java應(yīng)用使用了JUL API,但希望將日志記錄委托給SLF4J,以便于選擇和切換不同的日志實(shí)現(xiàn),那么可以引入jul-to-slf4j。這使得JUL的日志記錄能夠被SLF4J的實(shí)現(xiàn)如Logback或Log4j處理。
?
slf4j-jdk14
: 這個(gè)橋接器則剛好相反,它將SLF4J API的調(diào)用重定向到JUL。
這意味著,即使你的代碼使用SLF4J API,日志記錄實(shí)際上會(huì)通過(guò)JDK的java.util.logging框架進(jìn)行。這通常發(fā)生在你有一個(gè)使用SLF4J的庫(kù),但希望使用JUL作為日志實(shí)現(xiàn)的場(chǎng)景。請(qǐng)注意,使用這個(gè)橋接器可能會(huì)限制你對(duì)日志系統(tǒng)的控制和配置,因?yàn)镴UL通常不如SLF4J的其他實(shí)現(xiàn)那樣功能豐富。
如果這兩者共存的話,必然造成相互委托,造成內(nèi)存溢出
4. 處理日志包沖突
OK,到現(xiàn)在我們已經(jīng)清楚地知道日志門面與日志實(shí)現(xiàn)的對(duì)應(yīng)關(guān)系,以及在多個(gè)日志實(shí)現(xiàn)jar包存在的情況下如何通過(guò)橋接包實(shí)現(xiàn)我們期望的最終日志輸出效果,那么有沒(méi)有一種方式能夠幫助我們?cè)陧?xiàng)目啟動(dòng)的時(shí)候只管的發(fā)現(xiàn)是否存在日志jar包沖突呢(比如場(chǎng)景2情況能否提前感知呢)?回答這個(gè)問(wèn)題之前,我們需要先解答下文中最初的問(wèn)題,為什么同樣的應(yīng)用部署在不同的機(jī)器上面會(huì)出現(xiàn)不同的表征呢(一臺(tái)正常打印日志,另一臺(tái)雖正常啟動(dòng),但是無(wú)任何日志打?。??
4.1 無(wú)日志打印原因
答案:因?yàn)槊總€(gè)SLF4J的橋接包都有org.slf4j.impl.StaticLoggerBinder,SLF4J則會(huì)隨機(jī)選擇一個(gè)使用。當(dāng)選擇的跟系統(tǒng)配置的一樣時(shí)就可以打印日志,否則就打印不出。
查看acccheck應(yīng)用的pom文件定位對(duì)應(yīng)的jar包,發(fā)現(xiàn)同時(shí)共存多套日志jar包(圖8)。
日志門面: slf4j-api、 commons-logging 日志實(shí)現(xiàn): log4j、logback(logback-classic和logback-core) 橋接包: jcl-over-slf4j、slf4j-log4j12 日志記錄配置文件: logback.xml
很明顯該應(yīng)用是通過(guò)logback的日志實(shí)現(xiàn)方式來(lái)進(jìn)行日志記錄的,但是應(yīng)用中同時(shí)引用了日志門面SLF4J和JCL,并且在日志實(shí)現(xiàn)中同時(shí)引用了log4j和logback,兩個(gè)橋接包的作用分別是jcl-over-slf4j(commons-logging到slf4j的橋梁,將commons-logging的底層日志輸出切換到slf4j),slf4j-log4j12(將SLF4J的日志門面與log4j 1.x日志實(shí)現(xiàn)綁定)。
但是日志記錄的配置文件是logback.xml,所以當(dāng)SLF4J綁定到log4j日志實(shí)現(xiàn)時(shí),無(wú)法正常找到相關(guān)配置文件,故而無(wú)法輸出日志,只有當(dāng)SLF4J綁定到logback日志實(shí)現(xiàn)時(shí)才能夠正常進(jìn)行日志打印。
圖 8 acccheck應(yīng)用中應(yīng)用的相關(guān)日志jar包
4.2 解決方案
OK,現(xiàn)在已經(jīng)明確問(wèn)題是因?yàn)镾LF4J綁定到不同的日志實(shí)現(xiàn)導(dǎo)致的日志會(huì)出現(xiàn)無(wú)法記錄的表征,并且可以確定需要輸出的配置文件是logback.xml,那么處理方式已經(jīng)很清晰啦。
第一步:首先先確立需要使用的一套日志框架(日志門面+日志實(shí)現(xiàn)),在該應(yīng)用中使用SLF4J+Logback。不難發(fā)現(xiàn)滿足需求的日志jar包如下。
日志門面: slf4j-api 日志實(shí)現(xiàn): logback(logback-classic和logback-core)
第二步:需要對(duì)無(wú)用的日志jar包進(jìn)行去除,在該應(yīng)用中需要去除掉JCL(該jar包已經(jīng)通過(guò)橋接包 jcl-over-slf4j實(shí)現(xiàn)功能替換),去除log4j相關(guān)依賴(log4j的日志實(shí)現(xiàn)和SLF4J到log4j 1.x的橋接包slf4j-log4j12)。
第三步:需要評(píng)估是否存在引用需要共存場(chǎng)景,在該應(yīng)用中存在JCL日志門面,發(fā)現(xiàn)在代碼中未直接引用JCL包中的類和接口,因此橋接包 jcl-over-slf4j也無(wú)需保留。如果代碼中對(duì)JCL有直接引用的話可以通過(guò)引入橋接包jcl-over-slf4j實(shí)現(xiàn)功能替換。
4.3 監(jiān)控日志jar包沖突
回到本節(jié)的問(wèn)題,那么有沒(méi)有一種方式能夠幫助我們?cè)陧?xiàng)目啟動(dòng)的時(shí)候只管的發(fā)現(xiàn)是否存在日志jar包沖突呢(比如場(chǎng)景2情況能否提前感知呢)?答案是可以。
注:由于這塊不是本文的重點(diǎn),大家感興趣可以參考這篇文章。?
5. 總結(jié)
通過(guò)以上有關(guān)日志框架相關(guān)知識(shí)的介紹以及實(shí)踐,可以將解決日志框架共存/沖突問(wèn)題概括為需遵循一下幾個(gè)原則:
1.
明確
需要使用的一套日志實(shí)現(xiàn)2.
刪除
多余的無(wú)用日志依賴jar包3.視應(yīng)用的引用是否必須共存情況
引入橋接包
如果有引用必須共存的話,那么就移除原始包,使用“over”類型的包(over類型的包復(fù)制了一份原始接口,重新實(shí)現(xiàn))
4. 使用日志抽象提供的指定方式
不能over的,使用日志抽象提供的指定方式,例如jboss-logging中,可以通過(guò)org.jboss.logging.provider環(huán)境變量指定一個(gè)具體的日志框架實(shí)現(xiàn)
項(xiàng)目里統(tǒng)一了日志框架之后,無(wú)論用那種日志框架打印,最終還是走向我們中轉(zhuǎn)/適配后的唯一一個(gè)日志框架。解決了共存/沖突之后,項(xiàng)目里就只剩一款日志框架。再也不會(huì)出現(xiàn)“日志打不出”,“日志配置不生效”之類的各種惡心問(wèn)題。
最后補(bǔ)充一張以SLF4J為日志門面的適配方案圖(如圖9),目前SLF4J是適配方案中最核心的那個(gè)框架,也是圖9的中心樞紐。只要圍繞slf4j做適配/轉(zhuǎn)化,理論上就沒(méi)有處理不了的沖突。
圖 9 SLF4J的適配轉(zhuǎn)化流程圖
審核編輯 黃宇
-
接口
+關(guān)注
關(guān)注
33文章
8598瀏覽量
151163 -
日志
+關(guān)注
關(guān)注
0文章
138瀏覽量
10643 -
JCL
+關(guān)注
關(guān)注
0文章
2瀏覽量
6428
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論