log4j, log4j2, slf4j, logback關(guān)系
log4j是由Apache開發(fā)的一套元老級日志框架,為無數(shù)新老系統(tǒng)提供了日志服務(wù)窖梁;而后來log4j的作者Ceki因為某些原因離開Apache,并自己開發(fā)了性能更優(yōu)化的日志門面slf4j和新的日志框架logback臀晃,logback能夠與slf4j無縫集成搓扯;Apache之后也發(fā)力對log4j進行了多方面優(yōu)化,并推出了新的日志框架log4j2愤惰,相對于log4j和logback苇经,很大程度上提高了日志的吞吐量并降低了延時。
目前slf4j因為其優(yōu)秀的性能和“日志門面”的設(shè)計思想宦言,受到了廣泛的應(yīng)用扇单;而log4j2因為其性能已全面超越log4j與logback,也是日志系統(tǒng)中一個很重要的選擇奠旺。
除了這幾個糾葛比較深的日志框架外蜘澜,還包括其他的日志框架施流,common-logging, java.util.logging等。
日志門面與日志框架
slf4j就是典型的“日志門面(Logging Facade)”鄙信,利用了設(shè)計模式中的門面模式思想瞪醋,對外提供一套通用的日志記錄的API,而不提供具體的日志輸出服務(wù)装诡,要實現(xiàn)日志輸出银受,需要集成其他的日志框架,例如log4j2,logback,log4j,jul等鸦采。
這種門面模式的好處在于宾巍,記錄日志的API和日志輸出的服務(wù)分離開,代碼里面只需要關(guān)注記錄日志的API渔伯,通過slf4j指定的接口記錄日志顶霞;而日志輸出通過引入jar包的方式即可指定其他的日志框架。當(dāng)我們需要改變系統(tǒng)的日志輸出服務(wù)時锣吼,不用修改代碼确丢,只需要改變引入日志輸出框架jar包。
![slf4j集成原理.png-19.5kB](http://static.zybuluo.com/rainybowe/dakq2w4c68vda5d1j18pfbfu/slf4j%E9%9B%86%E6%88%90%E5%8E%9F%E7%90%86.png)
- 目前提供日志門面的框架包括:slf4j, common-logging
- 完整的日志框架包括:log4j2, logback, log4j, java.util.logging
(完整日志框架是指框架本身包括記錄日志的API和日志輸出的服務(wù))
需要指出一點吐限,門面模式提供了一種日志API和輸出分離的模式鲜侥,但是除slf4j和common logging之外的其他完整的日志框架,本身就具備同時提供日志API和輸出的服務(wù)诸典,當(dāng)然也是可以直接采用這些框架本身記錄日志的描函。
slf4j與common-logging
common-logging同樣是一套日志門面,spring框架本身使用的是common logging狐粱,與slf4j的區(qū)別主要在于與日志輸出服務(wù)的綁定機制舀寓。
common-logging采用運行時動態(tài)綁定的機制,運行時通過一套動態(tài)尋找綁定的規(guī)則:
- 在進程啟動時嘗試獲取名為"org.apache.commons.logging.Log"的配置屬性)肌蜻,按配置選取對應(yīng)的日志輸出服務(wù)
- 如果沒有獲取到對應(yīng)配置屬性互墓,會嘗試在系統(tǒng)參數(shù)中尋找名為"org.apache.commons.logging.Log"的參數(shù)項
- 如果均沒有獲取到,會在classpath下尋找log4j的相關(guān)class蒋搜,如果找到篡撵,則使用log4j作為日志輸出服務(wù)
- 如果沒有找到log4j,則嘗試使用java.util.logging包作為日志輸出服務(wù)
- 如果上述都失敗豆挽,則使用SimpleLog作為日志輸出服務(wù)育谬,即將所有日志輸出至控制臺標準輸出System.err
common-logging基于classLoader來動態(tài)尋找和加載所綁定的日志輸出服務(wù),但這種動態(tài)的方式效率不高帮哈;另外在一個復(fù)雜甚至混亂的依賴環(huán)境下膛檀,動態(tài)查找機制容易引發(fā)混亂;而且對于像OSGI這類需要使用自定義classLoader的框架,無法與common-logging一起工作咖刃。
slf4j日志輸出服務(wù)綁定則相對簡單很多泳炉,在編譯時就靜態(tài)綁定日志輸出服務(wù),只需要提前引入需要的日志框架嚎杨,以及引入slf4j到該框架的適配庫胡桃,常見的適配庫有l(wèi)og4j-slf4j-impl,slf4j-log4j12,slf4j-jdk14等磕潮,slf4j與logback天然集成翠胰,不需要適配庫(畢竟是一個作者寫的)。
slf4j的另外一些小優(yōu)點體現(xiàn)在提供的API上自脯,包括日志參數(shù)強制要求String類型之景,避免不規(guī)范代碼;提供支持填充參數(shù)的日志模板膏潮,而且只會在確實需要輸出日志時才會拼接日志字符串锻狗。
logger.error("Failed to format {}", s, e);
slf4j適配到各日志框架
基于slf4j的優(yōu)勢,目前常見的做法是使用slf4j做日志門面焕参,再結(jié)合其他日志輸出服務(wù)轻纪。目前除了logback之外,其他的日志框架無法直接與slf4j集成叠纷,因此需要我們在使用時引入各種適配庫刻帚,將基于slf4j API記錄的日志指向我們需要的日志框架進行輸出。下面列了一下slf4j到logback,log4j,java.util.logging,log4j2的適配庫涩嚣。
![slf4j-binding (1).png-30.4kB](http://static.zybuluo.com/rainybowe/un1dz9hk2slnk3kpuulyktd2/slf4j-binding%20(1).png)
除了slf4j本身需要引入的slf4j-api.jar之外崇众,其它還需要:
- slf4j+logback:
logback-classic.jar
,logback-core.jar
- slf4j+log4j:
slf44j-log4j12.jar
,log4j.jar
- slf4j+jul:
slf4j-jdk14.jar
- slf4j+log4j2:
log4j-slf4j-impl.jar
,log4j-api.jar
,log4j-core.jar
slf4j通過這些適配庫與各個日志框架集成的原理很簡單,首先我們在使用slf4j記錄日志時航厚,會首先初始化一個Logger
:
private static Logger logger=LoggerFactory.getLogger(TestClass.class);
slf4j的LoggerFactory提供的getLogger方法:
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
public static ILoggerFactory getILoggerFactory() {
…… ……
return StaticLoggerBinder.getSingleton().getLoggerFactory();
…… ……
}
可以看到顷歌,當(dāng)我們調(diào)用slf4j的LoggerFactory.getLogger()
方法時,適配庫的作用就是:
- 提供
org/slf4j/impl/StaticLoggerBinder.class
類幔睬,這個類的作用就是返回一個實現(xiàn)ILoggerFactory
接口的類(例如log4j-slf4j-impl
中返回Log4jLoggerFactory
類)眯漩; - 提供實現(xiàn)
ILoggerFactory
接口的類,該類實現(xiàn)getLogger()
方法麻顶,返回一個具體的logger
實例赦抖。
需要注意的是,當(dāng)我們使用slf4j日志門面之后澈蚌,只能指定一個slf4j的適配庫摹芙,否則會在編譯期間報錯灼狰。
各日志體系橋接到slf4j
如果目前應(yīng)用程序中已經(jīng)使用了如下混雜方式的API來進行日志的編程:
- commons-logging
- jdk-logging
- log4j
而程序希望統(tǒng)一通過logback進行日志輸出宛瞄,可以通過將這些日志框架橋接到slf4j,然后由slf4j指定logback做日志輸出的方式,這就需要指定各個日志框架到slf4j的橋接ba橋接包份汗。
- 去掉commons-logging(去不去都可以)盈电,使用jcl-over-slf4j將commons-logging的底層日志輸出切換到slf4j;
- 去掉log4j1(必須去掉),使用log4j-over-slf4j,將log4j1的日志輸出切換到slf4j
- 使用jul-to-slf4j杯活,將jul的日志輸出切換到slf4j
下圖是slf4j官網(wǎng)提供的橋接示例圖匆帚。
![此處輸入圖片的描述](https://www.slf4j.org/images/legacy.png)
這種橋接的方式的原理也好理解,橋接包中會直接提供與其他日志框架API相同路徑的類旁钧,替換掉它們本身的類吸重。例如jcl-over-slf4j會替換掉common-logging中的org.apache.commons.logging.LogFactory
類,這個類會使用slf4j創(chuàng)建logger實例歪今。
常見的log包沖突問題解決
1.項目中引入了slf4j的多個日志輸出框架嚎幸,導(dǎo)致報錯
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [slf4j-log4j12-1.7.12.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [logback-classic-1.1.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]
原因:slf4j的LoggerFactory中需要調(diào)用StaticLoggerBinder類,當(dāng)多個適配庫存在時寄猩,會有多個StaticLoggerBinder存在嫉晶。
解決:排掉多余的slf4j適配包,只保留需要的日志輸出服務(wù)田篇;
2.橋接包相互橋接替废,例如同時引入了log4j-over-slf4j 與 slf4j-log4j12,導(dǎo)致棧溢出
Exception in thread "main" java.lang.StackOverflowError
at java.util.Hashtable.containsKey(Hashtable.java:306)
at org.apache.log4j.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:36)
at org.apache.log4j.LogManager.getLogger(LogManager.java:39)
at org.slf4j.impl.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:73)
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:249)
at org.apache.log4j.Category.<init>(Category.java:53)
at org.apache.log4j.Logger..<init>(Logger.java:35)
at org.apache.log4j.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:39)
at org.apache.log4j.LogManager.getLogger(LogManager.java:39)
at org.slf4j.impl.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:73)
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:249)
at org.apache.log4j.Category..<init>(Category.java:53)
at org.apache.log4j.Logger..<init>(Logger.java:35)
at org.apache.log4j.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:39)
at org.apache.log4j.LogManager.getLogger(LogManager.java:39)
subsequent lines omitted...
原因:前者將log4j橋接到slf4j泊柬,后者將slf4j橋接到log4j椎镣,循環(huán)橋接,當(dāng)?shù)谝粋€通過slf4j或log4j獲取的logger被調(diào)用時兽赁,就會出現(xiàn)StackOverflowError衣陶。
解決:明確到底使用哪個日志框架,如果使用slf4j做日志API和輸出闸氮,則去掉slf4j-log4j12剪况;如果使用log4j做日志記錄和輸出,則去掉log4j-over-slf4j蒲跨。
3.項目中已有l(wèi)og4j做日志記錄API译断,同時又引入log4j-over-slf4j希望將log4j橋接到slf4j,但使用log4j記錄的日志沒有正常輸出或悲?
原因:log4j-over-slf4j橋接包的原理是替代log4j包本身的org.apache.log4j.Logger
類孙咪,如果引入了該橋接包,又沒有排除log4j本身的包巡语,導(dǎo)致使用Log4j做日志記錄的地方還是使用log4j做日志輸出翎蹈,然后項目里面沒有任何關(guān)于log4j的日志輸出配置,導(dǎo)致日志輸出失敗男公。
解決:解決方法很簡單荤堪,排除掉log4j的包即可。
另外關(guān)于slf4j相關(guān)的問題可以參考slf4j官網(wǎng)提供的一些常見問題和原因分析以及解決方法。
4.項目中依賴各種日志框架澄阳,有多個門面slf4j拥知,common logging,還有各種其他的日志框架log4j2, log4j, jul等碎赢;有用日志門面記錄日志的低剔,也有用非門面日志框架記錄日志的,總之肮塞,一片混亂 ~
思路:想給服務(wù)提供統(tǒng)一的日志輸出襟齿,可以將各種日志API首先橋接到slf4j,然后指定slf4j的日志輸出服務(wù)枕赵,這樣就算不同的日志記錄API蕊唐,也可以通過統(tǒng)一的日志輸出服務(wù)輸出日志。同時也要記得排除各種不需要的日志jar包烁设,解決各種循環(huán)橋接的問題替梨。
參考閱讀:
slf4j、jcl装黑、jul副瀑、log4j1、log4j2恋谭、logback大總結(jié)
slf4j與jul糠睡、log4j1、log4j2疚颊、logback的集成原理
該讓log4j退休了 - 論Java日志組件的選擇
混亂的 Java 日志體系