Log4j2 + SLF4j打造日誌系統

yoylee_web發表於2018-11-23

一:前言

近期做一個專案打造專案的日誌系統時,發現沒有一個系統的學習,故準備系統學習一下日誌系統,後續會有關於日誌系統的其他介紹與總結,比如log4j2為什麼這麼快,其底層實現原理等。

java 界裡有許多實現日誌功能的工具,最早得到廣泛使用的是 log4j,許多應用程式的日誌部分都交給了 log4j,不過作為元件開發者,他們希望自己的元件不要緊緊依賴某一個工具,畢竟在同一個時候還有很多其他很多日誌工具,假如一個應用程式用到了兩個元件,恰好兩個元件使用不同的日誌工具,那麼應用程式就會有兩份日誌輸出了。

為了解決這個問題,JCL和SLF4j就出現了,JCL只提供 log 介面,具體的實現則在執行時動態尋找。這樣一來元件開發者只需要針對JCL或者slf4j的介面開發,而呼叫元件的應用程式則可以在執行時搭配自己喜好的日誌實踐工具。跟 JCL 一樣,SLF4J 也是隻提供 log 介面,具體的實現是在打包應用程式時所放入的繫結器(名字為 slf4j-XXX-version.jar)來決定,XXX 可以是 log4j12, jdk14, jcl, nop 等,他們實現了跟具體日誌工具(比如 log4j)的繫結及代理工作。舉個例子:如果一個程式希望用 log4j 日誌工具,那麼程式只需針對 slf4j-api 介面程式設計,然後在打包時再放入 slf4j-log4j12-version.jar 和 log4j.jar 就可以了。

專案中我們選擇了SLF4j+Log4j2來打造日誌系統,log4j2的效能還是比Logback好一些的,下面有對比。

如果轉載此博文,請附上本文連結:https://blog.csdn.net/CSDN___LYY/article/details/84394244 謝謝合作~

二:新增依賴

2.1:去除直接和間接依賴的log4j1和SLF4j

首先我們應該先刪除專案已經依賴的其他日誌元件,這裡指的是沒有用到的日誌元件,例如janusgraph會間接依賴log4j1的元件,這個元件刪除就會報錯,所以我們只要刪除沒有使用的日誌元件,這樣可以使專案更加乾淨~
方法:我們可以觀察專案目錄下的External Libraries下的依賴檔案,如果有log4j1或者其他日誌依賴,我們將他們在pom檔案中找到刪除即可。
在這裡插入圖片描述
如果依賴中有但是pom檔案中找不到,就是被間接依賴進來的了,我們在pom 檔案中右擊滑鼠,選中Diagrams->show dependences就可以看到整個專案的依賴圖,在其中找到對應的log依賴,選中右擊Exclude即可。
在這裡插入圖片描述

2.2:新增依賴

新增的所有依賴都是截止2018.11.22日最新的穩定版本

<!--slf4j-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.7.25</version>
    <scope>runtime</scope>
</dependency>
<!--log4j2-->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.11.1</version>
</dependency> 
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.11.1</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-web</artifactId>
    <version>2.11.1</version>
    <scope>runtime</scope>
</dependency>
<!--log4j2+slf4j-->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.11.1</version>
</dependency>

上述的jcl-over-scf4j的作用以及原因: 即使現在你仍會看到很多程式應用 JCL + log4j 這種搭配,不過當程式規模越來越龐大時,JCL的動態繫結並不是總能成功,具體原因大家可以 Google 一下,這裡就不再贅述了。解決方法之一就是在程式部署時靜態繫結指定的日誌工具,這也是 SLF4J 產生的原因。

