做爲Java程序員,幸運的是,Java 擁有功能和性能都很是強大的日誌庫;不幸的是,這樣的日誌庫有不止一個——相信每一個人都曾經迷失在JUL(Java Util Log), JCL(Commons Logging), Log4j, SLF4J, Logback,Log4j2 等等的迷宮中。在我見過的絕大多數項目中,都沒有可以良好的配置和使用日誌庫。程序員
這篇文章先講述Java常見日誌庫的歷史和關係,後續會講日誌使用的最佳實踐。讓咱們從頭(Java Util Log)開始提及吧。api
Java Util Log框架
簡稱JUL,是JDK 中自帶的log功能。雖然是官方自帶的log lib,JUL的使用確不普遍。主要緣由:異步
Log4j 1.x函數
Log4j 是在 Logback 出現以前被普遍使用的 Log Lib, 由 Gülcü 於2001年發佈,後來成爲Apache 基金會的頂級項目。Log4j 在設計上很是優秀,對後續的 Java Log 框架有長久而深遠的影響,也產生了Log4c, Log4s, Log4perl 等到其餘語言的移植。Log4j 的短板在於性能,在Logback 和 Log4j2 出來以後,Log4j的使用也減小了。性能
Commons Loggingurl
簡稱JCL,是Apache下面的項目。JCL 是一個Log Facade,只提供 Log API,不提供實現,而後有 Adapter 來使用 Log4j 或者 JUL 做爲Log Implementation。spa
就像以前所說,JDK如今帶了本身的JUL,而後又有第三方的 Log4j 等日誌庫存在,不一樣的項目可能各自使用了不一樣的日誌庫。若是你的項目依賴的其餘 lib 各自使用了不一樣的日誌庫,你想控制日誌行爲,就須要針對每一個日誌庫都寫一個配置文件,是否是很酸爽?debug
而後這個時候 JCL 就出現了。在程序中日誌建立和記錄都是用JCL中的接口,在真正運行時,會看當前ClassPath中有什麼實現,若是有Log4j 就是用 Log4j, 若是啥都沒有就是用 JDK 的 JUL。設計
這樣,在你的項目中,還有第三方的項目中,你們記錄日誌都使用 JCL 的接口,而後最終運行程序時,能夠按照本身的需求(或者喜愛)來選擇使用合適的Log Implementation。若是用Log4j, 就添加 Log4j 的jar包進去,而後寫一個 Log4j 的配置文件;若是喜歡用JUL,就只須要寫個 JUL 的配置文件。若是有其餘的新的日誌庫出現,也只須要它提供一個Adapter,運行的時候把這個日誌庫的 jar 包加進去。
到這個時候一切看起來都很簡單,很美好。接口和實現作了良好的分離,在統一的JCL之下,不改變任何代碼,就能夠經過配置就換用功能更強大,或者性能更好的日誌庫實現。
這種簡單美好一直持續到SLF4J出現。
SLF4J/Logback
SLF4J(The Simple Logging Facade for Java) 和 Logback 也是Gülcü 創立的項目,其創立主要是爲了提供更高性能的實現。其中,SLF4j 是相似於JCL 的Log Facade,Logback 是相似於Log4j 的 Log Implementation。
以前已經說過,Apache 有了個JCL,用來作各類Log lib統一的接口,若是 Gülcü 要搞一個更好的 Log 實現的話,直接寫一個實現就行了,爲啥還要搞一個和SLF4J呢?
緣由是Gülcü 認爲 JCL 的 API 設計得很差,容易讓使用者寫出性能有問題的代碼。
好比在用 JCL 輸出一個 debug 級別的 log:
logger.debug("start process request, url:" + url);
這個有什麼問題呢?通常生產環境 log 級別都會設到 info 或者以上,那這條 log 是不會被輸出的。然而無論會不會輸出,這其中都會作一個字符串鏈接操做,而後生產一個新的字符串。若是這條語句在循環或者被調用不少次的函數中,就會多作不少無用的字符串鏈接,影響性能。
因此 JCL 的最佳實踐推薦這麼寫:
if (logger.isDebugEnabled()) { logger.debug("start process request, url:" + url); }
然而開發者經常忽略這個問題或是以爲麻煩而不肯意這麼寫。因此SLF4J提供了新的API,方便開發者使用:
logger.debug("start process request, url:{}", url);
這樣的話,在不輸出 log 的時候避免了字符串拼接的開銷;在輸出的時候須要作一個字符串format,代價比手工拼接字符串大一些,可是能夠接受。
而 Logback 則是做爲 Log4j 的繼承者來開發的,提供了性能更好的實現,異步 logger,Filter等更多的特性。
如今事情變複雜了。咱們有了兩個流行的 Log Facade,以及三個流行的 Log Implementation。Gülcü 是個追求完美的人,他決定讓這些Log之間都可以方便的互相替換,因此作了各類 Adapter 和 Bridge 來鏈接:
能夠看到甚至 Log4j 和 JUL 均可以橋接到SLF4J,再經過 SLF4J 適配到到 Logback!
在這裏須要注意不能搞出循環的橋接,好比下面這些依賴就不能同時存在:
總感受事情在變得更麻煩呢!
Log4j2
如今有了更好的 SLF4J 和 Logback——你會想事情到這裏總該瞭解了吧,讓他們慢慢取代JCL 和 Log4j 好了。
然而維護 Log4j 的人不這樣想,他們不想坐視用戶一點點被 SLF4J /Logback 蠶食,繼而搞出了 Log4j2。
Log4j2 和 Log4j1.x 並不兼容,設計上很大程度上模仿了 SLF4J/Logback,性能上也得到了很大的提高。
Log4j2 也作了 Facade/Implementation 分離的設計,分紅了 log4j-api 和 log4j-core。
如今好了,咱們有了三個流行的Log 接口和四個流行的Log實現,若是畫出橋接關係的圖來回事什麼樣子呢?
是否是感受有點暈呢?一樣,在添加依賴的時候,要當心不要搞成循環依賴。
看到這裏可能要問了,咱們如今究竟應該怎麼配置和使用 Java 的日誌庫呢?請看下回Java日誌全解析(下) - 最佳實踐。