一摇零、聊聊java中混亂的日志體系
???♀?波妞:先提個(gè)問(wèn)題推掸,你知道哪些日志框架?
???♀?波妞:呃遂黍。终佛。。我說(shuō)說(shuō)搜我聽(tīng)過(guò)的吧雾家,比如log4j铃彰、jul、jcl芯咧、slf4j牙捉、log4j2竹揍、simple log、logback邪铲,已經(jīng)有7個(gè)了芬位,但是肯定不止7個(gè)。
???♂?宗介:臥槽带到,那在項(xiàng)目中我到底該用哪個(gè)昧碉,看得我腦殼痛,它們有啥子關(guān)系沒(méi)得嘛揽惹?
???♀?波妞:別著急被饿,下面讓我慢慢說(shuō)。
二搪搏、先打印幾個(gè)日志看看
1. 使用log4j輸出一下日志
- 先導(dǎo)入
log4j
的依賴(lài)<dependencies> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies>
- 然后在
classpath
下加上log4j
的配置文件log4j.properties
狭握,日志級(jí)別設(shè)置的是info
log4j.rootLogger=info,stdout # 輸出到控制臺(tái) log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.target=System.out log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %p [%t] %C.%M(%L) | %m%n
- 主程序
import org.apache.log4j.Logger; public class Log4j { public static void main(String[] args) { Logger logger = Logger.getLogger("lof4j"); logger.info("log4j"); } }
-
運(yùn)行程序,看到控制臺(tái)輸出了日志
2. 使用jul(java.util.logging)輸出一下日志
- 因?yàn)?code>jul是
jdk
自帶的日志包,所以不需要添加依賴(lài)囱嫩,直接干恃疯,主程序如下,注意導(dǎo)包導(dǎo)的是java.util.logging.Logger
import java.util.logging.Logger; public class Jul { public static void main(String[] args) { Logger logger = Logger.getLogger("jul"); logger.info("jul"); } }
-
運(yùn)行程序墨闲,查看控制臺(tái)
3.使用jcl輸出一下日志
- 先導(dǎo)入
jcl
的依賴(lài)<dependencies> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> </dependencies>
- 上主程序,這次不是直接通過(guò)
new
得到一個(gè)logger
了损俭,而是使用工廠去拿一個(gè)logger
蛙奖,這也是有原因的哈import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class Jcl { public static void main(String[] args) { Log log = LogFactory.getLog("jcl"); log.info("jcl"); } }
-
運(yùn)行看控制臺(tái)
-
把
log4j
的依賴(lài)加上試試愈捅,加上后的依賴(lài)變成了下面的樣子<dependencies> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies>
-
再次運(yùn)行脆贵,看看控制臺(tái)是什么樣子
-
聰明的你可能已經(jīng)猜到了,
jcl
根本不是一個(gè)日志實(shí)現(xiàn)(或者說(shuō)日志產(chǎn)品)昂拂,而是一個(gè)日志抽象層受神,主程序里面那個(gè)LogFactory.getLog("jcl")
拿到是才一個(gè)具體的日志產(chǎn)品的對(duì)象。所以我們猜測(cè)jcl
的結(jié)構(gòu)是下面這樣的
-
為了進(jìn)一步證實(shí)我們的猜想格侯,我們看看源碼鼻听,在
LogFactoryImpl
這個(gè)類(lèi)中發(fā)現(xiàn)一段關(guān)鍵代碼- 定義了一個(gè)成員變量
classesToDiscover
private static final String[] classesToDiscover = { LOGGING_IMPL_LOG4J_LOGGER, // org.apache.commons.logging.impl.Log4JLogger "org.apache.commons.logging.impl.Jdk14Logger", "org.apache.commons.logging.impl.Jdk13LumberjackLogger", "org.apache.commons.logging.impl.SimpleLog" };
- 代碼片段
for(int i=0; i<classesToDiscover.length && result == null; ++i) { result = createLogFromClass(classesToDiscover[i], logCategory, true); } if (result == null) { throw new LogConfigurationException("No suitable Log implementation"); } return result;
- 所以玄機(jī)就在這里财著,
LogFactoryImpl
中先定義了一個(gè)成員變量classesToDiscover
,它是一個(gè)String
數(shù)組撑碴,里面包含了log4j
和jul
的logger
的全限定名撑教。然后會(huì)在代碼里面遍歷這個(gè)數(shù)組,每次拿到一個(gè)全限定名并調(diào)用createLogFromClass
嘗試去實(shí)例化對(duì)象醉拓,如果沒(méi)實(shí)例化成功則會(huì)返回null
伟姐,繼續(xù)使用下一個(gè)全類(lèi)名去實(shí)例化對(duì)象∫诼保可以看到數(shù)組的第二個(gè)元素是Jdk14Logger
玫镐,它封裝的是jul
里面的logger
,而jul
是jdk
自帶的庫(kù)怠噪,所以99.99%
的情況下,如果你沒(méi)導(dǎo)入log4j
的jar
包杜跷,最終返回的logger
是jul
的logger
傍念。如果導(dǎo)入了log4j
的jar
包,那么在遍歷數(shù)組第一個(gè)元素后就會(huì)得到logger
對(duì)象了葛闷,此時(shí)得到的就是log4j
的logger
對(duì)象憋槐。這就是前面的例子中為什么沒(méi)加log4j
依賴(lài)時(shí),使用的jul
的logger
淑趾,加了log4j
依賴(lài)后使用的是log4j
的logger
原因阳仔。最后需要注意jul
是java.util.logging
包的簡(jiǎn)稱(chēng),是jdk4
正式發(fā)布的日志庫(kù)扣泊,可以說(shuō)是老妖怪近范。而Jdk13LumberjackLogger
應(yīng)該是對(duì)jdk1.3
及以前版本的東西的封裝(沒(méi)有深究過(guò),)延蟹,可以說(shuō)是個(gè)超級(jí)老妖怪评矩。你肯定不會(huì)去用jdk3
去做開(kāi)發(fā),所以不用在意Jdk13LumberjackLogger
阱飘。至于SimpleLog
斥杜,這個(gè)是jcl
的默認(rèn)實(shí)現(xiàn),簡(jiǎn)單但是功能不夠強(qiáng)大沥匈,很少用蔗喂,所以也不用在意啦。
- 定義了一個(gè)成員變量
4. 小總結(jié)
通過(guò)上面的demo
高帖,我們發(fā)現(xiàn)jc
l是一個(gè)日志抽象層缰儿。我們面向抽象編程,可以讓我們的程序可擴(kuò)展散址,可以無(wú)縫切換日志產(chǎn)品返弹,比如你今天用的log4j
锈玉,明天想用jul
了,那么直接把項(xiàng)目中的log4j
依賴(lài)排除掉即可义起。但是有個(gè)問(wèn)題拉背,看了源碼發(fā)現(xiàn)jcl
只支持4
個(gè)日志產(chǎn)品,還有兩個(gè)是沒(méi)用的默终,也就說(shuō)jcl
只支持log4j
和jul
∫喂祝現(xiàn)在市面上這么多日志產(chǎn)品,還有什么logback
呀齐蔽、nop
呀什么的两疚。我如果要用loback
,想面向抽象編程jcl
也不支持呀含滴。的確是的诱渤,因?yàn)?code>jcl已經(jīng)很久沒(méi)有更新了,沒(méi)有對(duì)新的日志框架做適配谈况。但是好消息是有個(gè)后起之秀來(lái)代替它勺美,它就是slf4j
。
三碑韵、下面我們開(kāi)始來(lái)說(shuō)說(shuō)java中日志系統(tǒng)的前世今生吧
起源:System.out和System.err
這應(yīng)該是最早的記錄日志的方式赡茸,不靈活、不能根據(jù)實(shí)際需要進(jìn)行一些選項(xiàng)配置
Ⅰ祝闻、1996年占卧,Log4j橫空出世
在1996
年初,E.U.SEMPER(歐洲安全電子市場(chǎng))
項(xiàng)目決定編寫(xiě)自己的跟蹤API
联喘,最后該API
演變?yōu)?code>Log4j华蜒,Log4j
日志軟件包一經(jīng)推出就備受歡迎,當(dāng)然這里必須要提到一個(gè)人豁遭,他是Log4j
的主要貢獻(xiàn)者友多,這個(gè)大佬叫??Ceki Gülcü
,可能應(yīng)該叫巨佬堤框。域滥。。后面你就明白了蜈抓。后來(lái)Log4j
成為了Apache
基金會(huì)項(xiàng)目中的一員启绰,同時(shí)Log4j
的火爆,讓Log4j
一度成為業(yè)內(nèi)日志標(biāo)桿沟使。(據(jù)說(shuō)???♂?Apache基金會(huì)
還曾經(jīng)建議????♀?Sun
引入Log4j
到java
的標(biāo)準(zhǔn)庫(kù)中委可,但是????♀?sun
拒絕了,????♀?sun
可能是有自己的私心)。
Ⅱ着倾、2002年2月拾酝,JUL誕生
果然????♀?sun
是有私心的(??我自己的親兒子怎么能用你擼出來(lái)的東西),于是在2002
年2
月Java1.4
發(fā)布卡者,????♀?sun
正式推出了自己的日志庫(kù)Java Util Logging
蒿囤,其實(shí)很多日志的實(shí)現(xiàn)思想也都是仿照Log4j
,畢竟Log4j
先出來(lái)很多年了崇决,已經(jīng)很成熟了此時(shí)材诽,這兩個(gè)日志工具打架,顯然Log4j
是更勝一籌恒傻。它們打架其實(shí)就是互相競(jìng)爭(zhēng)脸侥,????♀?sun
心里可能在想,不就是做個(gè)日志工具嘛盈厘,誰(shuí)不會(huì)睁枕!當(dāng)然好景不長(zhǎng)。
Ⅲ沸手、2002年8月外遇,JCL(Jakarta Commons Logging)出生,想一統(tǒng)江湖
終于罐氨,???♂?Apache
發(fā)言了, 玩編程滩援,誰(shuí)玩的過(guò)我栅隐!你不讓我成為jdk
標(biāo)準(zhǔn),我就把自己變成日志的標(biāo)準(zhǔn)玩徊,哼??W馇摹!6鞲ぁ泣棋!于是JUL
剛出來(lái)不久,2002
年8
月???♂?Apache
又推出了日志接口Jakarta Commons Logging
畔塔,也就是日志抽象層潭辈,當(dāng)然也提供了一個(gè)默認(rèn)實(shí)現(xiàn)Simple Log
,這野心很大澈吨,想一統(tǒng)日志抽象(就像以前的JDBC
一統(tǒng)數(shù)據(jù)庫(kù)訪問(wèn)層)把敢,讓日志產(chǎn)品去實(shí)現(xiàn)它的抽象,這樣只要你的日志代碼依賴(lài)的是JCL
的接口谅辣,你就可以很方便的在實(shí)現(xiàn)了jcl
接口的日志產(chǎn)品之間做切換修赞,當(dāng)時(shí)日志領(lǐng)域大概是這樣的結(jié)構(gòu),當(dāng)然也還是很容易理解的桑阶,也很優(yōu)雅柏副。
但是好景不長(zhǎng)勾邦,在使用過(guò)程中,雖然現(xiàn)在日志系統(tǒng)在JCL
的統(tǒng)一下很優(yōu)雅割择,很美好眷篇,但大家發(fā)現(xiàn)了JCL
還不夠好,有些人甚至認(rèn)為JCL
造成的問(wèn)題比解決的問(wèn)題還多...??????【抱怨地址】
Ⅳ锨推、2005年铅歼,slf4j(Simple Logging Facade for Java)出世,最后證明换可,后來(lái)者居上
大佬??Ceki Gülcü
(也就是Log4j
的作者)由于一些原因離開(kāi)了???♂?Apache
椎椰,之后覺(jué)得jcl
不好,于是于2005
年自己擼出一個(gè)新工程沾鳄,也就是一套新日志接口(也叫java簡(jiǎn)單日志門(mén)面):slf4j
(Simple Logging Facade for Java
)慨飘,感覺(jué)粗來(lái)了么。译荞。瓤的。這戰(zhàn)爭(zhēng)的硝煙 ??,明顯這個(gè)Slf4j
是劍指jcl
啊吞歼,但是后面確實(shí)也證明了slf4j
是要比jcl
的戰(zhàn)斗力更強(qiáng)圈膏。
??Ceki Gülcü
心想,和我玩接口篙骡,我一個(gè)人就是一支軍隊(duì)??稽坤!玩死你們??。
slf4j綁定器:綁定器是什么糯俗?
由于slf4j
出生的較晚尿褪,而且還只是一個(gè)日志接口,所以之前已經(jīng)出現(xiàn)的日志產(chǎn)品得湘,如JCL
和Log4j
都是沒(méi)有實(shí)現(xiàn)這個(gè)接口的杖玲,所以尷尬的是光有一個(gè)接口,沒(méi)有實(shí)現(xiàn)的產(chǎn)品也是很憋屈啊淘正,就算開(kāi)發(fā)者想用Slf4j
也是用不了摆马,這時(shí)候,巨佬??Ceki Gülcü發(fā)話了鸿吆。
??Ceki Gülcü
:咳咳....今膊,別急,我早幫你們想好了伞剑,要讓????♀? sun
或者???♂?Apache
這兩個(gè)家伙來(lái)實(shí)現(xiàn)我的接口斑唬,太南啦,老鐵,但恕刘。缤谎。。我?guī)湍銈儗?shí)現(xiàn)褐着,不就完了么坷澡。。含蓉。
于是巨佬??Ceki Gülcü
擼了個(gè)綁定器(巨佬??Ceki Gülcü的slf4j官網(wǎng)說(shuō)的是slf4j binding
频敛,就叫它綁定器吧),他是這樣說(shuō)的馅扣,只需要在你的類(lèi)路徑下替換不同的綁定器斟赚,就可以實(shí)現(xiàn)日志框架的切換。比如要將jul
替換成log4j
差油,只需要用slf4j-log4j-xxx.jar
替換slf4j-jdk14-xxx.jar
(xxx表示版本號(hào))拗军。下面三張圖,仔細(xì)品味下哦蓄喇,特別是第二張发侵。
slf4j橋接器:橋接器是什么?
巨佬??Ceki Gülcü
的意思就是叔锐,你的組件可能依賴(lài)slf4j
以外的日志組件,并且這些日志組件不能改變成slf4j
罐柳,這樣就會(huì)產(chǎn)生日志沖突掌腰,在你的應(yīng)用中就會(huì)出現(xiàn)兩種及以上的日志狰住,造成日志混亂张吉。對(duì)于這種場(chǎng)景,巨佬??Ceki Gülcü
提供了slf4j橋
這個(gè)東西催植,讓其他框架的日志可以重定向到slf4j肮蛹,這樣就完美解決了日志沖突問(wèn)題。
下面舉一個(gè)栗子创南,先喝杯水??:
假設(shè)你的應(yīng)用中使用的日志框架是jul
伦忠,而??spring
使用的jcl
,jcl
又使用的log4j
稿辙,如下圖昆码。
現(xiàn)在有兩種解決方式:
-
一是使用
log4j
,因?yàn)槟憧梢愿淖约旱拇a,所以可以直接更換slf4j
綁定器赋咽,將應(yīng)用使用的日志框架切換到log4j
-
二是如果不想妥協(xié)旧噪,我就想使用
juc
,這個(gè)時(shí)候脓匿,你又改不了人家??spring
的代碼淘钟,讓??spring
不使用log4j
,怎么辦陪毡,橋接器登場(chǎng)
看了上面的介紹后是不是覺(jué)得绊起,有木有jio得巨佬??Ceki Gülcü
真的是個(gè)巨佬了精拟,他一個(gè)人干贏了????♀? sun
和???♂?Apache
,讓他們做自己的小弟??虱歪。
??Ceki Gülcü
心想:??挖鼻屎蜂绎,??和我斗,我一個(gè)人就能爆發(fā)出你們兩個(gè)加起來(lái)乘上99999的力量??笋鄙。
Logback(2006年)
巨佬??Ceki Gülcü
覺(jué)得师枣,使用slf4j
,要么需要加上綁定器萧落,要么需要家還是那個(gè)橋接包践美,因?yàn)槟切┤罩井a(chǎn)品都不是slf4j
的親生兒子,所以他覺(jué)得應(yīng)該讓slf4j
有個(gè)親兒子找岖,于是在2006年logback
誕生了陨倡,logback
完美實(shí)現(xiàn)了slf4j
。由于log4j
也是自己寫(xiě)的许布,他專(zhuān)門(mén)寫(xiě)了篇文章建議在你的應(yīng)用中首先考慮使用logback
而不是log4j
兴革,并指出logback
相比log4j
更好的原因--->文章地址,具體優(yōu)點(diǎn)體現(xiàn)在性能更好蜜唾、更安全可靠杂曲、更節(jié)約內(nèi)存等等,具體可以去看文章袁余,這里不多費(fèi)口舌了擎勘。
先對(duì)上面提到的東西做個(gè)小總結(jié),這里面只畫(huà)出了常用的日志產(chǎn)品颖榜,也就是log4j棚饵、jul和logback
Ⅴ煤裙、2012年,戰(zhàn)火還未結(jié)束噪漾,天降log4j2
在2012年积暖,???♂?Apache
直接推出新項(xiàng)目,不是對(duì)log4j1.x
升級(jí)怪与,而是新項(xiàng)目log4j2
夺刑,因?yàn)?code>log4j2是完全不兼容log4j1.x
的
并且很微妙的,log4j2
幾乎涵蓋logback
所有的特性(這不是對(duì)著干是啥~而且還有抄襲的嫌疑分别。遍愿。。哈哈哈)耘斩,更甚者的Log4j2
也搞了分離的設(shè)計(jì)沼填,分化成log4j-api
和log4j-core
,這個(gè)log4j-api
也是日志接口括授,log4j-core
才是日志產(chǎn)品坞笙。。荚虚。
emmm薛夜,我看到這,我都有點(diǎn)崩潰了
現(xiàn)在我們可有了3個(gè)日志接口(jcl
版述、slf4j
梯澜、log4j2
),以及4個(gè)日志產(chǎn)品(log4j
渴析、jul
晚伙、simple-log
、logback
)俭茧。咆疗。。當(dāng)然???♂?Apache
也知道該做啥母债,為了讓大家可以接入自己的Log4j2
午磁,那不就是綁定器嘛,或者要不就是橋接器(在官網(wǎng)只看到了橋接器场斑,沒(méi)有看到綁定器漓踢,我猜應(yīng)該是???♂?Apache
太懶了)牵署,???♂?Apache
也麻溜的推出了它的橋接包漏隐。
??。奴迅。青责。最后的圖大概是這樣挺据。嗯,畫(huà)完這張圖我掉了99999根頭發(fā)????脖隶。
Ⅵ扁耐、現(xiàn)在是2020年,各個(gè)公司使用不同的日志框架产阱,沒(méi)具體統(tǒng)計(jì)過(guò)婉称,我覺(jué)得是slf4j
占主導(dǎo)地位
四、前上面說(shuō)了那么多构蹬,現(xiàn)在說(shuō)一點(diǎn)注意事項(xiàng)和總結(jié)
??先說(shuō)注意事項(xiàng)??:
- 如果系統(tǒng)系統(tǒng)采用
slf4j
王暗,那么需要導(dǎo)入slf4j-api
和對(duì)應(yīng)的綁定器。需要說(shuō)的是這個(gè)綁定器庄敛,比如導(dǎo)入了slf4j-log4j12
的依賴(lài)俗壹,slf4j-log4j12
會(huì)自動(dòng)導(dǎo)入log4j
的依賴(lài)。所以需要排除掉應(yīng)用自己導(dǎo)入的log4j依賴(lài)
藻烤。 - 如果使用需要將
jul
或者jcl
橋接到log4j2
绷雏,除了需要導(dǎo)入log4j-api
和lo4j-core
,還需要導(dǎo)入log4j-jul
或log-jcl
橋接包怖亭;而如果想將log4j
橋接到log4j2
患亿,則只需要導(dǎo)入lo4j-core
和log4j-1.2-api
即可。 - 如果要將
jul
橋接到log4j2
考润,需要設(shè)置系統(tǒng)屬性刽沾,屬性名為java.util.logging.manager
,屬性值為org.apache.logging.log4j.jul.LogManager
峭跳”焐簦可以通過(guò)兩種方式設(shè)置,一是添加vm參數(shù)-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
蛀醉,二是在調(diào)用Logger
或LoggerManager
之前執(zhí)行System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager")
悬襟。
import java.util.logging.Logger; public class Jul { public static void main(String[] args) { System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager")。 Logger logger = Logger.getLogger("jul"); logger.info("jul"); } }
- 如果要將
jul
橋接到slf4j
拯刁,也需要做些特殊處理將jul的日志路由到slf4j
介紹了兩種方法:
第一種是編程的方式脊岳,如下代碼所示import org.slf4j.bridge.SLF4JBridgeHandler; import java.util.logging.Logger; public class Jul { public static void main(String[] args) { // 移除已經(jīng)存在的handler SLF4JBridgeHandler.removeHandlersForRootLogger(); // 安裝SLF4JBridgeHandler SLF4JBridgeHandler.install(); Logger logger = Logger.getLogger("jul"); logger.info("jul"); } }
*第二種方式是修改JAVA_HOME\jre\lib\logging.properties
循環(huán)依賴(lài)
比如應(yīng)用中加上了jul-to-slf4j和slf4j-jdk14的依賴(lài),jul會(huì)重定向到slf4j垛玻,slf4j又綁定的jul割捅,所以這樣就產(chǎn)生了循環(huán)依賴(lài)。
下面列出一些不能會(huì)禪心循環(huán)依賴(lài)的jar依賴(lài)
slf4j中
jul-to-slf4j 和 jul-to-slf4j
log4j-over-slf4j 和 slf4j-log4j12
jcl-over-slf4j 和 slf4j-jcl
log4j2中
log4j-to-slf4j 和 log4j-slf4j-impl還有許多細(xì)節(jié)帚桩,比如性能呀什么的亿驾,文中沒(méi)有提到,鄉(xiāng)親們感興趣的話可以去看官網(wǎng)哦:slf4j官網(wǎng)账嚎、log4j2官網(wǎng)
??總結(jié)??:
- 對(duì)于庫(kù)的創(chuàng)造者(上帝??)莫瞬,不寫(xiě)接口造成的后果就是------>看到上面畫(huà)的日志的各種橋接的圖你心碎嗎儡蔓,我反正碎了??。如果一開(kāi)始就定義了接口規(guī)范疼邀,我想我們的日志框架不會(huì)那么混亂喂江。
- 對(duì)于庫(kù)的使用者,一定要面向抽象編程旁振,這樣的應(yīng)用才會(huì)健壯获询,易于擴(kuò)展。
- 沒(méi)有什么問(wèn)題是加一個(gè)適配器層(比如各種橋接器拐袜、綁定器筐付,他們都是適配器)解決不了的,解決不了阻肿,那就兩個(gè)瓦戚。