Java日志工具
基本介紹
在java的世界裡有許多實作日志功能的工具,最早得到廣泛使用的是 log4j,現在比較流行的是slf4j+logback。作為開發人員,我們有時候需要封裝一些元件(二方包)提供給其他人員使用,但是那麼多的日志工具,根本沒法保證每個元件裡都能使用約定好的日志工具,況且還有很多第三方的包,鬼知道他會用什麼日志工具。假如一個應用程式用到了兩個元件,恰好兩個元件使用不同的日志工具,那麼應用程式就會有兩份日志輸出了,蛋疼吧。。
下面簡單介紹下常見的日志工具:
-
JUL
JUL全稱java.util.logging.Logger,JDK自帶的日志系統,從JDK1.4就有了。因為log4j的存在,這個logger一直沉默着,其實在一些測試性的代碼中,jdk自帶的logger比log4j更友善。JUL是自帶具體實作的,與log4j、logback等類似,而不是像JCL、slf4j那樣的日志接口封裝。
import java.util.logging.Level; import java.util.logging.Logger; private static final Logger LOGGER = Logger.getLogger(MyClass.class.getName());
- 相同名字的Logger對象全局隻有一個;
- 一般使用圓點分隔的層次命名空間來命名 Logger;Logger 名稱可以是任意的字元串,但是它們一般應該基于被記錄元件的包名或類名,如 java.net 或 javax.swing;
- 配置檔案預設使用
,日志級别預設為INFO;jre/lib/logging.properties
- 可以通過系統屬性
指定路徑覆寫系統預設檔案;java.util.logging.config.file
- 日志級别由高到低依次為:
。另外還有兩個全局開關:OFF「關閉日志記錄」和ALL「啟用所有消息日志記錄」。SEVERE(嚴重)、WARNING(警告)、INFO(資訊)、CONFIG(配置)、FINE(詳細)、FINER(較詳細)、FINEST(非常詳細)
- 《logging.properties》檔案中,預設日志級别可以通過
來控制,也可以基于層次命名空間來控制,按照Logger名字進行字首比對,比對度最高的優先采用;日志級别隻認大寫;.level= ALL
- JUL通過handler來完成實際的日志輸出,可以通過配置檔案指定一個或者多個hanlder,多個handler之間使用逗号分隔;handler上也有一個日志級别,作為該handler可以接收的日志最低級别,低于該級别的日志,将不進行實際的輸出;handler上可以綁定日志格式化器,比如java.util.logging.ConsoleHandler就是使用的
來支援的;String.format
handlers= java.util.logging.ConsoleHandler .level= ALL com.suian.logger.jul.xxx.level = CONFIG com.suian.logger.jul.xxx.demo2.level = FINE com.suian.logger.jul.xxx.demo3.level = FINER java.util.logging.ConsoleHandler.level = ALL java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter java.util.logging.SimpleFormatter.format=%$tF %$tT [%$s] %$s - %$s %n
-
Apache Commons Logging
之前叫Jakarta Commons Logging,簡稱JCL,是Apache提供的一個通用日志API,可以讓應用程式不再依賴于具體的日志實作工具。
commons-logging包中對其它一些日志工具,包括Log4J、Avalon LogKit、JUL等,進行了簡單的包裝,可以讓應用程式在運作時,直接将JCL API打點的日志适配到對應的日志實作工具中。
common-logging通過動态查找的機制,在程式運作時自動找出真正使用的日志庫。這一點與slf4j不同,slf4j是在編譯時靜态綁定真正的Log實作庫。
commons-logging包裡的包裝類和簡單實作列舉如下:
- org.apache.commons.logging.impl.Jdk14Logger,适配JDK1.4裡的JUL;
- org.apache.commons.logging.impl.Log4JLogger,适配Log4J;
- org.apache.commons.logging.impl.LogKitLogger,适配avalon-Logkit;
- org.apache.commons.logging.impl.SimpleLog,common-logging自帶日志實作類,它實作了Log接口,把日志消息都輸出到系統錯誤流System.err中;
- org.apache.commons.logging.impl.NoOpLog,common-logging自帶日志實作類,它實作了Log接口,其輸出日志的方法中不進行任何操作;
如果隻引入Apache Commons Logging,也沒有通過配置檔案《commons-logging.properties》進行擴充卡綁定,也沒有通過系統屬性或者SPI重新定義LogFactory實作,預設使用的就是jdk自帶的java.util.logging.Logger來進行日志輸出。
JCL使用配置檔案
,可以在該檔案中指定具體使用哪個日志工具。不配置的話,預設會使用JUL來輸出日志。配置示例:commons-logging.properties
org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
-
Avalon LogKit
Avalon LogKit是一個高速日志記錄工具集,Avalon裡的各個元件Framework、Excalibur、Cornerstone和Phoenix都用到它。它的模型與JDK 1.4 Logging package采用相同的原理,但與JDK 1.2+相容。使用LogKit的原因是:Context和LogTargets。使用Log4j的時候,日志的内容隻能是一句話,而使用LogKit,你可以記錄很多項内容,甚至可以各項内容記錄到對應的資料庫字段中。如果使用Log4j存儲日志到不同的存儲媒體,如資料庫,需要使用Appender,而LogKit已經可以支援多種存儲目标。
-
log4j
Log4j是Apache的一個開放源代碼項目,通過使用Log4j,我們可以控制日志資訊輸送的目的地是控制台、檔案、資料庫等;我們也可以控制每一條日志的輸出格式;通過定義每一條日志資訊的級别,我們能夠更加細緻地控制日志的生成過程。 Log4j有7種不同的log級别,按照等級從低到高依次為:TRACE、DEBUG、INFO、WARN、ERROR、FATAL、OFF。如果配置為OFF級别,表示關閉log。 Log4j支援兩種格式的配置檔案:properties和xml。包含三個主要的元件:Logger、appender、Layout。
-
SLF4J
SLF4J全稱The Simple Logging Facade for Java,簡單日志門面,這個不是具體的日志解決方案,而是通過門面模式提供一些Java Logging API,類似于JCL。題外話,作者當時建立SLF4J的目的就是為了替代Jakarta Commons Logging(JCL)。 SLF4J提供的核心API是一些接口以及一個LoggerFactory的工廠類。在使用SLF4J的時候,不需要在代碼中或配置檔案中指定你打算使用哪個具體的日志系統,可以在部署的時候不修改任何配置即可接入一種日志實作方案,在編譯時靜态綁定真正的Log庫。
使用SLF4J時,如果你需要使用某一種日志實作,那麼你必須選擇正确的SLF4J的jar包的集合(各種橋接包)。SLF4J提供了統一的記錄日志的接口,隻要按照其提供的方法記錄即可,最終日志的格式、記錄級别、輸出方式等通過具體日志系統的配置來實作,是以可以在應用中靈活切換日志系統。
logback是slf4j-api的天然實作,不需要橋接包就可以使用。另外slf4j還封裝了很多其他的橋接包,可以使用到其他的日志實作中,比如slf4j-log4j12,就可以使用log4j進行底層日志輸出,再比如slf4j-jdk14,可以使用JUL進行日志輸出。
-
Logback
Logback,一個“可靠、通用、快速而又靈活的Java日志架構”。Logback目前分成三個子產品:logback-core,logback- classic和logback-access。logback-core是其它兩個子產品的基礎子產品。logback-classic是log4j的一個改良版本,完整實作了SLF4J API。logback-access子產品與Servlet容器內建提供通過Http來通路日志的功能。Logback依賴配置檔案logback.xml,當然也支援groovy方式。Logback相比log4j,有很多很多的優點,網上一搜一大片,此處就不再贅述了。
-
Log4j2
Log4j 2是log4j 1.x和logback的改進版,據說采用了一些新技術(無鎖異步等等),使得日志的吞吐量、性能比log4j 1.x提高10倍,并解決了一些死鎖的bug,而且配置更加簡單靈活。
Log4j2支援插件式結構,可以根據需要自行擴充Log4j2,實作自己的appender、logger、filter等。 在配置檔案中可以引用屬性,還可以直接替代或傳遞到元件,而且支援json格式的配置檔案。不像其他的日志架構,它在重新配置的時候不會丢失之前的日志檔案。 Log4j2利用Java5中的并發特性支援,盡可能地執行最低層次的加鎖。解決了在log4j 1.x中存留的死鎖的問題。 Log4j 2是基于LMAX Disruptor庫的。在多線程的場景下,和已有的日志架構相比,異步logger擁有10倍左右的效率提升。
Log4j2體系結構:
史上最全Java各類日志元件分析彙總Java日志工具
使用場景
-
隻使用java.util.logging.Logger
最簡單的場景,正式系統一般不會這麼用,自己寫點小demo、測試用例啥的是可以這麼用。不要任何第三方依賴,jdk原生支援。
-
隻使用Apache Commons Logging
需要引入commons-logging包,示例如下:
<dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency>
-
Apache Commons Logging和log4j結合使用
需要引入commons-logging包和log4j包,示例如下:
該模式下可以使用的打點api:<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>
- org.apache.commons.logging.Log,commons-logging裡的api;
- org.apache.log4j.Logger,log4j裡的api;
無論使用哪種api打點,最終日志都會通過log4j進行實際的日志記錄。推薦用commons-logging裡的api,如果直接用log4j裡的api,就跟單用log4j沒差別,就沒有再引入commons-logging包的必要了。
既然最終是通過log4j實作日志記錄,那麼日志輸出的level、target等也就是通過log4j的配置檔案進行控制了。下面是一個log4j配置檔案《log4j.properties》的簡單示例:
既然是推薦使用commons-logging裡的api打點,為了能找到log4j的日志實作,必須通過《commons-logging.properties》配置檔案顯式的确定關聯,示例如下:#log4j.rootLogger = error,console log4j.logger.com.suian.logtest = trace,console #輸出源console輸出到控制台 log4j.appender.console = org.apache.log4j.ConsoleAppender log4j.appender.console.Target = System.out log4j.appender.console.layout = org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %-p %c - [log4j]%m%n
代碼中使用JCL api進行日志打點,底層使用log4j進行日志輸出。日志輸出控制依托于log4j的配置檔案,另外需要在commons-logging.properties配置檔案中顯式指定與log4j的綁定關系。org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
-
單獨使用log4j
這個是早幾年最最流行的用法了,現在因為log4j本身的問題以及新的日志架構的湧現,已經逐漸退出曆史舞台了。具體怎麼用自己去百度吧。
-
SLF4J結合Logback
當下最流行的用法,SLF4J為使用場景最廣泛的日志門面,加上Logback的天然實作,簡單、統一、快速。
需要引入第三方依賴:
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>${logback.version}</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>${logback.version}</version> </dependency>
-
單獨使用Log4j2
Log4j2感覺就是SLF4J+Logback。log4j-api等價于SLF4J,log4j-core等價于Logback。
需要引入第三方依賴:
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.6.2</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.6.2</version> </dependency>
沖突處理
理論上各種日志輸出方式是可以共存的,比如log4j和log4j2以及logback等,但是麻煩的是我們得維護多個配置檔案,必須充分了解每個元件使用的是那種日志元件,然後進行對應的配置檔案配置。
如何解決呢?每一個想做通用日志解決方案的,都對相容性問題進行了特殊處理。目前隻有slf4j和log4j2提供了這樣的整合機制,其他的基本都很弱。
代碼中可能使用的日志打點Api列舉:
- java.util.logging.Logger,jdk自帶的;
- org.apache.commons.logging.Log,commons-logging包裡的api;
- org.apache.log4j.Logger,log4j包裡的api;
- org.apache.logging.log4j.Logger,log4j2提供的api,在log4j-api包裡;
- org.slf4j.Logger,slf4j提供的api,在slf4j-api包裡;
上述打點方式,在一個應用中是有可能共存的,即使自己寫的代碼可以確定都使用同一類api,但是引入的第三方依賴裡就可能各式各樣了。該怎麼處理呢?
前面已經提過了,現在能夠對各類沖突支援比較到位的就是slf4j和log4j2,他們都提供了很多的綁定器和橋接器。
- 所謂的綁定器,也可以稱之為擴充卡或者包裝類,就是将特定api打點的日志綁定到具體日志實作元件來輸出。比如JCL可以綁定到log4j輸出,也可以綁定到JUL輸出;再比如slf4j,可以通過logback輸出,也可以綁定到log4j、log4j2、JUL等;
- 所謂的橋接器就是一個假的日志實作工具,比如當你把 jcl-over-slf4j.jar 放到 CLASS_PATH 時,即使某個元件原本是通過 JCL 輸出日志的,現在卻會被 jcl-over-slf4j “騙到”SLF4J 裡,然後 SLF4J 又會根據綁定器把日志交給具體的日志實作工具。
slf4j整合日志輸出
-
java.util.logging.Logger
将JUL日志整合到slf4j統一輸出,需要引入slf4j提供的依賴包:
隻引入依賴并不能整合JUL日志,該包裡隻是提供了一個JUL的handler,仍舊需要通過JUL的配置檔案進行配置,slf4j綁定器(如logback)上設定的日志級别等價于JUL handler上的日志級别,是以控制JUL的日志輸出,日志級别仍舊分兩個地方控制:JUL配置檔案《logging.properties》和slf4j綁定器的配置檔案,比如《logback.xml》、《log4j2.xml》等。<dependency> <groupId>org.slf4j</groupId> <artifactId>jul-to-slf4j</artifactId> <version>1.7.22</version> </dependency>
- 建立jdk14-logger的配置檔案《logger.properties》,加入handler配置以及日志級别配置;
handlers= org.slf4j.bridge.SLF4JBridgeHandler .level= ALL
- 在啟動程式或容器的時候加入JVM參數配置
;當然也可以使用程式設計方式進行處理,可以在main方法或者擴充容器的listener來作為系統初始化完成;此種方式有些場景下不如配置JVM參數來的徹底,比如想代理tomcat的系統輸出日志,程式設計方式就搞不定了;-Djava.util.logging.config.file = /path/logger.properties
-
org.apache.commons.logging.Log
将JCL日志整合到slf4j統一輸出,需要引入slf4j提供的依賴包:
jcl-over-slf4j包裡所有類的根路徑為<dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.7.22</version> </dependency>
,也有Log和LogFactory類,相當于以重寫commons-logging包的代價來實作對JCL的橋接。Log與commons-logging包裡的一模一樣,LogFactory的實作,代碼寫死使用的是org.apache.commons.logging
。commons-logging包裡預設使用的是org.apache.commons.logging.impl.SLF4JLogFactory
org.apache.commons.logging.impl.LogFactoryImpl
。以這樣的代價來實作橋接,可以實作無縫對接,不像JUL那樣還得添加額外配置,但是有一個壞處就是需要處理類庫沖突了。commons-logging包和jcl-over-slf4j包肯定是不能共存的,需要将commons-logging包在classpath裡排掉。
題外話,因為JCL本身就支援通過配置檔案《commons-logging.properties》綁定擴充卡,是以個人感覺更傾向于封裝一個擴充卡的方式來支援,就像commons-logging包裡的
org.apache.commons.logging.impl.Log4JLogger
,這樣更符合程式員的思維,明明白白。
橋接包的命名也是很講究的,覆寫的這種,命名為xxx-over-slf4j,如本例的jcl-over-slf4j;純橋接的,命名為xxx-to-slf4j,如文章前面提到的jul-to-slf4j。
-
org.apache.log4j.Logger
将log4j日志整合到slf4j統一輸出,需要引入slf4j提供的依賴包:
看橋接包的名字就知道了,log4j-over-slf4j肯定是覆寫了<dependency> <groupId>org.slf4j</groupId> <artifactId>log4j-over-slf4j</artifactId> <version>1.7.22</version> </dependency>
包,是以使用起來隻需要引入依賴即可,不需要其他額外的配置。但是仍舊是要處理沖突的,log4j包和log4j-over-slf4j是不能共存的哦。log4j:log4j
-
org.apache.logging.log4j.Logger
将log4j2日志整合到slf4j統一輸出,slf4j沒有提供橋接包,但是log4j2提供了,原理是一樣的,首先引入log4j2的橋接包:
log4j2提供的依賴包有<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-to-slf4j</artifactId> <version>2.6.2</version> </dependency>
和org.apache.logging.log4j:log4j-api
org.apache.logging.log4j:log4j-core
,其作用看包名就清楚了。log4j-core是log4j-api的标準實作,同樣log4j-to-slf4j也是log4j-api的一個實作。
log4j-to-slf4j用于将log4j2輸出的日志橋接到slf4j進行實際的輸出,作用上來講,log4j-core和log4j-to-slf4j是不能共存的,因為會存在兩個log4j2的實作。
經測試,就測試結果分析,共存也是木有問題的,何解?log4j2加載provider的時候采用了優先級政策,即使找到多個也能決策出一個可用的provider來。在所有提供log4j2實作的依賴包中,都有一個
META-INF/log4j-provider.properties
配置檔案,裡面的FactoryPriority屬性就是用來配置provider優先級的,幸運的是log4j-to-slf4j(15)的優先級是高于log4j-core(10)的,是以測試結果符合預期,log4j2的日志橋接到了slf4j中進行輸出。
同樣,為確定系統的确定性,不會因為log4j2的provider決策政策變更導緻問題,建議還是要在classpath裡排掉log4j-core,log4j2也是推薦這麼做的。
log4j2整合日志輸出
-
java.util.logging.Logger
将JUL日志整合到log4j2統一輸出,需要引入log4j2提供的依賴包:
log4j2整合JUL日志的方式與slf4j不同,slf4j隻是定義了一個handler,仍舊依賴JUL的配置檔案;log4j2則直接繼承重寫了<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-jul</artifactId> <version>2.6.2</version> </dependency>
。使用時,隻需要通過系統屬性java.util.logging.LogManager
綁定重寫後的LogManager(java.util.logging.manager
)即可,感覺比slf4j的方式要簡單不少。org.apache.logging.log4j.jul.LogManager
-
org.apache.commons.logging.Log
将JCL日志整合到log4j2統一輸出,需要引入log4j2提供的依賴包:
基于log4j-jcl包整合JCL比較簡單,隻要把log4j-jcl包扔到classpath就可以了。看起來slf4j的整合方式優雅多了,底層原理是這樣的:JCL的LogFactory在初始化的時候,查找LogFactory的具體實作,是分了幾種場景的,簡單描述如下:<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-jcl</artifactId> <version>2.6.2</version> </dependency>
- 首先根據系統屬性
查找LogFactory實作類;org.apache.commons.logging.LogFactory
- 如果找不到,則以SPI方式查找實作類,
;log4j-jcl就是以這種方式支撐的;此種方式必須確定整個應用中,包括應用依賴的第三方jar包中,org.apache.commons.logging.LogFactory檔案隻有一個,如果存在多個的話,哪個先被加載則以哪個為準。萬一存在沖突的話,排查起來也挺麻煩的。META-INF/services/org.apache.commons.logging.LogFactory
- 還找不到,則讀取《commons-logging.properties》配置檔案,使用
屬性指定的LogFactory實作類;org.apache.commons.logging.LogFactory
- 最後再找不到,就使用預設的實作org.apache.commons.logging.impl.LogFactoryImpl。
- 首先根據系統屬性
-
org.apache.log4j.Logger
将log4j 1.x日志整合到log4j2統一輸出,需要引入log4j2提供的依賴包:
log4j2裡整合log4j 1.x日志,也是通過覆寫log4j 1.x api的方式來實作的,跟slf4j的實作原理一緻。是以也就存在類庫沖突的問題,使用log4j-1.2-api的話,必須把classpath下所有log4j 1.x的包清理掉。<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-1.2-api</artifactId> <version>2.6.2</version> </dependency>
-
org.slf4j.Logger
将slf4j日志整合到log4j2統一輸出,需要引入log4j2提供的依賴包:
log4j-slf4j-impl基于log4j2實作了slf4j的接口,其就是slf4j-api和log4j2-core之間的一個橋梁。這裡有個問題需要注意下,務必確定classpath下<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.6.2</version> </dependency>
和log4j-slf4j-impl
不要共存,否則會導緻事件無止盡地在SLF4J和Log4j2之間路由。log4j-to-slf4j
日志打點API綁定實作
slf4j-api和log4j-api都是接口,不提供具體實作,理論上基于這兩種api輸出的日志可以綁定到很多的日志實作上。slf4j和log4j2也确實提供了很多的綁定器。簡單列舉幾種可能的綁定鍊:
- slf4j → logback
- slf4j → slf4j-log4j12 → log4j
- slf4j → log4j-slf4j-impl → log4j2
- slf4j → slf4j-jdk14 → jul
- slf4j → slf4j-jcl → jcl
- jcl → jul
- jcl → log4j
- log4j2-api → log4j2-core
- log4j2-api → log4j-to-slf4j → slf4j
來個環圖: