Java猿社區—log4j2一站式教程

Java猿社區—log4j2一站式教程


參考:
https://www.cnblogs.com/jjj250/p/10350761.html
https://cloud.tencent.com/developer/ask/201470
https://blog.csdn.net/ThinkWon/article/details/101625124
https://www.cnblogs.com/java-zzl/p/10026563.html

前言

目前公司使用的日誌還未統一管理,版本未統一,主要有:

  • log4j-1.2.16
  • log4j-1.2.17
  • logback-1.1.2

存在的缺陷:

  • 日誌版本管理不統一
  • log4j併發量高可能引起的性能和死鎖問題
  • 對於高併發服務同步日誌對性能的影響(比如在線客服回調、抄送和消息服務)

在開發在線客服系統的過程中,壓測環節,考慮到大量頻繁的日誌IO可能會對未來消息回調和抄送服務帶來影響,經過調研準備升級log4j2。

Log4j2、Log4j、Logback性能壓測對比

在這裏插入圖片描述

  • 可見在同步日誌模式下, Logback的性能是最糟糕的.
  • 而log4j2的性能無論在同步日誌模式還是異步日誌模式下都是最佳的.

其根本原因在於log4j2使用了LMAX, 一個無鎖的線程間通信庫代替了logback和log4j之前的隊列. 併發性能大大提升,

1000w條消息測試 985M

log4j2:
同步日誌:log4j2 1000萬 end cost time:123937 ms
異步日誌:log4j2 1000萬 end cost time:62005 ms

官方性能測試報告

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

關於log4j2的新特性

  1. 丟數據這種情況少,可以用來做審計功能。而且自身內部報的exception會被發現,但是logback和log4j不會。
  2. log4j2使用了disruptor技術,在多線程環境下,性能高於logback等10倍以上。
  3. 之前的版本會產生非常多的臨時對象,會造成GC頻繁,log4j2則在這方面上做了優化,減少產生臨時對象。儘可能少的GC
  4. 利用插件系統,使得擴展新的appender,filter,layout等變得容易,log4j不可以擴展 插件
  5. 因爲插件系統的簡單性,所以在配置的時候,可以不用具體指定所要處理的類型。
  6. 可以自定義level
  7. Java 8 lambda support for lazy logging
  8. Support for Message objects
  9. 對filter的功能支持的更強大
  10. 系統日誌(Syslog)協議supports both TCP and UDP
  11. 利用jdk1.5併發的特性,減少了死鎖的發生。
  12. Socket LogEvent SerializedLayout
  13. 支持kafka queue

log4j主要特點

  1. 異常處理,在logback中,Appender中的異常不會被應用感知到,但是在log4j2中,提供了一些異常處理機制。
  2. 性能提升, log4j2相較於log4j 1和logback都具有很明顯的性能提升
  3. 自動重載配置,參考了logback的設計,當然會提供自動刷新參數配置,最實用的就是我們在生產上可以動態的修改日誌的級別而不需要重啓應用——那對監控來說,是非常敏感的。
  4. 無垃圾機制,log4j2在大部分情況下,都可以使用其設計的一套無垃圾機制,避免頻繁的日誌收集導致的jvm gc。

異步日誌

log4j2最大的特點就是異步日誌,其性能的提升主要也是從異步日誌中受益。

Log4j2提供了兩種實現日誌的方式,一個是通過AsyncAppender,一個是通過AsyncLogger,分別對應前面我們說的Appender組件和Logger組件。注意這是兩種不同的實現方式,在設計和源碼上都是不同的體現。

AsyncAppender方式

AsyncAppender是通過引用別的Appender來實現的,當有日誌事件到達時,會開啓另外一個線程來處理它們。需要注意的是,如果在Appender的時候出現異常,對應用來說是無法感知的。 AsyncAppender應該在它引用的Appender之後配置,默認使用 java.util.concurrent.ArrayBlockingQueue實現而不需要其它外部的類庫。 當使用此Appender的時候,在多線程的環境下需要注意,阻塞隊列容易受到鎖爭用的影響,這可能會對性能產生影響。這時候,我們應該考慮使用無鎖的異步記錄器(AsyncLogger)。


