今天看到了一篇對Java日志系統講解很不錯的文章,所以做個學習記錄,如有侵權請聯系刪除
概述
???????Java的日志系統非常豐富,常用的有log4j、JUL、logback等等,隨著日志系統的發展出現了日志框架commons-logging、slf4j
發展史
???????日志最早出現的是apache開源社區的log4j,是應用最為廣泛的日志工具,然而sun公司在JDK1.4中增加了JUL日志實現企圖對抗log4j,同時斷斷續續也出現了其他的日志工具,這就造成了混亂,因為這些日志系統互相沒有關聯,替換和統一變得棘手,想象一下你的應用使用了log4j,然后使用了其他團隊的庫,而他們使用了JUL,那么你的應用就需要使用兩個日志系統了,然后又有第二個庫使用了simplelog,這個時候估計你就會崩潰了...那么如何解決呢?進行抽象,抽象出一個接口層對每個日志實現都適配或者轉接,提供給別人的庫都直接使用抽象層而具體的實現由使用者決定。不錯,開源社區提供了commons-logging抽象,被稱為JCL,JCL確實出色的完成了兼容主流的日志實現(log4j、JUL、simplelog),基本一統江湖,就連大名鼎鼎的spring也是依賴了JCL。然而好景不長,另一個優秀的日志框架slf4j的出現使場面更加混亂,而slf4j的作者(Ceki Gülcü)正是log4j的作者,他覺得JCL不夠優秀所以要搞一套更優雅的出來,于是slf4j誕生了,同時為slf4j實現了一個親兒子——logback。
???????slf4j確實更加優雅,但是之前已有很多代碼庫已經使用了JCL,雖然出現了slf4j與JCL之間的橋接轉換,但是集成的時候依然問題多多,到此本來應該完了,但是Ceki Gülcü覺得還是得回頭拯救下自己的“大阿哥”——log4j,于是log4j2誕生了,同樣log4j2也參與到了slf4j日志體系中,想必將來會更加混亂......
JCL
???????使用JCL一般需要配置一個commons-logging.properties在classpath上,這個文件有一行代碼:
org.apache.commons.logging.LogFactory= org.apache.commons.logging.impl.LogFactoryImpl
???????這個是告訴JCL我們要使用哪個日志實現,JCL會在classpath下去加載對應的日志工廠實現類,具體的日志工廠實現類可以是log4j,也可以是jul等等。用戶主需要依賴JCL的api即可,對日志系統的替換主需要修改一下commons-logging.properties文件切換到對應的日志工廠實現即可,但是我們也可以看到因為JCL是在運行時去加載classpath下的實現類,會有classloader的問題。
slf4j
???????slf4j的設計確實比較優雅,它采用了我們比較熟悉的方式——接口和實現分離,有個純粹的接口層slf4j-api工程,這個里面基本完全定義了日志的接口,所以對于開發者來說只需要是這個即可。
???????有接口就要有實現,比較推崇的實現是logback,logback完全實現了slf4j-api的接口,并且性能是那個也比log4j更好,我們知道log4j的使用比較普遍,所以為了支持這部分用戶是必須的,slf4j-log4j12也實現了slf4j-api,這個算是對log4j的適配器。同樣的道理,對JUL的是配置為slf4j-jdk14。
???????為了讓使用JCL等等其他其他日志系統的用戶可以很簡單的切換到slf4j上來,給出了各種橋接工程,例如:jcl-over-slf4j會把JCL的調用都橋接到slf4j上來(可以看出jcl-over-slf4j的api和JCL是相同的,所以這兩個jar是不能共存的),jul-to-slf4j是把jul的調用橋接到slf4j上,log4j-over-slf4j是把log4j的調用橋接到slf4j,下面用一張圖來表示下這個家族的大致成員(紅線表示沖突)
???????如上圖所示,最上層表示橋接層,中間是接口層,最下層表示具體的實現,可以看出這個圖中所有的jar都是圍繞著slf4j活動的,其中slf4j-jul的jar包名稱是slf4j-jdk14
???????slf4j-api和具體的實現層是怎么綁定的呢?這個其實是在編譯時綁定的,它可以不需要像使用JCL那樣需要配置一下,只需要把slf4j-api和slf4j-log4j放到classpath上,即實現綁定。原理可以下載slf4j-api的源碼查看,這個設計還是很巧妙的,slf4j-api中會去調用StaticLoggerBinder這個類獲取綁定的工廠類,而每個日志實現會在自己的jar中提供這樣一個類,這樣slf4j-api就實現了編譯時綁定實現。但是這樣接口的源碼編譯需要依賴具體的實現了,不太合理吧?這里容易讓人迷惑,因為打開slf4j-api的jar,看不到StaticLoggerBinder,當我們去查看slf4j-api的源碼,在源碼中看到了StaticLoggerBinder這個類,猜想應該是slf4j-api在打包過程中有動作,刪除了自己包中的那個類,結果不出所料,確實是pom中的ant-task給處理了,pom中處理方式如下:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>process-classes</phase>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
<configuration>
<tasks>
<echo>Removing slf4j-api's dummy StaticLoggerBinder and StaticMarkerBinder</echo>
<delete dir="target/classes/org/slf4j/impl"/>
</tasks>
</configuration>
</plugin>
???????打出來的slf4j-api的包是"不完整"的,只有找到包含StaticLoggerBinder這個類的包才可以,于是slf4j-log4j和logback-classic都提供了這個類。另外,slf4j-log4j和logback以及slf4j-jdk14是不能同時和slf4j共存的,也就是說只能有一個實現存在,不然啟動會提示有多個綁定。
???????同時這個圖中橋階層和對應的實現jar是不能共存的,比如log4j-over-slf4j和slf4j-log4j,jul-to-slf4j和slf4j-jdk14,這個很好理解,會有死循環,啟動也會報錯。也就是說jar之前有互斥性。
???????當然slf4j也提供了可以把對slf4j的調用橋接到JCL上的工程包——slf4j-jcl,可以看出slf4j的設計者考慮非常周到,想想這樣的情況:遺留系統使用的是JCL+log4j,因為系統功能演進,依賴了其他業務線的庫,恰好那個庫依賴了slf4j-api,并且應用需要關心這個庫的日志,那么就需要轉接日志到JCL上即可。細心的你可能一經發現,slf4j-jcl和jcl-over-slf4j也是互斥的,太多互斥的了.......
???????對于log4j2的加入,也很簡單,和logback是很相似的,如下圖:
紅線依然表示依賴的互斥,當然log4j-slf4j-impl也會和logback-classic、slf4j-log4j、slf4j-jdk14互斥。
常見的問題:
slf4j-api和實現版本不對應,尤其是1.6.x和1.5.x不兼容,如果沒有特殊需求,直接升級到最新版本。
slf4j的多個實現同時存在,比如slf4j-log4j和logback-classic,排除其中一個即可。
log4j和logback不能同時使用?可以同時使用,這兩個并不矛盾,遺留系統可能直接使用了log4j的代碼,并且不能通過log4j-over-slf4j橋接,那么可以讓他繼續使用log4j,這里(http://www.slf4j.org/legacy.html)有詳細的介紹。
該如何選用這些呢?建議在非特殊情況下,都使用slf4j-api+logback,不要直接使用日志實現,性能沒什么影響。對于要提供給別人的類庫,建議使用slf4j-api,使用方可以自由選擇具體的實現,并且建議類庫不要依賴具體的日志實現。對于自己的桌面小應用,可以直接使用log4j,畢竟只是隨便做做。
logback因為木有spring提供的啟動listener,所以要自己寫?可以看看這里(https://github.com/qos-ch/logback-extensions),開源社區已經做好了。
日志系統一般不會影響到系統性能,除非你的系統對性能非常苛刻,如果這樣你可以考慮使用Blitz4j(https://github.com/Netflix/blitz4j),這個是Netflix(http://netflix.github.io/)社區對log4j的性能改進版,不過他們依然建議去使用log4j或者logback。