轉載自:http://www.javashuo.com/article/p-qgnyfndm-bp.htmlhtml
對於一個應用程序來講日誌記錄是必不可少的一部分。線上問題追蹤,基於日誌的業務邏輯統計分析等都離不日誌。java領域存在多種日誌框架,目前經常使用的日誌框架包括Log4j 1,Log4j 2,Commons Logging,Slf4j,Logback,Jul。java
看了上面的介紹是否會以爲比較混亂,這些日誌框架之間有什麼異同,都是由誰在維護,在項目中應該如何選擇日誌框架,應該如何使用? 下文會逐一介紹。git
1996年早期,歐洲安全電子市場項目組決定編寫它本身的程序跟蹤API(Tracing API)。通過不斷的完善,這個API終於成爲一個十分受歡迎的Java日誌軟件包,即Log4j。後來Log4j成爲Apache基金會項目中的一員。github
期間Log4j近乎成了Java社區的日誌標準。聽說Apache基金會還曾經建議Sun引入Log4j到java的標準庫中,但Sun拒絕了。spring
2002年Java1.4發佈,Sun推出了本身的日誌庫JUL(Java Util Logging),其實現基本模仿了Log4j的實現。在JUL出來之前,Log4j就已經成爲一項成熟的技術,使得Log4j在選擇上佔據了必定的優點。apache
接着,Apache推出了Jakarta Commons Logging,JCL只是定義了一套日誌接口(其內部也提供一個Simple Log的簡單實現),支持運行時動態加載日誌組件的實現,也就是說,在你應用代碼裏,只需調用Commons Logging的接口,底層實現能夠是Log4j,也能夠是Java Util Logging。設計模式
後來(2006年),Ceki Gülcü不適應Apache的工做方式,離開了Apache。而後前後建立了Slf4j(日誌門面接口,相似於Commons Logging)和Logback(Slf4j的實現)兩個項目,並回瑞典建立了QOS公司,QOS官網上是這樣描述Logback的:The Generic,Reliable Fast&Flexible Logging Framework(一個通用,可靠,快速且靈活的日誌框架)。api
現今,Java日誌領域被劃分爲兩大陣營:Commons Logging陣營和Slf4j陣營。
Commons Logging在Apache大樹的籠罩下,有很大的用戶基數。但有證據代表,形式正在發生變化。2013年末有人分析了GitHub上30000個項目,統計出了最流行的100個Libraries,能夠看出Slf4j的發展趨勢更好:安全
Apache眼看有被Logback反超的勢頭,於2012-07重寫了Log4j 1.x,成立了新的項目Log4j 2, Log4j 2具備Logback的全部特性。框架
Commons Logging是經過動態查找機制,在程序運行時,使用本身的ClassLoader尋找和載入本地具體的實現。詳細策略能夠查看commons-logging-*.jar包中的org.apache.commons.logging.impl.LogFactoryImpl.java文件。因爲Osgi不一樣的插件使用獨立的ClassLoader,Osgi的這種機制保證了插件互相獨立, 其機制限制了Commons Logging在Osgi中的正常使用。
Slf4j在編譯期間,靜態綁定本地的Log庫,所以能夠在Osgi中正常使用。它是經過查找類路徑下org.slf4j.impl.StaticLoggerBinder,而後在StaticLoggerBinder中進行綁定。
若是是在一個新的項目中建議使用Slf4j與Logback組合,這樣有以下的幾個優勢。
# 在使Commons Logging時爲了減小構建日誌信息的開銷,一般的作法是 if(log.isDebugEnabled()){ log.debug("User name: " + user.getName() + " buy goods id :" + good.getId()); } # 在Slf4j陣營,你只需這麼作: log.debug("User name:{} ,buy goods id :{}", user.getName(),good.getId()); # 也就是說,Slf4j把構建日誌的開銷放在了它確認須要顯示這條日誌以後,減小內存和Cup的開銷,使用佔位符號,代碼也更爲簡潔
jar包名 | 說明 |
---|---|
slf4j-log4j12-1.7.13.jar | Log4j1.2版本的橋接器,你須要將Log4j.jar加入Classpath。 |
slf4j-jdk14-1.7.13.jar | java.util.logging的橋接器,Jdk原生日誌框架。 |
slf4j-nop-1.7.13.jar | NOP橋接器,默默丟棄一切日誌。 |
slf4j-simple-1.7.13.jar | 一個簡單實現的橋接器,該實現輸出全部事件到System.err. 只有Info以及高於該級別的消息被打印,在小型應用中它也許是有用的。 |
slf4j-jcl-1.7.13.jar | Jakarta Commons Logging 的橋接器. 這個橋接器將Slf4j全部日誌委派給Jcl。 |
logback-classic-1.0.13.jar(requires logback-core-1.0.13.jar) | Slf4j的原生實現,Logback直接實現了Slf4j的接口,所以使用Slf4j與Logback的結合使用也意味更小的內存與計算開銷 |
類與接口 | 用途 |
---|---|
org.slf4j.LoggerFactory(class) | 給調用方提供的建立Logger的工廠類,在編譯時綁定具體的日誌實現組件 |
org.slf4j.Logger(interface) | 給調用方提供的日誌記錄抽象方法,例如debug(String msg),info(String msg)等方法 |
org.slf4j.ILoggerFactory(interface) | 獲取的Logger的工廠接口,具體的日誌組件實現此接口 |
org.slf4j.helpers.NOPLogger(class) | 對org.slf4j.Logger接口的一個沒有任何操做的實現,也是Slf4j的默認日誌實現 |
org.slf4j.impl.StaticLoggerBinder(class) | 與具體的日誌實現組件實現的橋接類,具體的日誌實現組件須要定義org.slf4j.impl包,並在org.slf4j.impl包下提供此類,注意在slf4j-api-version.jar中不存在org.slf4j.impl.StaticLoggerBinder,在源碼包slf4j-api-version-source.jar中才存在此類 |
Slf4j調用過程源碼分析,只加入slf4j-api-version.jar,不加入任何實現包
示例代碼參見:https://github.com/chlsmile/slf4j-demo
<dependencies> <!--只有slf4j-api依賴--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.13</version> </dependency> </dependencies>
1)調用LoggerFactory的getLogger()方法建立Logger
2)調用LoggerFactory的getILoggerFactory方法來建立ILoggerFactory
3)調用LoggerFactory的performInitialization方法來進行初始化
4)調用LoggerFactory的bind()方法
5)調用LoggerFactory的findPossibleStaticLoggerBinderPathSet()方法獲取StaticLoggerBinderPath集合
6)調用LoggerFactory的reportMultipleBindingAmbiguity()方法,記錄綁定的StaticLoggerBinder信息
7)LoggerFactory的reportMultipleBindingAmbiguity()方法
8)LoggerFactory的bind()方法找不到StaticLoggerBinder,拋出NoClassDefFoundError異常
9)LoggerFactory的bind()方法捕獲NoClassDefFoundError異常,匹配到StaticLoggerBinder關鍵詞記錄信息到控制檯
10)LoggerFactory的performInitialization()方法內部調用bind()方法結束
11)LoggerFactory的getLogger()方法內部getILoggerFactory()方法調用完成,建立出NOPLoggerFactory,而後由NOPLoggerFactory調用內部的getLogger()方法,建立出NOPLogger
12)App類內部的logger實際爲NOPLogger,調用logger.info()方法實際調用的是NOPLogger的info方法
加入slf4j-api-version.jar,與Logback組件
Slf4j做爲門面採用Logback做爲實現或者採用其它上面提到過的組件做爲實現相似,這裏只分析採用Logback組件做爲實現
示例代碼參見:https://github.com/chlsmile/slf4j-logback-demo
<dependencies> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.13</version> </dependency> <!--logback-classic依賴logback-core,會自動級聯引入--> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> </dependencies>
程序入口類同上
加入slf4j-api-version.jar,同時加入多種日誌實現組件
在項目中若是用slf4j-api做爲日誌門面,有多個日誌實現組件同時存在,例如同時存在Logback,slf4j-log4j12,slf4j-jdk14,slf4j-jcl四種實現,則在項目實際運行中,Slf4j的綁定選擇綁定方式將有Jvm肯定,而且是隨機的,這樣會和預期不符,實際使用過程當中須要避免這種狀況。
示例代碼參見:https://github.com/chlsmile/slf4j-logback-log4j-demo
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.25</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jdk14</artifactId> <version>1.7.25</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jcl</artifactId> <version>1.7.25</version> </dependency> </dependencies>
程序入口類同上
在實際環境中咱們常常會遇到不一樣的組件使用的日誌框架不一樣的狀況,例如Spring Framework使用的是日誌組件是Commons Logging,XSocket依賴的則是Java Util Logging。當咱們在同一項目中使用不一樣的組件時應該若是解決不一樣組件依賴的日誌組件不一致的狀況呢?如今咱們須要統一日誌方案,統一使用Slf4j,把他們的日誌輸出重定向到Slf4j,而後Slf4j又會根據綁定器把日誌交給具體的日誌實現工具。Slf4j帶有幾個橋接模塊,能夠重定向Log4j,JCL和java.util.logging中的Api到Slf4j。
jar包名 | 做用 |
---|---|
log4j-over-slf4j-version.jar | 將Log4j重定向到Slf4j |
jcl-over-slf4j-version.jar | 將Commons Logging裏的Simple Logger重定向到slf4j |
jul-to-slf4j-version.jar | 將Java Util Logging重定向到Slf4j |
多個日誌jar包造成死循環的條件 | 產生緣由 |
---|---|
log4j-over-slf4j.jar和slf4j-log4j12.jar同時存在 | 因爲slf4j-log4j12.jar的存在會將全部日誌調用委託給log4j。但因爲同時因爲log4j-over-slf4j.jar的存在,會將全部對log4j api的調用委託給相應等值的slf4j,因此log4j-over-slf4j.jar和slf4j-log4j12.jar同時存在會造成死循環 |
jul-to-slf4j.jar和slf4j-jdk14.jar同時存在 | 因爲slf4j-jdk14.jar的存在會將全部日誌調用委託給jdk的log。但因爲同時jul-to-slf4j.jar的存在,會將全部對jul api的調用委託給相應等值的slf4j,因此jul-to-slf4j.jar和slf4j-jdk14.jar同時存在會造成死循環 |
這裏以項目中集成log4j-over-slf4j與slf4j-log4j12爲例,其它組合造成死循環原理相相似。
示例代碼參見:https://github.com/chlsmile/slf4j-Infinite-loop-demo
程序入口類同上
基本步驟同上,調用鏈路LoggerFactory.getLogger()>LoggerFactory.getILoggerFactory()> LoggerFactory.performInitialization()>LoggerFactory.bind()
在實際使用過程當中,項目會根據須要引入一些第三方組件,例如經常使用的Spring,而Spring自己的日誌實現使用了Commons Logging,咱們又想使用Slf4j+Loback組合,這時候須要在項目中將Commons Logging排除掉,一般會用到如下3種方案,3種方案各有利弊,能夠根據項目的實際狀況選擇最適合本身項目的解決方案。
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> <version>${springframework.version}</version> </dependency>
這種方案優勢是exclusion是maven原生提供的,不足之處是若是有多個組件都依賴了commons-logging,則須要在不少處增長,使用起來不太方便
<dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.8.0-beta2</version> </dependency>
這種方案在調試代碼時仍是有可能致使IDE將commons-logging放置在classpath下,從而致使程序運行時出現異常
在maven私服中增長相似於99.0-does-not-exist這種虛擬的版本號
<dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>99.0-does-not-exist</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.8.0-beta2</version> </dependency>
這種方案好處是聲明方式比較簡單,用IDE調試代碼時也不會出現問題,不足之處是99.0-does-not-exist這種版本是maven中央倉庫中是不存在的,須要發佈到本身的maven私服中。
因爲歷史緣由JDK自身提供的Log組件出現的較晚,致使Jdk提供Log組件時第三方社區的日誌組件已經比較穩定成熟。通過多年的發展Slf4j+Logback與組合,Commons Logging與Log4j組合兩大陣營已經基本成爲了Java項目開發的標準,建議在新的項目開發中從這兩種方案中選擇適合本身項目的組合方案。