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