Java日誌框架研究及常見配置
按照基本的定義,日誌即是對程式執行過程中關鍵事件的記錄;大體日誌分為執行日誌和開發日誌,執行日誌在業務層面記錄一些關鍵事件,為後面的跟蹤執行提供幫助,而開發日誌大多數時候是除錯日誌,根據事件流的輸出來除錯程式;因為開發人員本身的關注領域,執行日誌可能製作的比較少,難以達到跟蹤業務流的作用,而即使是開發日誌,因為開發的除錯有各種技巧,即使是跟蹤事件流,使用println也比日誌配置簡單多了,這是一個投資回報的問題,而人經常性的是短視的,除錯可能在這些人眼裡根本不需要認真對待,沒有前期的事件記錄規劃,隨著系統規模膨脹,執行流增多,執行事件保障,這時候沒有一種合理的“開關”機制來選擇檢視感興趣事件流,那結局是明晰的,日誌不止提供了一種樹形的開關結構,它還有靈活的輸出控制,在Java常用日誌框架Log4J還提供了JMX等介面,可以通過管理面板來監控和修改日誌記錄行為,這無疑是非常誘人的。
大體上Java體系中比較常用的日誌框架如下
日誌框架 |
支援日誌級別 |
Log4J |
FATAL ERROR WARN INFO DEBUG TRACE |
Java Logging API |
SEVERE WARNING INFO CONFIG FINE FINER FINEST |
Apache Commons Logging |
FATAL ERROR WARN INFO DEBUG TRACE |
SLF4J |
ERROR WARN INFO DEBUG TRACE |
Logback |
ERROR WARN INFO DEBUG TRACE |
Jboss logging |
FATAL ERROR WARN INFO DEBUG TRACE |
按照我比較有限的經驗,我大多時候使用的是Log4J1.x版本,在配置一些開源框架時,可能需要到Apache Commons Logging和SLF4J,在配置Jboss系工具和框架的時候需要用到Jboss logging(例如Hibernate),其他日誌框架如Java Logging,為Java自帶的,功能上比較簡單,用的也不是很多 ,而另一個Logback,這是一個號稱Log4J繼承者的新一代日誌框架,因為我對“新一代”這個名詞比較不感冒,我還沒有怎麼看,感興趣的可以看看。
基本上,日誌級別是很相似的,這不僅僅是Java生態範圍內,整個程式設計行業應該都有共識,通過日誌級別來標記事件的輕重緩急,我們可以通過這個條件進行過濾,顯示我們感興趣的內容,關於日誌級別之間的相互關係,這個比較簡單,這裡就不再說了。
優秀的程式設計設計原則中有一條“依賴倒置”,要依賴抽象,不要依賴具體,具體的實踐就是面向介面設計了,優秀的專案都會定義高層次抽象,通過封裝隱藏多餘的資訊,這樣在程式碼變動或者想不修改原有程式碼擴充套件的時候(開閉原則),能夠相對容易的滿足這些需求,而這些日誌框架設計結構中則充分發揮了面向介面設計的優良傳統,日誌框架的核心介面是Logger類(介面),其他部分則是圍繞此類(介面)展開,這裡我們看兩個定義例子,SLF4J的Logger定義如下
Apache Commons Logging的Log定義如下
因為其他的日誌框架實在都太“重量級”了,方法太多,這裡就不貼出定義了,日誌框架定義了基本的介面,通過提供一個Facade,而後端由誰去實現就不那麼重要了,這裡面Apache Commons Logging、SLF4J、Jboss logging都容許其他後端實現為別的日誌框架(大部分為Java Logging 、Log4J,差異化的部分在講這些日誌框架的時候再詳細敘述),如何將Facade的請求轉為後端可識別請求,知道一點設計模式的人應該馬上想到介面卡模式,這裡,我們想給出幾個日誌框架如何實現這個介面適配的解決方法,我們一一道來。
Apache Commons Logging
這是標準的介面卡模式,沒有什麼好講的,Apache Commons Logging提供的實際工作類如下
這裡面我們比較熟悉的是JDK自帶的Java Logging和Log4J,其他幾個因為不怎麼常用就不說了,關於Apache Commons Logging如何選擇具體的實現類,這個可以從LogFactory的實現類中查詢到邏輯,邏輯程式碼如下:
因為程式碼比較長,這裡只貼出關鍵部分邏輯,可以看得很清楚,使用者通過配置檔案指定後端實現,反之通過查詢類路徑來選擇具體實現類。
SLF4J
SLF4J也存在上述類似的適配方式,這裡我們給出SLF4J有些不同的bridge方式,其形式類似適配,如下
這個結構有些複雜,這裡是SLF4J到Log4J的橋接,我們看到Log4jLoggerFactory提供了類似的適配功能,這種方式的好處,按照官方文件的說法是提供了編譯時繫結,排除了執行時的探測方式,效能會好一些,看著這圖有些抽象,這裡給出一個具體的應用場景,如下
Spring日誌配置
Spring專案使用了Apache Commons Logging日誌框架,因為該日誌框架比較老舊,我們一般使用Log4J作為其後端實現,通過上面的介紹,我們知道只要將Log4J放入ClassPath中就可以了,如果使用maven,在依賴中新增Log4J就可以了,這裡如何使用SLF4J的橋接,結果有些複雜,首先需要去掉Apache Commons Logging依賴,然後新增Spring到SLF4J的Bridge,然後使用一個後端實現Log4J,因此在新增一個SLF4J到Log4J的Bridge,最後是Log4J實現,用maven管理的話,結果如下
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.2.2.BUILD-SNAPSHOT</version>
<exclusions>
<exclusion>
<!--排除commons-logging依賴-->
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<!--commons-logging到SLF4J之間的Bridge-->
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.5.8</version>
</dependency>
<dependency>
<!--SLF4J介面-->
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.5.8</version>
</dependency>
<dependency>
<!--SLF4J到Log4J之間的Bridge-->
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.5.8</version>
</dependency>
<dependency>
<!--Log4J實現-->
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>
</dependencies>
這種方式比較複雜,因為在平時工作中沒有用過SLF4J日誌框架,所以這種方式看起來有些多此一舉,但考慮到SLF4J作為事實上的Java Logging標準,如何更加有效的在不同前端Facade Logger介面到後端實現之間自由適配,SLF4J提供了一個很好的形式,如下
前端到後端的適配能夠更加方便,這確實是一個非常有趣的想法。
Jboss logging
事實上這個日誌框架貌似不是很出名,但是基本上Jboss系的產品、框架都會使用這個日誌框架,因此,我們也需要好好的學習一下,該日誌結構如下
這裡的也是一種介面卡方式,只是最終暴露的使用介面是特定的LoggerProvider,查詢特定日誌框架的LoggerProvider是由LoggerProviders提供的,程式碼如下
程式碼顯示在LoggerProviders載入時就開始查詢特定LoggerProvider,具體查詢邏輯如下
上面看到的是通過配置檔案查詢
後半部分是根據ClassPath查詢。
Jboss logging比較奇特的地方是其提供了一個JBossLogManagerLogger,這個Logger使用的內部Logger為JBoss容器託管日誌管理器,這個可以注意一下
上面分析了日誌框架前端Facade和後端實現如何適配,現在我們選取一個具體的框架來給出一些常見配置,希望能夠對大家有用,我們選取的就是Log4J,因為其他框架我使用的頻率比較低,二來因為日誌框架的概念都類似,熟悉了一個其他的應該也可以快速掌握,下面讓我們展開對Log4J的論述。
Log4J來自於Apache社群,是目前應用最廣的一個日誌框架,官方提供了兩個大的版本,為Log4J 1.x和Log4J 2.x,這兩個主版本連官網連結都不一樣,看來Log4J 2.x應該針對前一版本問題而重新設計,以至於導致和前一版本的不相容,但因為Log4J 1.x普及的根深蒂固,很多日誌框架沒有及時跟上對Log4J 2.x後端的支援,Log4J 2.x徹底替換掉Log4J 1.x還有很長的一段路要走。
這裡我們給出的是Log4J 1.x的版本介紹,關於Log4J 2.x,我們有機會再進行探索;在Log4J的體系裡,有三個最重要的概念:Logger、Appender、Layout,Logger即為日誌寫入入口,Appender定義了將日誌時間輸出的策略,Layout給出輸出的格式,三者的關係如下
因為Log4J中有Category的概念,這裡我把它看成一個Logger,三者的關係這樣看起來比較明顯,Logger接收日誌記錄,Appender接收Logger發來的日誌事件,然後根據Layout定義格式進行輸出,這裡沒有什麼好講的,我們開始講如何配置。
Log4J 1.x有兩種配置方式,一種是Java properties檔案,一種是XML,第一種用的比較多一些,這個和Log4J 2.x正好相反,配置檔案配置主要配置上面三個物件,配置Logger,然後配置使用哪種具體Appender,然後定義格式,看一個例子,如下:
log4j.rootLogger=DEBUG, A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-4r %-5p [%t] %37c %3x - %m%n
如上,一定要定義的Logger是root Logger,這裡構成了一個類似樹形結構,頂部節點定義預設行為,子節點可以對行為進行修改定製,這裡定義rootLogger使用DEBUG級別,使用A1這個Appender,注意這裡的寫法:第一個為日誌級別,後面可以跟很多個Appender;線面定義了這個具體的Appender A1,我們看到其使用的是ConsoleAppender,後面我們給這個Appender指定Layout,這裡的Layout為PatternLayout,這個比較常用,最後一行定義了日誌格式,這種格式寫法類似於printf那種格式化字串,裡面的佔位符構成為"%[格式修飾符]日誌含義符號"(中括號裡的為可選),格式修飾符比較簡單,“-”代表靠左對其,不加就是靠右對其,後面的數字代表了該日誌內容部分的最少顯示長度,如果有“.”,"."之後為該日誌內容最長顯示長度,超過長度會進行裁短;後面的日誌含義符號如下表
符號 |
意義 |
c(小寫) |
輸出日誌的類別 |
C(大寫) |
輸出觸發日誌請求的類的全稱 |
d |
輸出日誌事件的日期時間 |
F |
輸出日誌請求類檔名 |
l |
輸出請求日誌呼叫位置資訊 |
L |
輸出請求日誌程式碼行號 |
m |
輸出應用提供的日誌訊息 |
M |
輸出請求日誌方法名 |
n |
輸出平臺特定的行分隔符 |
p |
輸出日誌級別 |
r |
輸出從程式啟動到現在的毫秒數 |
t |
輸出生成這個日誌事件執行緒的名字 |
這裡在給出一個例子
log4j.rootLogger=DEBUG, A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%p [%t] %c{2} (%M:%L) - %m%n
log4j.logger.org.apache.log4j.examples=INFO, A2
log4j.appender.A2=org.apache.log4j.FileAppender
log4j.appender.A2.File=${user.home}/test
#如果test檔案存在就先清空它
log4j.appender.A2.Append=false
log4j.appender.A2.layout=org.apache.log4j.PatternLayout
log4j.appender.A2.layout.ConversionPattern=%5r %-5p [%t] %c{2} - %m%n
上面的例子稍微複雜一些,這裡設定了一個rootLogger,並制定了其日誌級別和Appender,後面定義了一個名為org.apache.log4j.examples 的Logger,這個Logger又定義了日誌級別和Appender,通過這裡的例子可以看出Log4J這種樹形的關係具有足夠的靈活性。
關於XML的配置,這裡給出一個例項,大家可以根據上面對properties的理解來配置XML,如下
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd">
<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
</layout>
</appender>
<category name="org.apache.log4j.xml">
<priority value="info" />
</category>
<Root>
<priority value ="debug" />
<appender-ref ref="STDOUT" />
</Root>
</log4j:configuration>
這裡的category如前面分析結構時所說,可以認為其就是一個Logger,這個結構也比較一目瞭然,沒有什麼要講的,大家可以根據自己的實際需要來調整結構。
這裡還有個問題是配置檔案命名的問題,預設情況下,如果配置檔案命名為log4j.xml或log4j.properties時Log4J會自動載入,如果因為某種原因不能這樣命名,那麼久需要編碼來實現配置的載入了,程式碼片段如下
String resource =
"/app/resources/example.properties";
URL configFileResource =
InitUsingPropertiesFile.class.getResource(resource);
PropertyConfigurator.configure(configFileResource);
日誌框架就寫這些吧,希望能對大家有所幫助,對單一常用日誌框架的詳細解析,有機會在寫吧