現在還有一個問題,假如你正在開發應用程式所呼叫的元件當中已經使用了 JCL 的,還有一些組建可能直接呼叫了 java.util.logging,這時你需要一個橋接器(名字為 XXX-over-slf4j.jar)把他們的日誌輸出重定向到 SLF4J,所謂的橋接器就是一個假的日誌實現工具,比如當你把 jcl-over-slf4j.jar 放到 CLASS_PATH 時,即使某個元件原本是通過 JCL 輸出日誌的,現在卻會被 jcl-over-slf4j “騙到”SLF4J 裡,然後 SLF4J 又會根據繫結器把日誌交給具體的日誌實現工具,這樣就可以實現日誌的統一了。如果你的專案沒有使用jcl那麼就不必新增這個。

上述的log4j-web是在開發web專案的時候需要的,如果你不是web專案,可以酌情刪除

三:xml配置

3.1:log4j2.xml常用demo

在類路徑下新建檔案:log4j2.xml ,注意“2”不要缺少,位置放的正確並且檔名符合要求的話,專案會自動掃描到該配置檔案。

Log4j2能夠在初始化期間自動配置自身。當Log4j2啟動時,它將找到所有ConfigurationFactory外掛並按加權順序從最高到最低排列。在交付時,Log4j包含四個ConfigurationFactory實現:一個用於JSON,一個用於YAML,一個用於 properties,一個用於XML,下面為查詢載入順序:

  1. Log4j2將檢查“log4j.configurationFile”系統屬性,如果設定,將嘗試使用與副檔名匹配的ConfigurationFactory載入配置。
  2. 如果未設定系統屬性,則ConfigurationFactory屬性將在類路徑中查詢 log4j2-test.properties。
  3. 如果沒有找到這樣的檔案,YAML ConfigurationFactory將在類路徑中查詢 log4j2-test.yaml或log4j2-test.yml。
  4. 如果沒有找到這樣的檔案,JSON ConfigurationFactory將在類路徑中查詢 log4j2-test.json或log4j2-test.jsn。
  5. 如果找不到這樣的檔案,XML ConfigurationFactory將在類路徑中查詢 log4j2-test.xml。
  6. 如果找不到測試檔案,ConfigurationFactory屬性將在類路徑上查詢 log4j2.properties。
  7. 如果找不到屬性檔案,YAML ConfigurationFactory將在類路徑上查詢 log4j2.yaml或log4j2.yml。
  8. 如果找不到YAML檔案,JSON ConfigurationFactory將在類路徑上查詢 log4j2.json或log4j2.jsn。
  9. 如果找不到JSON檔案,XML ConfigurationFactory將嘗試在類路徑上找到 log4j2.xml。
  10. 如果找不到配置檔案,則將使用DefaultConfiguration。這將導致所有日誌記錄輸出轉到控制檯