<?xml version="1.0" encoding="UTF-8"?><Configuration status="warn" name="MyApp" packages="">
  <Appenders>
    <File name="MyFile" fileName="logs/app.log">
      <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
      </PatternLayout>
    </File>
    <Async name="Async">
      <AppenderRef ref="MyFile"/>
    </Async>
  </Appenders>
  <Loggers>
    <Root level="error">
      <AppenderRef ref="Async"/>
    </Root>
  </Loggers></Configuration>

AsyncAppender有一些配置項,如下:

在這裏插入圖片描述

AsyncLogger方式

AsyncLogger纔是log4j2 的重頭戲,也是官方推薦的異步方式。它可以使得調用Logger.log返回的更快。你可以有兩種選擇:全局異步和混合異步。

  • 全局異步就是,所有的日誌都異步的記錄,在配置文件上不用做任何改動,只需要在jvm啓動的時候增加一個參數;
  • 混合異步就是,你可以在應用中同時使用同步日誌和異步日誌,這使得日誌的配置方式更加靈活。因爲Log4j文檔中也說了,雖然Log4j2提供以一套異常處理機制,可以覆蓋大部分的狀態,但是還是會有一小部分的特殊情況是無法完全處理的,比如我們如果是記錄審計日誌,那麼官方就推薦使用同步日誌的方式,而對於其他的一些僅僅是記錄一個程序日誌的地方,使用異步日誌將大幅提升性能,減少對應用本身的影響。混合異步的方式需要通過修改配置文件來實現,使用AsyncLogger標記配置。
全局異步

<?xml version="1.0" encoding="UTF-8"?>
 
<!-- Don't forget to set system property
-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
     to make all loggers asynchronous. -->
 
<Configuration status="WARN">
  <Appenders>
    <!-- Async Loggers will auto-flush in batches, so switch off immediateFlush. -->
    <RandomAccessFile name="RandomAccessFile" fileName="async.log" immediateFlush="false" append="false">
      <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m %ex%n</Pattern>
      </PatternLayout>
    </RandomAccessFile>
  </Appenders>
  <Loggers>
    <Root level="info" includeLocation="false">
      <AppenderRef ref="RandomAccessFile"/>
    </Root>
  </Loggers></Configuration

