1. 程式人生 > >Java日誌框架研究及常見配置

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 LoggingSLF4J,在配置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);

日誌框架就寫這些吧,希望能對大家有所幫助,對單一常用日誌框架的詳細解析,有機會在寫吧