log4j2.xml 檔案內容:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO" monitorInterval="30">
    <properties>
        <!--設定容器日誌在硬碟上輸出的目錄-->
        <property name="logPath">/opt/logs/hrmapp/</property>
        <!--設定專案日誌在硬碟上輸出的目錄-->
        <property name="logPathForProject">/opt/logs/hrmapp/project/</property>
    </properties>

    <Appenders>
        <!--=====容器日誌配置=====-->
        <!--設定在控制檯列印日誌-->
        <Console name="Console" target="SYSTEM_OUT">
            <!--設定輸出格式-->
            <PatternLayout pattern="[%-5p]:%d{yyyy-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n" />
        </Console>

        <!--設定級別為INFO日誌輸出到info.log中,filename為輸出日誌的目錄,filepattern為壓縮檔案的命名規範與目錄 -->
        <RollingFile name="INFO" filename="${logPath}/info.log"
                     filepattern="${logPath}/%d{yyyyMMdd}-info-%i.log.zip">
            <!--設定日誌級別-->
            <Filters>
                <ThresholdFilter level="INFO"/>
            </Filters>
            <!--輸出日誌的格式-->
            <PatternLayout pattern="[ %-5p]:%d{yyyy-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n" />
            <Policies>
                <!--設定每天打包日誌一次,**此處應注意一個問題,下面有描述**-->
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
            </Policies>
            <!--設定最多儲存20個日誌檔案,預設為7個-->
            <DefaultRolloverStrategy max="20" />
        </RollingFile>

        <!--設定級別為WARN日誌輸出到warn.log中-->
        <RollingFile name="WARN" filename="${logPath}/warn.log"
                     filepattern="${logPath}/%d{yyyyMMdd}-warn-%i.log.zip">
            <Filters>
                <!--設定只輸出級別為WARN的日誌-->
                <ThresholdFilter level="WARN"/>
                <ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/>
            </Filters>
            <PatternLayout pattern="[ %-5p]:%d{yyyy-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n" />
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
            </Policies>
            <DefaultRolloverStrategy max="20" />
        </RollingFile>

        <!--設定級別為ERROR日誌輸出到error.log中-->
        <RollingFile name="ERROR" filename="${logPath}/error.log"
                     filepattern="${logPath}/%d{yyyyMMdd}-error-%i.log.zip">
            <!--設定只輸出級別為ERROR的日誌-->
            <ThresholdFilter level="ERROR"/>
            <PatternLayout pattern="[ %-5p]:%d{yyyy-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n" />
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
            </Policies>
            <DefaultRolloverStrategy max="20" />
        </RollingFile>


        <!--=====專案日誌配置=====-->
        <Console name="ConsolePro" target="SYSTEM_OUT">
            <PatternLayout pattern="[%-5p]:%d{yyyy-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n" />
        </Console>

        <RollingFile name="INFOPro" filename="${logPathForProject}/info.log"
                     filepattern="${logPathForProject}/%d{yyyyMMdd}-info-%i.log.zip">
            <Filters>
                <ThresholdFilter level="INFO"/>
            </Filters>
            <PatternLayout pattern="[ %-5p]:%d{yyyy-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n" />
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
            </Policies>
            <DefaultRolloverStrategy max="20" />
        </RollingFile>

        <RollingFile name="WARNPro" filename="${logPathForProject}/warn.log"
                     filepattern="${logPathForProject}/%d{yyyyMMdd}-warn-%i.log.zip">
            <Filters>
                <ThresholdFilter level="WARN"/>
                <ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/>
            </Filters>
            <PatternLayout pattern="[ %-5p]:%d{yyyy-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n" />
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
            </Policies>
            <DefaultRolloverStrategy max="20" />
        </RollingFile>

        <RollingFile name="ERRORPro" filename="${logPathForProject}/error.log"
                     filepattern="${logPathForProject}/%d{yyyyMMdd}-error-%i.log.zip">
            <ThresholdFilter level="ERROR"/>
            <PatternLayout pattern="[ %-5p]:%d{yyyy-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n" />
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
            </Policies>
            <DefaultRolloverStrategy max="20" />
        </RollingFile>
    </Appenders>
    
    <Loggers>
        <!--
            新增專案日誌
            配置專案日誌只輸出com.bj58.renetwork下面的日誌
            additivity="false" 表示只將該日誌輸出到該配置的路徑下面,並不會再重複輸出到root也就是容器日誌中
        -->
        <logger name="com.bj58.renetwork" level="debug" additivity="false">
            <appender-ref ref = "ConsolePro"/>
            <appender-ref ref = "INFOPro"/>
            <appender-ref ref = "WARNPro" />
            <appender-ref ref = "ERRORPro" />
        </logger>
        <!--新增容器日誌-->
        <root level="INFO">
            <appender-ref ref = "Console"/>
            <appender-ref ref = "INFO" />
            <appender-ref ref = "WARN" />
            <appender-ref ref = "ERROR" />
        </root>
    </Loggers>
</Configuration>

註釋我寫的應該比較清楚了,如果你還是不太明白,下面我會詳細介紹一下。