在系統初始化的時候,增加全局參數配置:System.setProperty("log4j2.contextSelector,"org.apache.logging.log4j.core.async.AsyncLoggerContextSelector");
你可以在你第一次獲取Logger之前設置,也可以加載JVM啓動參數裏,類似
java -Dog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector

混合異步

<?xml version="1.0" encoding="UTF-8"?>
 
<!-- No need to set system property "log4j2.contextSelector" to any value
     when using <asyncLogger> or <asyncRoot>. -->
 
<Configuration status="WARN">
  <Appenders>
    <!-- Async Loggers will auto-flush in batches, so switch off immediateFlush. -->
    <RandomAccessFile name="RandomAccessFile" fileName="asyncWithLocation.log"
              immediateFlush="false" append="false">
      <PatternLayout>
        <Pattern>%d %p %class{1.} [%t] %location %m %ex%n</Pattern>
      </PatternLayout>
    </RandomAccessFile>
  </Appenders>
  <Loggers>
    <!-- pattern layout actually uses location, so we need to include it -->
    <AsyncLogger name="com.foo.Bar" level="trace" includeLocation="true">
      <AppenderRef ref="RandomAccessFile"/>
    </AsyncLogger>
    <Root level="info" includeLocation="true">
      <AppenderRef ref="RandomAccessFile"/>
    </Root>
  </Loggers></Configuration>

在上面示例的配置中,root logger就是同步的,但是com.foo.Bar的logger就是異步的。

使用Log4j日誌的注意事項

在使用異步日誌的時候需要注意一些事項,如下:

  1. 不要同時使用AsyncAppender和AsyncLogger,也就是在配置中不要在配置Appender的時候,使用Async標識的同時,又配置AsyncLogger,這不會報錯,但是對於性能提升沒有任何好處。
  2. 不要在開啓了全局同步的情況下,仍然使用AsyncAppender和AsyncLogger。這和上一條是同一個意思,也就是說,如果使用異步日誌,AsyncAppender、AsyncLogger和全局日誌,不要同時出現。
  3. 如果不是十分必須,不管是同步異步,都設置immediateFlush爲false,這會對性能提升有很大幫助。
  4. 如果不是確實需要,不要打印location信息,比如HTML的location,或者pattern模式裏的%C or $class, %F or %file, %l or %location, %L or %line, %M or %method, 等,因爲Log4j需要在打印日誌的時候做一次棧的快照才能獲取這些信息,這對於性能來說是個極大的損耗。

異步日誌的弊端

異步日誌不是隻有優點沒有弊端的,官方給出了一些不使用的場景(Drawbacks):

  • 異常處理。即使有異常處理器,也不能覆蓋所有的案例。
  • 在一些使用了諸如 MapMessage 和 StructuredDataMessage 這種沒法通過snapshot規避異步打印時日誌信息可能變化的問題
  • 在CPU很少的環境,比如就1個CPU
  • 打印日誌的速度遠遠超過appender的吞吐。此時異步並不能解決問題,需要用更快的appender。

在線客服Log4j2日誌配置

本次對回調服務、以及抄送服務、消息服務採取Log4j2的異步日誌配置,客服WEB、用戶WEB、業務服務使用Log4j2同步日誌配置

同步日誌配置:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>

    <Properties>
        <!-- 日誌輸出級別 -->
        <Property name="LOG_INFO_LEVEL" value="info"/>
        <!-- error級別日誌 -->
        <Property name="LOG_ERROR_LEVEL" value="error"/>
        <!-- 在當前目錄下創建名爲log目錄做日誌存放的目錄 -->
        <Property name="LOG_HOME" value="./log"/>
        <!-- 檔案日誌存放目錄 -->
        <Property name="LOG_ARCHIVE" value="${LOG_HOME}/archive"/>
        <!-- 模塊名稱, 影響日誌配置名,日誌文件名,根據自己項目進行配置 -->
        <Property name="LOG_MODULE_NAME" value="im-bis"/>
        <!-- 日誌文件大小,超過這個大小將被壓縮 -->
        <Property name="LOG_MAX_SIZE" value="100 MB"/>
        <!-- 保留多少天以內的日誌 -->
        <Property name="LOG_DAYS" value="15"/>
        <!--輸出日誌的格式:%d表示日期,%thread表示線程名,%-5level:級別從左顯示5個字符寬度, %msg:日誌消息,%n是換行符 -->
        <Property name="LOG_PATTERN" value="%d [%t] %-5level %logger{0} - %msg%n"/>
        <!--interval屬性用來指定多久滾動一次-->
        <Property name="TIME_BASED_INTERVAL" value="1"/>
    </Properties>

    <Appenders>
        <!-- 控制檯輸出 -->
        <Console name="STDOUT" target="SYSTEM_OUT">
            <!--輸出日誌的格式-->
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <!--控制檯只輸出level及其以上級別的信息(onMatch),其他的直接拒絕(onMismatch)-->
            <ThresholdFilter level="${LOG_INFO_LEVEL}" onMatch="ACCEPT" onMismatch="DENY"/>
        </Console>

        <!-- 這個會打印出所有的info級別以上,error級別一下的日誌,每次大小超過size或者滿足TimeBasedTriggeringPolicy,則日誌會自動存入按年月日建立的文件夾下面並進行壓縮,作爲存檔-->
        <RollingRandomAccessFile name="RollingRandomAccessFileInfo"
                                 fileName="${LOG_HOME}/${LOG_MODULE_NAME}-info.log"
                                 filePattern="${LOG_ARCHIVE}/${LOG_MODULE_NAME}-info-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <!--如果是error級別拒絕,設置 onMismatch="NEUTRAL" 可以讓日誌經過後續的過濾器-->
                <ThresholdFilter level="${LOG_ERROR_LEVEL}" onMatch="DENY" onMismatch="NEUTRAL"/>
                <!--如果是info\warn輸出-->
                <ThresholdFilter level="${LOG_INFO_LEVEL}" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--interval屬性用來指定多久滾動一次,根據當前filePattern設置是1天滾動一次-->
                <TimeBasedTriggeringPolicy interval="${TIME_BASED_INTERVAL}"/>
                <SizeBasedTriggeringPolicy size="${LOG_MAX_SIZE}"/>
            </Policies>
            <!-- DefaultRolloverStrategy屬性如不設置,則默認同一文件夾下最多保存7個文件-->
            <DefaultRolloverStrategy max="${LOG_DAYS}"/>
        </RollingRandomAccessFile>

        <!--只記錄error級別以上的日誌,與info級別的日誌分不同的文件保存-->
        <RollingRandomAccessFile name="RollingRandomAccessFileError"
                                 fileName="${LOG_HOME}/${LOG_MODULE_NAME}-error.log"
                                 filePattern="${LOG_ARCHIVE}/${LOG_MODULE_NAME}-error-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <ThresholdFilter level="${LOG_ERROR_LEVEL}" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="${TIME_BASED_INTERVAL}"/>
                <SizeBasedTriggeringPolicy size="${LOG_MAX_SIZE}"/>
            </Policies>
            <DefaultRolloverStrategy max="${LOG_DAYS}"/>
        </RollingRandomAccessFile>

    </Appenders>

    <Loggers>
        <!-- 開發測試環境使用 -->
        <Root level="${LOG_INFO_LEVEL}">
            <AppenderRef ref="STDOUT"/>
            <AppenderRef ref="RollingRandomAccessFileInfo"/>
            <AppenderRef ref="RollingRandomAccessFileError"/>
        </Root>

        <!-- 生產預發佈環境使用 -->
        <!--<Root level="${LOG_INFO_LEVEL}">-->
            <!--<AppenderRef ref="RollingRandomAccessFileInfo"/>-->
            <!--<AppenderRef ref="RollingRandomAccessFileError"/>-->
        <!--</Root>-->
    </Loggers>

</Configuration>

異步日誌配置:
方式一:
JVM啓動參數

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

方式二:
log4j2.component.properties

# 設置異步日誌系統屬性 
log4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector

同時在application.properties加入log4j.configurationFile=log4j2.component.properties

log4j2-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>

    <Properties>
        <!-- 日誌輸出級別 -->
        <Property name="LOG_INFO_LEVEL" value="info"/>
        <!-- error級別日誌 -->
        <Property name="LOG_ERROR_LEVEL" value="error"/>
        <!-- 在當前目錄下創建名爲log目錄做日誌存放的目錄 -->
        <Property name="LOG_HOME" value="D:\\logs\\im-message-log"/>
        <!--<Property name="LOG_HOME" value="./log"/>-->
        <!-- 檔案日誌存放目錄 -->
        <Property name="LOG_ARCHIVE" value="${LOG_HOME}/archive"/>
        <!-- 模塊名稱, 影響日誌配置名,日誌文件名,根據自己項目進行配置 -->
        <Property name="LOG_MODULE_NAME" value="im-message"/>
        <!-- 日誌文件大小,超過這個大小將被壓縮 -->
        <Property name="LOG_MAX_SIZE" value="100 MB"/>
        <!-- 保留多少天以內的日誌 -->
        <Property name="LOG_DAYS" value="15"/>
        <!--輸出日誌的格式:%d表示日期,%thread表示線程名,%-5level:級別從左顯示5個字符寬度, %msg:日誌消息,%n是換行符 -->
        <Property name="LOG_PATTERN" value="%d [%t] %-5level %logger{0} - %msg%n"/>
        <!--interval屬性用來指定多久滾動一次-->
        <Property name="TIME_BASED_INTERVAL" value="1"/>
    </Properties>

    <Appenders>
        <!-- 控制檯輸出 -->
        <Console name="STDOUT" target="SYSTEM_OUT">
            <!--輸出日誌的格式-->
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <!--控制檯只輸出level及其以上級別的信息(onMatch),其他的直接拒絕(onMismatch)-->
            <ThresholdFilter level="${LOG_INFO_LEVEL}" onMatch="ACCEPT" onMismatch="DENY"/>
        </Console>

        <!-- 這個會打印出所有的info級別以上,error級別一下的日誌,每次大小超過size或者滿足TimeBasedTriggeringPolicy,則日誌會自動存入按年月日建立的文件夾下面並進行壓縮,作爲存檔-->
        <!--異步日誌會自動批量刷新,所以將immediateFlush屬性設置爲false-->
        <RollingRandomAccessFile name="RollingRandomAccessFileInfo"
                                 fileName="${LOG_HOME}/${LOG_MODULE_NAME}-infoLog.log"
                                 filePattern="${LOG_ARCHIVE}/${LOG_MODULE_NAME}-infoLog-%d{yyyy-MM-dd}-%i.log.gz"
                                 immediateFlush="false">
            <Filters>
                <!--如果是error級別拒絕,設置 onMismatch="NEUTRAL" 可以讓日誌經過後續的過濾器-->
                <ThresholdFilter level="${LOG_ERROR_LEVEL}" onMatch="DENY" onMismatch="NEUTRAL"/>
                <!--如果是info\warn輸出-->
                <ThresholdFilter level="${LOG_INFO_LEVEL}" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--interval屬性用來指定多久滾動一次,根據當前filePattern設置是1天滾動一次-->
                <TimeBasedTriggeringPolicy interval="${TIME_BASED_INTERVAL}"/>
                <SizeBasedTriggeringPolicy size="${LOG_MAX_SIZE}"/>
            </Policies>
            <!-- DefaultRolloverStrategy屬性如不設置,則默認同一文件夾下最多保存7個文件-->
            <DefaultRolloverStrategy max="${LOG_DAYS}"/>
        </RollingRandomAccessFile>

        <!--只記錄error級別以上的日誌,與info級別的日誌分不同的文件保存-->
        <RollingRandomAccessFile name="RollingRandomAccessFileError"
                                 fileName="${LOG_HOME}/${LOG_MODULE_NAME}-errorLog.log"
                                 filePattern="${LOG_ARCHIVE}/${LOG_MODULE_NAME}-errorLog-%d{yyyy-MM-dd}-%i.log.gz"
                                 immediateFlush="false">
            <Filters>
                <ThresholdFilter level="${LOG_ERROR_LEVEL}" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="${TIME_BASED_INTERVAL}"/>
                <SizeBasedTriggeringPolicy size="${LOG_MAX_SIZE}"/>
            </Policies>
            <DefaultRolloverStrategy max="${LOG_DAYS}"/>
        </RollingRandomAccessFile>

    </Appenders>

    <Loggers>
        <!-- 開發環境使用 -->
        <Root level="${LOG_INFO_LEVEL}">
            <AppenderRef ref="STDOUT"/>
            <AppenderRef ref="RollingRandomAccessFileInfo"/>
            <AppenderRef ref="RollingRandomAccessFileError"/>
        </Root>

        <!-- 生產預發佈環境使用 -->
        <!--<Root level="${LOG_INFO_LEVEL}" includeLocation="false">-->
            <!--<AppenderRef ref="RollingRandomAccessFileInfo"/>-->
            <!--<AppenderRef ref="RollingRandomAccessFileError"/>-->
        <!--</Root>-->
    </Loggers>

</Configuration>

個人總結

個人覺得異步模式無非就是在原來同步寫盤的前提下,增加消息隊列作爲緩存,或者交個另一個線程去做,這理論上除了帶來一些額外的,較小的cpu和內存的開銷,應該會在高流量的時候帶來不小的性能提升,對比下來,log4j2無疑是當下最值得使用的日誌組件來,且可以使用其異步模式。

當然了,也不能說異步就一定好,如果日誌的流量不是特別大,磁盤性能又跟得上,沒有必要一定使用異步日誌。

注意:
1、混合異步:如果是記錄審計日誌,官方就推薦使用同步日誌的方式,而對於其他的一些僅僅是記錄一個程序日誌的地方,使用異步日誌將大幅提升性能,減少對應用本身的影響。
2、全局異步:如果僅僅是記錄程序日誌,採用全局異步的方式記錄日誌,將大幅提升性能,減少對應用本身的影響。

想提升編碼能力嗎,掃碼購買提升逼格!

在這裏插入圖片描述

歡迎加入Java猿社區!
免費領取我歷年收集的所有學習資料哦!

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章