歷史
log4j可以當(dāng)之無愧地說是Java日志框架的元老来庭,1999年發(fā)布首個(gè)版本宣决,2012年發(fā)布最后一個(gè)版本,2015年正式宣布終止吆你,至今還有無數(shù)的系統(tǒng)在使用log4j弦叶,甚至很多新系統(tǒng)的日志框架選型仍在選擇log4j。
然而老的不等于好的妇多,在IT技術(shù)層面更是如此湾蔓。盡管log4j有著出色的歷史戰(zhàn)績(jī),但早已不是Java日志框架的最優(yōu)選擇砌梆。
在log4j被Apache Foundation收入門下之后,由于理念不合贬循,log4j的作者Ceki離開并開發(fā)了slf4j和logback咸包。
slf4j因其優(yōu)秀的性能和理念很快受到了廣泛歡迎,2016年的統(tǒng)計(jì)顯示杖虾,github上的熱門Java項(xiàng)目中烂瘫,slf4j是使用率第二名的類庫(kù)(第一名是junit)。
logback則吸取了log4j的經(jīng)驗(yàn)奇适,實(shí)現(xiàn)了很多強(qiáng)大的新功能坟比,再加上它和slf4j能夠無縫集成,也受到了歡迎嚷往。
在這期間葛账,Apache Logging則一直在關(guān)門憋大招,log4j2在beta版鼓搗了幾年皮仁,終于在2014年發(fā)布了GA版籍琳,不僅吸收了logback的先進(jìn)功能菲宴,更通過優(yōu)秀的鎖機(jī)制、LMAX Disruptor趋急、"無垃圾"機(jī)制等先進(jìn)特性喝峦,在性能上全面超越了log4j和logback。
slf4j
slf4j是一個(gè)“日志門面”(Logging Facade)呜达,而不是一個(gè)完整的日志框架谣蠢。它提供了一套記錄日志的api,但不提供輸出日志的功能查近,而是通過對(duì)接如log4j眉踱、java.util.logging等日志框架,來實(shí)現(xiàn)日志的輸出嗦嗡。
所以說slf4j的對(duì)標(biāo)選手實(shí)際上是Jakarta Commons Logging(簡(jiǎn)稱JCL)勋锤,二者的最大區(qū)別在于與日志服務(wù)的綁定機(jī)制
JCL采用的動(dòng)態(tài)綁定機(jī)制:
- 在進(jìn)程啟動(dòng)時(shí)嘗試獲取名為"org.apache.commons.logging.Log"的配置屬性(可與在commons-logging.properties文件中配置,或使用Java代碼進(jìn)行配置)侥祭,按配置選取對(duì)應(yīng)的日志輸出服務(wù)
- 如果沒有獲取到對(duì)應(yīng)配置屬性叁执,會(huì)嘗試在系統(tǒng)參數(shù)中尋找名為"org.apache.commons.logging.Log"的參數(shù)項(xiàng)
- 如果1,2均沒有獲取到,會(huì)在classpath下尋找log4j的相關(guān)class矮冬,如果找到谈宛,則使用log4j作為日志輸出服務(wù)
- 如果沒有找到log4j,則嘗試使用java.util.logging包作為日志輸出服務(wù)
- 如果上述都失敗胎署,則使用SimpleLog作為日志輸出服務(wù)吆录,即將所有日志輸出至控制臺(tái)標(biāo)準(zhǔn)輸出System.err
JCL的動(dòng)態(tài)綁定機(jī)制基于ClassLoader實(shí)現(xiàn),缺點(diǎn)一是效率較低琼牧,二是容易引發(fā)混亂恢筝,在一個(gè)復(fù)雜甚至混亂的依賴環(huán)境下,確定當(dāng)前正在生效的日志服務(wù)是很費(fèi)力的巨坊,特別是在程序開發(fā)和設(shè)計(jì)人員并不理解JCL的機(jī)制時(shí)撬槽,三是最致命的問題:在使用了自定義ClassLoader的程序中,使用JCL會(huì)引發(fā)各類問題趾撵,例如內(nèi)存泄露侄柔、與OSGI沖突等。
而slf4j則簡(jiǎn)單得多占调,采用靜態(tài)綁定機(jī)制:
- slf4j為各類日志輸出服務(wù)提供了適配庫(kù)暂题,如slf4j-log4j12,slf4j-simple究珊,slf4j-jdk14等薪者。一個(gè)Java工程下只能引入一個(gè)slf4j適配庫(kù)
- slf4j會(huì)加載org.slf4j.impl.StaticLoggerBinder作為輸出日志的實(shí)現(xiàn)類。這個(gè)類在每個(gè)適配庫(kù)中都存在苦银,所以slf4j不需要像JCL一樣主動(dòng)去尋找日志輸出實(shí)現(xiàn)啸胧,自然而然地就能與具體的日志輸出實(shí)現(xiàn)綁定起來
- 當(dāng)需要更換日志輸出服務(wù)時(shí)(比如從logback切換回log4j)赶站,只需要替換掉適配庫(kù)即可
所以slf4j不僅對(duì)比JCL有性能上的優(yōu)勢(shì),使用slf4j的程序員也不需要去翻找配置文件或追蹤啟動(dòng)過程就能夠清除明白地了解當(dāng)前使用的是什么日志輸出服務(wù)纺念。
slf4j的優(yōu)勢(shì)還不止此:
強(qiáng)制輸出String贝椿,避免不規(guī)范代碼
傳統(tǒng)的日志api都接收Object類型的參數(shù),在程序員不遵守規(guī)范時(shí)容易引發(fā)一些錯(cuò)誤陷谱,比如:
SomeObject obj;
//...
logger.info(obj); //如果SomeObject并未覆蓋toString()方法烙博,這里就只記下來了hashcode
又如:
try {
//...
} catch(Exception e) {
logger.error(e); //未記錄異常stacktrace
}
slf4j的api強(qiáng)制要求傳入String類型的參數(shù),能夠在一定程度上避免此類不規(guī)范的代碼出現(xiàn)烟逊。
日志模板功能
在使用傳統(tǒng)的日志api時(shí)渣窜,可能會(huì)有這樣的代碼:
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
這引發(fā)了兩個(gè)問題:
- 需要編寫拼接字符串的代碼,使開發(fā)效率降低
- 即使不需要輸出這條日志(比如當(dāng)前日志級(jí)別是ERROR時(shí))宪躯,也會(huì)執(zhí)行拼接字符串的操作乔宿,消耗額外性能,占用額外內(nèi)存
而slf4j使用日志模板功能解決了這兩個(gè)問題:
logger.debug("Entry number: {} is {}", i, String.valueOf(entry[i]));
不僅開發(fā)變得簡(jiǎn)單了访雪,而且slf4j只會(huì)在此條日志確實(shí)需要輸出時(shí)才會(huì)去拼裝字符串详瑞。
并且在輸出異常信息時(shí)也可以使用模板,不會(huì)妨礙stacktrace的輸出:
String s = "Hello world";
try {
Integer i = Integer.valueOf(s);
} catch (NumberFormatException e) {
logger.error("Failed to format {}", s, e);
}
slf4j的橋接功能
雖然slf4j如此優(yōu)秀臣缀,但一些類庫(kù)因?yàn)闅v史原因仍然在使用JCL作為日志api(如Spring等)坝橡,為此slf4j還推出了jcl-over-slf4j橋接庫(kù),能夠把使用JCL的API輸出的日志橋接到slf4j上精置,方便那些想要使用slf4j作為日志門面但同時(shí)又要使用Spring等需要依賴JCL的類庫(kù)的系統(tǒng)计寇。
對(duì)于自動(dòng)依賴JCL的類庫(kù),如要橋接至slf4j的話脂倦,除了引入jcl-over-slf4j適配庫(kù)之外番宁,還需要把JCL庫(kù)從classpath中移除±底瑁可以在maven配置中將JCL庫(kù)標(biāo)記為provided:
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
<scope>provided</scope>
</dependency>
同時(shí)slf4j還提供了log4j-over-slf4j等橋接庫(kù)贝淤,能夠在不改動(dòng)代碼的前提下把使用各種日志框架輸出的日志都橋接到slf4j,如下圖:
slf4j提供的適配庫(kù)和橋接庫(kù)
適配庫(kù):
- slf4j-log4j12:使用log4j-1.2作為日志輸出服務(wù)
- slf4j-jdk14:使用java.util.logging作為日志輸出服務(wù)
- slf4j-jcl:使用JCL作為日志輸出服務(wù)
- slf4j-simple:日志輸出至System.err
- slf4j-nop:不輸出日志
- log4j-slf4j-impl:使用log4j2作為日志輸出服務(wù)
logback天然與slf4j適配政供,不需要額外引入適配庫(kù)(畢竟是一個(gè)作者寫的)
橋接庫(kù):
- log4j-over-slf4j:將使用log4j api輸出的日志橋接至slf4j
- jcl-over-slf4j:將使用JCL api輸出的日志橋接至slf4j
- jul-to-slf4j:將使用java.util.logging輸出的日志橋接至slf4j
- log4j-to-slf4j:將使用log4j2輸出的日志橋接至slf4j
題外話:
slf4j唯獨(dú)沒有提供log4j2的適配庫(kù)和橋接庫(kù),log4j-slf4j-impl和log4j-to-slf4j都是Apache Logging自己開發(fā)的朽基,看樣子Ceki和Apache Logging的梁子真的很深啊……倒是Apache沒有端架子布隔,可能也是因?yàn)閟lf4j太火了吧
logback與log4j2
logback和log4j2都宣稱自己是log4j的后代,一個(gè)是出于同一個(gè)作者稼虎,另一個(gè)則是在名字上根正苗紅衅檀。
撇開血統(tǒng)不談,比較一下log4j2和logback:
- log4j2比logback更新:log4j2的GA版在2014年底才推出霎俩,比logback晚了好幾年哀军,這期間log4j2確實(shí)吸收了slf4j和logback的一些優(yōu)點(diǎn)(比如日志模板)沉眶,同時(shí)應(yīng)用了不少的新技術(shù)
- 由于采用了更先進(jìn)的鎖機(jī)制和LMAX Disruptor庫(kù),log4j2的性能優(yōu)于logback杉适,特別是在多線程環(huán)境下和使用異步日志的環(huán)境下
- 二者都支持Filter(應(yīng)該說是log4j2借鑒了logback的Filter)谎倔,能夠?qū)崿F(xiàn)靈活的日志記錄規(guī)則(例如僅對(duì)一部分用戶記錄debug級(jí)別的日志)
- 二者都支持對(duì)配置文件的動(dòng)態(tài)更新
- 二者都能夠適配slf4j,logback與slf4j的適配應(yīng)該會(huì)更好一些猿推,畢竟省掉了一層適配庫(kù)
- logback能夠自動(dòng)壓縮/刪除舊日志
- logback提供了對(duì)日志的HTTP訪問功能
- log4j2實(shí)現(xiàn)了“無垃圾”和“低垃圾”模式片习。簡(jiǎn)單地說,log4j2在記錄日志時(shí)蹬叭,能夠重用對(duì)象(如String等)藕咏,盡可能避免實(shí)例化新的臨時(shí)對(duì)象,減少因日志記錄產(chǎn)生的垃圾對(duì)象秽五,減少垃圾回收帶來的性能下降
log4j2和logback各有長(zhǎng)處孽查,總體來說,如果對(duì)性能要求比較高的話坦喘,log4j2相對(duì)還是較優(yōu)的選擇盲再。
附上log4j2與logback性能對(duì)比的benchmark,這份benchmark是Apache Logging出的起宽,有多大水分不知道洲胖,僅供參考
同步寫文件日志的benchmark:
異步寫日志的benchmark:
當(dāng)然,這些benchmark都是在日志Pattern中不包含Location信息(如日志代碼行號(hào) 坯沪,調(diào)用者信息绿映,Class名/源碼文件名等)時(shí)測(cè)定的,如果輸出Location信息的話腐晾,性能誰也拯救不了:
對(duì)Java日志組件選型的建議
- slf4j已經(jīng)成為了Java日志組件的明星選手叉弦,可以完美替代JCL,使用JCL橋接庫(kù)也能完美兼容一切使用JCL作為日志門面的類庫(kù)藻糖,現(xiàn)在的新系統(tǒng)已經(jīng)沒有不使用slf4j作為日志API的理由了
- 日志記錄服務(wù)方面淹冰,log4j在功能上輸于logback和log4j2,在性能方面log4j2則全面超越log4j和logback巨柒。所以新系統(tǒng)應(yīng)該在logback和log4j2中做出選擇樱拴,對(duì)于性能有很高要求的系統(tǒng),應(yīng)優(yōu)先考慮log4j2
對(duì)現(xiàn)有系統(tǒng)日志架構(gòu)的改造建議
-
如果現(xiàn)有系統(tǒng)使用JCL作為日志門面洋满,又確實(shí)面臨著JCL的ClassLoader機(jī)制帶來的問題晶乔,完全可以引入slf4j并通過橋接庫(kù)將JCL api輸出的日志橋接至slf4j,再通過適配庫(kù)適配至現(xiàn)有的日志輸出服務(wù)(如log4j)牺勾,如下圖:
這樣做不需要任何代碼級(jí)的改造正罢,就可以解決JCL的ClassLoader帶來的問題,但沒有辦法享受日志模板等slf4j的api帶來的優(yōu)點(diǎn)驻民。不過之后在現(xiàn)系統(tǒng)上開發(fā)的新功能就可以使用slf4j的api了翻具,老代碼也可以分批進(jìn)行改造履怯。
如果現(xiàn)有系統(tǒng)使用JCL作為日志門面,又頭疼JCL不支持logback和log4j2等新的日志服務(wù)裆泳,也可以通過橋接庫(kù)以slf4j替代JCL叹洲,但同樣無法直接享受slf4j api的優(yōu)點(diǎn)。
如果想要使用slf4j的api晾虑,那么就不得不進(jìn)行代碼改造了疹味,當(dāng)然改造也可以參考1中提到的方式逐步進(jìn)行。
如果現(xiàn)系統(tǒng)面臨著log4j的性能問題帜篇,可以使用Apache Logging提供的log4j到log4j2的橋接庫(kù)log4j-1.2-api糙捺,把通過log4j api輸出的日志橋接至log4j2。這樣可以最快地使用上log4j2的先進(jìn)性能笙隙,但組件中缺失了slf4j洪灯,對(duì)后續(xù)進(jìn)行日志架構(gòu)改造的靈活性有影響。另一種辦法是先把log4j橋接至slf4j竟痰,再使用slf4j到log4j2的適配庫(kù)签钩。這樣做稍微麻煩了一點(diǎn),但可以逐步將系統(tǒng)中的日志輸出標(biāo)準(zhǔn)化為使用slf4j的api坏快,為后面的工作打好基礎(chǔ)铅檩。
最后附上一些鏈接
slf4j官網(wǎng): https://www.slf4j.org/
logback官網(wǎng): https://logback.qos.ch/
log4j2官網(wǎng): http://logging.apache.org/log4j/2.x/