<特別注意!!!>:

  • 其中的<TimeBasedTriggeringPolicy interval="1" modulate="true"/>語句這裡的“1”並不是特指一天,而是數量1,對於單位不管是“天”、“分”、“秒”,也就是1秒打包一次或者1分鐘列印一次,都是取決於<RollingFile name="WARNPro" filename="${logPathForProject}/warn.log" filepattern="${logPathForProject}/%d{yyyyMMdd}-%i-warn.log.zip">中filepattern最小單位。這裡我們的filepattern最小單位是天,所以是每天打包一次。
  • 其中的yyyyMMdd、HH:mm:ss類似的日期或者時間設定,其中的大小寫不要寫錯了,比如YYYYMMdd這樣在一些伺服器可能就不識別,導致系統就直接使用預設的列印格式了。

3.2:demo的優點

  • 將專案的日誌和容器的日誌分開列印到不同的資料夾中,這樣便於檢視與管理。比如,一個容器中部署了多個專案,如果不分開列印log的話所有的log都列印到容器的log中,所有專案和容器的log在一個檔案中管理和檢視的難度可以想象出來。如果每個專案一個對應的資料夾,所有的專案和容器都相互分開,將自己的日誌列印到自己對應的日誌檔案中,簡潔、方便檢視、便於管理
  • 將日誌的info、warn、error級別的日誌分開單獨列印,INFO包含info\warn\error所有的日誌,WARN使其只包含warn的日誌,ERROR使其只包含error的日誌,這樣在發現錯誤和異常更加便利
  • 將日誌檔案壓縮儲存,減少資源消耗
  • 控制日誌檔案數量,在保證日誌可追溯許可的範圍下刪除過早的日誌檔案,減少資源消耗
  • 以天為單位打包日誌,便於查詢日誌

3.3:內容詳解

1: 根節點Configuration有兩個屬性:status和monitorinterval

  • status用來指定log4j2本身的列印日誌的級別
  • monitorinterval用於指定log4j自動重新配置的監測間隔時間,單位是s,最小是5s

2:根節點下的子節點properties,用於定義變數和修改變數,這裡我只定義了兩個路徑變數,一個是容器log路徑,一個是專案log路徑
3:根節點下的子節點Appenders,主要用於定義Appender,常見的有三種子節點:Console、RollingFile、File

log4j元件提供了好多種appender供我們使用,介紹看官網咖特別詳細: http://logging.apache.org/log4j/2.x/manual/appenders.html

  • Console節點用來定義輸出到控制檯的Appender.
    • name:指定Appender的名字.
    • target:SYSTEM_OUT(輸出所有) 或 SYSTEM_ERR(只輸出錯誤),一般只設定預設:SYSTEM_OUT.
    • PatternLayout:輸出格式,不設定預設為:%m%n.
  • File節點用來定義輸出到指定位置的檔案的Appender.
    • name:指定Appender的名字.
    • fileName:指定輸出日誌的目的檔案帶全路徑的檔名.
    • PatternLayout:輸出格式,不設定預設為:%m%n.
  • RollingFile節點用來定義超過指定大小自動刪除舊的建立新的的Appender並且可以壓縮檔案.
    • name:指定Appender的名字.
    • fileName:指定輸出日誌的目的檔案帶全路徑的檔名.
    • PatternLayout:輸出格式,不設定預設為:%m%n.
    • filePattern:指定新建日誌檔案的名稱格式.
    • Policies:指定滾動日誌的策略,就是什麼時候進行新建日誌檔案輸出日誌.
    • TimeBasedTriggeringPolicy:Policies子節點,基於時間的滾動策略,interval屬性用來指定多久滾動一次,預設是1 hour。modulate=true用來調整時間:比如現在是早上3am,interval是4,那麼第一次滾動是在4am,接著是8am,12am…而不是7am.
    • SizeBasedTriggeringPolicy:Policies子節點,基於指定檔案大小的滾動策略,size屬性用來定義每個日誌檔案的大小.
    • DefaultRolloverStrategy:用來指定同一個資料夾下最多有幾個日誌檔案時開始刪除最舊的,建立新的(通過max屬性)。

