java log4j logback jcl_Java 日志二三事
前言
Java 擁有功能和性能都非常強(qiáng)大的日志庫(kù),但另一方面,Java 日志庫(kù)依賴看起來(lái)豐富的讓人眼花繚亂。相信大家或多或少都有這樣的疑問(wèn),Log4j,SLF4J,Logback,Log4j2 這些日志框架我該如何選擇?它們彼此間又有什么關(guān)系?本篇文章將介紹這些日志庫(kù)的歷史演進(jìn)和之間的關(guān)系,方便你選擇最適合的日志庫(kù)。文章最后還有日志庫(kù)使用的最佳實(shí)踐。
歷史
Log4j (Log For Java) 可以當(dāng)之無(wú)愧地說(shuō)是 Java 日志框架的元老,1999 年發(fā)布首個(gè)版本,2012 年發(fā)布最后一個(gè)版本,2015 年正式宣布終止,至今還有無(wú)數(shù)的系統(tǒng)在使用 Log4j,甚至很多新系統(tǒng)的日志框架選型仍在選擇 Log4j。
然而老的不等于好的,在 IT 技術(shù)層面更是如此。盡管 Log4j 有著出色的歷史戰(zhàn)績(jī),但早已不是 Java 日志框架的最優(yōu)選擇。
在 Log4j 被 Apache Foundation 收入門(mén)下之后,由于理念不合, Log4j 的作者 Ceki Gülcü 離開(kāi)并開(kāi)發(fā)了 SLF4J 和 Logback。
SLF4J (Simple Log Facade For Java) 因其優(yōu)秀的性能和理念很快受到了廣泛歡迎,2016 年的統(tǒng)計(jì)顯示,GitHub 上的熱門(mén) Java 項(xiàng)目中,SLF4J 是使用率第二名的類庫(kù)(第一名是 Junit)。
Logback 則吸取了 Log4j 的經(jīng)驗(yàn),實(shí)現(xiàn)了很多強(qiáng)大的新功能,再加上它和 SLF4J 能夠無(wú)縫集成,也受到了歡迎。
在這期間,Apache Logging 則一直在關(guān)門(mén)憋大招,Log4j2 在 beta 版鼓搗了幾年,終于在 2014 年發(fā)布了 GA 版,不僅吸收了 Logback 的先進(jìn)功能,更通過(guò)優(yōu)秀的鎖機(jī)制、LMAX Disruptor、"無(wú)垃圾"機(jī)制等先進(jìn)特性,在性能上全面超越了 Log4j 和 Logback。
Log4j 1.x
Log4j (Log For Java) 是在 Logback 出現(xiàn)之前被廣泛使用的日志庫(kù),由 Gülcü 于 2001 年發(fā)布,后來(lái)成為 Apache 基金會(huì)的頂級(jí)項(xiàng)目。Log4j 在設(shè)計(jì)上非常優(yōu)秀,對(duì)后續(xù)的 Java Log 框架有長(zhǎng)久而深遠(yuǎn)的影響,也產(chǎn)生了 Log4c、Log4s、Log4perl 等到其他語(yǔ)言的移植。Log4j 的短板在于性能,在Logback 和 Log4j2 出來(lái)之后,Log4j 的使用也減少了。
Commons Logging
Commons Logging,簡(jiǎn)稱 JCL,是 Apache 下屬項(xiàng)目。JCL 是一個(gè) Log Facade,只提供 Log API,不提供實(shí)現(xiàn),然后有 Adapter 來(lái)使用 Log4j 或者 JDK 中自帶的 JUL(Java Util Logging)作為 Log Implementation。
不同的項(xiàng)目可能各自使用了不同的日志庫(kù),如果你的項(xiàng)目依賴的其他項(xiàng)目各自使用了不同的日志庫(kù),你想控制日志行為,就需要針對(duì)每個(gè)日志庫(kù)都寫(xiě)一個(gè)配置文件,那豈不是很麻煩?所以這個(gè)時(shí)候 JCL 就出現(xiàn)了。
在程序中日志創(chuàng)建和記錄都是用 JCL 中的接口,而真正運(yùn)行時(shí)會(huì)搜索當(dāng)前 ClassPath 中有什么實(shí)現(xiàn),如果有 Log4j 就是用 Log4j,如果啥都沒(méi)有則使用 JDK 的 JUL。這樣,在你的項(xiàng)目中,還有第三方的項(xiàng)目中,大家記錄日志都使用 JCL 的接口,然后最終運(yùn)行程序時(shí),可以按照自己的需求(或者喜好)來(lái)選擇使用合適的 Log Implementation。比如你想使用 Log4j,就添加 Log4j 的依賴并編寫(xiě)一個(gè) Log4j 的配置文件(通常命名為 log4j.properties)。
SLF4J/Logback
SLF4J (Simple Logging Facade for Java) 和 Logback 也是 Gülcü 創(chuàng)立的項(xiàng)目,其創(chuàng)立主要是為了提供更高性能的實(shí)現(xiàn)。其中,SLF4j 是類似于 JCL 的 Log Facade,Logback 是類似于 Log4j 的 Log Implementation。
SLF4J 出現(xiàn)的緣由是 Gülcü 認(rèn)為 JCL 的 API 設(shè)計(jì)得不好,容易讓使用者寫(xiě)出性能有問(wèn)題的代碼。比如在用 JCL 輸出一個(gè) debug 級(jí)別的 log:
logger.debug("start process request, url: " + url);這個(gè)有什么問(wèn)題呢?一般生產(chǎn)環(huán)境 log 級(jí)別都會(huì)設(shè)到 info 或者以上,那這條 log 是不會(huì)被輸出的。然而不管會(huì)不會(huì)輸出,這其中都會(huì)做一個(gè)字符串連接操作,然后生產(chǎn)一個(gè)新的字符串。如果這條語(yǔ)句在循環(huán)或者被調(diào)用很多次的函數(shù)中,就會(huì)多做很多無(wú)用的字符串連接,影響性能。所以 JCL 的最佳實(shí)踐推薦這么寫(xiě):
if (logger.isDebugEnabled()) {logger.debug("start process request, url: " + url); }顯然作為 API 來(lái)說(shuō)這太為繁瑣,所以 SLF4J 提供了新的 API,方便開(kāi)發(fā)者使用:
logger.debug("start process request, url: {}", url);這樣的話,在不輸出 log 的時(shí)候避免了字符串拼接的開(kāi)銷;在輸出的時(shí)候需要做一個(gè)字符串 format,代價(jià)比手工拼接字符串大一些,但是可以接受。
而 Logback 則是作為 Log4j 的繼承者來(lái)開(kāi)發(fā)的,提供了性能更好的實(shí)現(xiàn),異步 logger,Filter等更多的特性。
Log4j2
現(xiàn)在有了更好的 SLF4J 和 Logback 正慢慢取代 JCL 和 Log4j,然而維護(hù) Log4j 的人不想坐視用戶一點(diǎn)點(diǎn)被 SLF4J /Logback 蠶食,所以 Log4j2 誕生了。Log4j2 和 Log4j1.x 并不兼容,設(shè)計(jì)上很大程度上模仿了 SLF4J/Logback,性能上也獲得了很大的提升。Log4j2 也做了 Facade/Implementation 分離的設(shè)計(jì),分成了 log4j-api 和 log4j-core。
Facade & Implementation
JCL、SLF4J 和 Log4j2 日志框架都使用了 GoF 設(shè)計(jì)模式中的門(mén)面模式(Facade Pattern),將接口和實(shí)現(xiàn)分離,定義統(tǒng)一的接口,而實(shí)現(xiàn)可以由用戶自由選擇?,F(xiàn)在我們有了三個(gè)流行的 Log Facade,以及多個(gè) Log Implementation,那么該如何配合使用呢?
SLF4J
Gülcü 是個(gè)追求完美的人,他決定讓 SLF4J 和這些 Log 之間都能夠方便的互相替換,所以做了各種 Adapter 和 Bridge 來(lái)連接:
有趣的是,唯獨(dú)沒(méi)有 slf4j-over-log4j2 的橋接庫(kù),而且 log4j-to-slf4j 和 log4j-slf4j-impl 也是由 Apache 自己開(kāi)發(fā)的。
slf4j-api 只是 Log Facade 的依賴,添加了該依賴意味著在編碼時(shí)你能夠使用 Logger log = LoggerFactory.getLogger(Main.class); 和 log.info("hello, {}", "world"); 這種方式。除此之外,還需要添加 Log Implementation 的依賴。
下圖是 SLF4J官網(wǎng) 介紹可以綁定的日志實(shí)現(xiàn)框架。其中 slf4j-simple 是為小項(xiàng)目提供的簡(jiǎn)單實(shí)現(xiàn),logback-classic 是官方的原生實(shí)現(xiàn),不需要額外的適配器。而 slf4j-log4j12 和 slf4j-jdk14 分別是適配到 Log4j 和 JUL 的依賴,JUL 由于是 JDK 自帶所以不需要額外依賴,而 Log4j 還需要自己的底層實(shí)現(xiàn)依賴。
下面這張圖展示了 SLF4J 綁定不同日志實(shí)現(xiàn)框架需要的依賴:
Log4j2
關(guān)于 Log Facade 選擇 SLF4J 還是 Log4j2,個(gè)人覺(jué)得要看項(xiàng)目需求。總的來(lái)說(shuō) SLF4J 的兼容性更好,日志實(shí)現(xiàn)可以隨意搭配使用;雖然 Log4j2 可以通過(guò) log4j-to-slf4j 橋接到 SLF4J 再使用其他的 Log Implementation,但這必然帶來(lái)多余的性能消耗。
而 Log4j2 的優(yōu)點(diǎn)則在于性能,在 Is it worth to use slf4j with log4j2 這個(gè)問(wèn)題中推薦直接面向 Log4j2 API 編程,理由如下:
- Message API
- Lambdas for lazy logging
- Log any Object instead of just Strings
- Garbage-free: avoid creating varargs or creating Strings where possible
- CloseableThreadContext automatically removes items from the MDC when you’re finished with them
Logback 和 Log4j2 都宣稱自己是 Log4j 的后代,一個(gè)是出自同一作者,另一個(gè)則是在名字上根正苗紅。撇開(kāi)血統(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)該說(shuō)是 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 訪問(wèn)功能
- Log4j2 實(shí)現(xiàn)了“無(wú)垃圾”和“低垃圾”模式。簡(jiǎn)單地說(shuō),Log4j2 在記錄日志時(shí),能夠重用對(duì)象(如String等),盡可能避免實(shí)例化新的臨時(shí)對(duì)象,減少因日志記錄產(chǎn)生的垃圾對(duì)象,減少垃圾回收帶來(lái)的性能下降
這是 Apache 官方提供的同步和異步寫(xiě)日志時(shí)的性能對(duì)比圖:
所以綜上所訴,個(gè)人的看法是:如果對(duì)性能有要求,且 Log Implementation 想選用 Log4j2 的話,推薦 Log Facade 直接使用 Log4j2 API。
最佳實(shí)踐
1. 總是使用 Log Facade,而不是具體 Log Implementation
正如之前所說(shuō)的,使用 Log Facade 可以方便的切換具體的日志實(shí)現(xiàn)。而且,如果依賴多個(gè)項(xiàng)目,使用了不同的 Log Facade,還可以方便的通過(guò) Adapter 轉(zhuǎn)接到同一個(gè)實(shí)現(xiàn)上。如果依賴項(xiàng)目使用了多個(gè)不同的日志實(shí)現(xiàn),就麻煩的多了。
具體來(lái)說(shuō),現(xiàn)在推薦使用 Log4j-API 或者 SLF4j,不推薦繼續(xù)使用 JCL。
2. 只添加一個(gè) Log Implementation 依賴
毫無(wú)疑問(wèn),項(xiàng)目中應(yīng)該只使用一個(gè)具體的 Log Implementation,建議使用 Logback 或者 Log4j2。如果有依賴的項(xiàng)目中,使用的 Log Facade 不支持直接使用當(dāng)前的 Log Implementation,就添加合適的橋接器依賴。
3. 總是為 Log Implementation 依賴設(shè)置 optional 和 runtime scope
在項(xiàng)目中,Log Implementation 的依賴強(qiáng)烈建議設(shè)置為 runtime scope,并且設(shè)置為 optional。例如項(xiàng)目中使用了 SLF4J 作為 Log Facade,然后想使用 Logback 作為 Implementation,那么使用 POM 文件應(yīng)該這么寫(xiě):
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>${slf4j.version}</version> </dependency> <dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>${logback.version}</version><optional>true</optional><scope>runtime</scope> </dependency>設(shè)為 optional,依賴不會(huì)傳遞,這樣如果你的項(xiàng)目被別的項(xiàng)目依賴,它就不會(huì)引入不想要的 Log Implementation 依賴,即使用你提供的庫(kù)的用戶可以自定義 Log Implementation;
Scope 設(shè)置為 runtime,是為了防止開(kāi)發(fā)人員在項(xiàng)目中直接使用 Log Implementation 中的類,而不適用 Log Facade 中的類,即編碼時(shí)程序員只可見(jiàn) Log Facade 層面而不必關(guān)注實(shí)現(xiàn)層面。
4. 如果有必要,排除依賴的第三方庫(kù)中的 Log Impementation 依賴
這是很常見(jiàn)的一個(gè)問(wèn)題,第三方庫(kù)的開(kāi)發(fā)者未必會(huì)把具體的日志實(shí)現(xiàn)或者橋接器的依賴設(shè)置為 optional,然后你的項(xiàng)目繼承了這些依賴。然而具體的日志實(shí)現(xiàn)未必是你想使用的,比如他依賴了 Log4j,你想使用 Logback,這樣程序在運(yùn)行時(shí)會(huì)檢測(cè)到有多個(gè)日志實(shí)現(xiàn)類,如下圖。另外,如果不同的第三方依賴使用了不同的橋接器和 Log 實(shí)現(xiàn),也容易形成環(huán)。
SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/Users/s1mple/.m2/repository/org/slf4j/slf4j-log4j12/1.7.5/slf4j-log4j12-1.7.5.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/Users/s1mple/.m2/repository/com/caacitc/slf4j-jdk14-1.6.1.jar/1.0.2/slf4j-jdk14-1.6.1.jar-1.0.2.jar!/org/slf4j/impl/StaticLoggerBinder.class]這種情況下,推薦的處理方法,是使用 exclude 來(lái)排除所有的這些 Log 實(shí)現(xiàn)和橋接器的依賴,只保留第三方庫(kù)里面對(duì) Log Facade 的依賴。
<dependency><groupId>com.alibaba.jstorm</groupId><artifactId>jstorm-core</artifactId><version>2.1.1</version><exclusions><exclusion><groupId>org.slf4j</groupId><artifactId>log4j-over-slf4j</artifactId></exclusion><exclusion><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId></exclusion></exclusions> </dependency>另外,在 IntelliJ IDEA 中,可以使用 Show Maven Dependencies 查看依賴關(guān)系圖,可以方便的搜索依賴并 exclude 掉。
5. 避免為不會(huì)輸出的 log 付出代價(jià)
Log 庫(kù)都可以靈活的設(shè)置輸出界別,所以每一條程序中的 log,都是有可能不會(huì)被輸出的。這時(shí)候要注意不要額外的付出代價(jià)。
先看兩個(gè)有問(wèn)題的寫(xiě)法:
logger.debug("start process request, url: " + url); logger.debug("receive request: {}", toJson(request));第一條是直接做了字符串拼接,所以即使日志級(jí)別高于 debug 也會(huì)做一個(gè)字符串連接操作;第二條雖然用了 SLF4J/Log4j2 中的懶求值方式來(lái)避免不必要的字符串拼接開(kāi)銷,但是 toJson() 這個(gè)函數(shù)卻是都會(huì)被調(diào)用并且開(kāi)銷更大。
推薦的寫(xiě)法如下:
logger.debug("start process request, url:{}", url); // SLF4J/LOG4J2 if (logger.isDebugEnabled()) { // SLF4J/LOG4J2logger.debug("receive request: " + toJson(request)); } logger.debug("receive request: {}", () -> toJson(request)); // LOG4J2 logger.debug(() -> "receive request: " + toJson(request)); // LOG4J26. 日志中盡量避免輸出行號(hào),函數(shù)名等字段
原因是,為了獲取語(yǔ)句所在的函數(shù)名,或者行號(hào),log 庫(kù)的實(shí)現(xiàn)都是獲取當(dāng)前的 stacktrace,然后分析取出這些信息,而獲取 stacktrace 的代價(jià)是很昂貴的。如果有很多的日志輸出,就會(huì)占用大量的 CPU。在沒(méi)有特殊需要的情況下,建議不要在日志中輸出這些這些字段。
正確做法是使用日志打印的類名和內(nèi)容定位到代碼位置。
public class Main {private static final Logger log = LoggerFactory.getLogger(Main.class);public static void main(String[] args) {log.info("hello world");} }// 16:08:14.913 [main] INFO com.github.s1mplecc.log.Main - hello world參考
- Java 日志框架解析(上) - 歷史演進(jìn)
- Java 日志框架解析(下) - 最佳實(shí)踐
- 面向log4j2 API編程而不是slf4j
- SLF4J 官方文檔
- Apache Log4j2 官方文檔
總結(jié)
以上是生活随笔為你收集整理的java log4j logback jcl_Java 日志二三事的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 餐厅的菜贵是否有依据可以举证?
- 下一篇: 鸡蛋被我吃了组一个句?