其中:fileName和filePattern不同的作用:

fileName指定的是當天日誌輸出的日誌輸出位置
filePattern指的根據配置,對每天的日誌檔案進行壓縮儲存的時候的檔名,也就是新建的檔名

4:根節點下的子節點Loggers,用於配置上述新增的appender,兩種子節點:Root、Logger

  • Root節點用來指定專案的根日誌,如果沒有單獨指定Logger,那麼就會預設使用該Root日誌輸出

    • level:日誌輸出級別,共有8個級別,按照從低到高為:All < Trace < Debug < Info < Warn < Error < Fatal < OFF.
    • AppenderRef(appender-ref):Root的子節點,用來指定該日誌輸出到哪個Appender.
  • Logger節點用來單獨指定日誌的形式,比如要為指定包下的class指定不同的日誌級別等

    • level:日誌輸出級別,共有8個級別,按照從低到高為:All < Trace < Debug < Info < Warn < Error < Fatal < OFF.
    • additivity : 設定是否繼承,也就是是否將log也列印到Root下,“false”為不列印到Root下
    • name:用來指定該Logger所適用的類或者類所在的包全路徑,繼承自Root節點.
    • AppenderRef(appender-ref):Logger的子節點,用來指定該日誌輸出到哪個Appender,如果沒有指定,就會預設繼承自Root.如果指定了,那麼會在指定的這個Appender和Root的Appender中都會輸出,此時我們可以設定Logger的additivity="false"只在自定義的Appender中進行輸出。

5:輸出格式相關:

  • %t:執行緒名稱
  • %p:日誌級別
  • %c:日誌訊息所在類名
  • %m:訊息內容
  • %M:輸出執行方法
  • %d:發生時間,%d{yyyy-MM-dd HH:mm:ss,SSS},輸出類似:2011-10-18 22:10:28,921
  • %x::輸出和當前執行緒相關聯的NDC(巢狀診斷環境),尤其用到像java servlets這樣的多客戶多執行緒的應用中。
  • %L:程式碼中的行數
  • %n:換行
  • %c{*}系列:顯示LoggerName的格式
    在這裡插入圖片描述
    我平常使用的就是:[%-5p]:%d{YYYY-MM-dd HH:mm:ss} [%t] %c{2}:%L - %msg%n

3.4:demo變形

3.4.1:同步列印日誌

同步列印日誌是最消耗資源的方式,我們在開發的時候,可以選擇使用全同步方式列印日誌,這樣便於我們debug。或者專案併發度不高的情況下也可以使用這種方式。但是,當併發量比較大、對專案響應速度敏感時並且對日誌不是強實時性要求的話,最好還是使用全部非同步或者混合方式。

上述的demo便是全部同步的案例。在此不再贅述。

3.4.2:全部非同步列印日誌

全部非同步列印日誌是對專案請求速度最理想的方式,在500個執行緒的情況下速度幾乎是全同步列印log的10倍,是混合列印的2倍。下面是官網的比較圖,可以對照著看一下:
在這裡插入圖片描述
非同步Logger是讓業務邏輯把日誌資訊放入Disruptor佇列後可以直接返回,具有更高吞吐、呼叫log方法更低的延遲。但也有一些缺點比如:異常處理麻煩、 可變日誌訊息問題、更大的CPU開銷、需要等待“最慢的Appender”消費完成。

所以我們在併發量高、日誌實時性要求不高,並且所暴漏的缺點都可以容忍的情況下最好還是選用全部非同步列印日誌,這樣可以獲得更快的響應,也會給使用者更好的體驗。
非同步列印配置有幾種方式:
1:在你的classpath下面新增個log4j2.component.properties檔案,並且新增以下內容:

這種方式不需要修改原來的log4j2.xml檔案

Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector

2:使用非同步標籤,修改上面的demo的部分:

如果想要全部非同步log的話,一定要所有的相關標籤都是用非同步標籤

    <Loggers>
        <!--
            新增專案日誌
            配置專案日誌只輸出com.bj58.renetwork下面的日誌
            additivity="false" 表示只將該日誌輸出到該配置的路徑下面,並不會再重複輸出到root也就是容器日誌中
        -->
        <asyncLogger name="com.bj58.renetwork" level="debug" additivity="false">
            <appender-ref ref = "ConsolePro"/>
            <appender-ref ref = "INFOPro"/>
            <appender-ref ref = "WARNPro" />
            <appender-ref ref = "ERRORPro" />
        </asyncLogger>
        <!--新增容器日誌-->
        <asyncRoot level="INFO">
            <appender-ref ref = "Console"/>
            <appender-ref ref = "INFO" />
            <appender-ref ref = "WARN" />
            <appender-ref ref = "ERROR" />
        </asyncRoot>
    </Loggers>

3:JVM啟動引數(boot.ini)加上:

-DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector

3.4.3:混合模式列印日誌

混合模式就是既有一非同步又有同步日誌列印,那些部分需要同步或者非同步,這需要根據具體對專案該部分的需求來定了.

下面我設定了專案日誌同步列印,容器日誌非同步列印

    <Loggers>
        <!--
            新增專案日誌
            配置專案日誌只輸出com.bj58.renetwork下面的日誌
            additivity="false" 表示只將該日誌輸出到該配置的路徑下面,並不會再重複輸出到root也就是容器日誌中
        -->
        <logger name="com.bj58.renetwork" level="debug" additivity="false">
            <appender-ref ref = "ConsolePro"/>
            <appender-ref ref = "INFOPro"/>
            <appender-ref ref = "WARNPro" />
            <appender-ref ref = "ERRORPro" />
        </logger>
        <!--新增容器日誌-->
        <asyncRoot level="INFO">
            <appender-ref ref = "Console"/>
            <appender-ref ref = "INFO" />
            <appender-ref ref = "WARN" />
            <appender-ref ref = "ERROR" />
        </asyncRoot>
    </Loggers>

四:其他

4.1:Log日誌level

級別只輸出“大於等於”自身級別的log,7中level的級別關係如下:
OFF > FATAL > ERROR > WARN > INFO > DEBUG > ALL
 1. DEBUG :
    DEBUG Level指出細粒度資訊事件對除錯應用程式是非常有幫助的。
 2. INFO
    INFO level表明 訊息在粗粒度級別上突出強調應用程式的執行過程。
 3.WARN
    WARN level表明會出現潛在錯誤的情形。
 4.ERROR
    ERROR level指出雖然發生錯誤事件,但仍然不影響系統的繼續執行。
 5.FATAL
    FATAL level指出每個嚴重的錯誤事件將會導致應用程式的退出。
 6.ALL
    ALL Level是最低等級的,用於開啟所有日誌記錄。
 7.OFF
    OFF Level是最高等級的,用於關閉所有日誌記錄。

4.2:Log4j2與logback速度對比

Log4j2和logback都是日誌元件,logback就是為了替代log4j1出現的,log4j2是log4j1的升級版,幾乎相當於重構了log4j1。
log4j2的效率可以在多執行緒時,線上程數量大的情況下,超過logback10倍左右!下面是官網提供的資料對比:
速度對比圖(來自官網):
在這裡插入圖片描述
在Solaris和windows作業系統上的資料對比(來自官網):
在這裡插入圖片描述

如果轉載此博文,請附上本文連結,謝謝合作~ :https://blog.csdn.net/csdn___lyy

如果感覺這篇文章對您有所幫助,請點選一下“喜歡”或者“關注”博主,您的喜歡和關注將是我前進的最大動力!

refer:部落格 部落格

相關文章