Log4j1 升級 Log4j2 實戰

本文屬於轉載,方便本身查看。html

原文地址:Log4j1 升級 Log4j2 實戰java

一、背景

在任何系統中,日誌都是很是重要的組成部分,它是反映系統運行狀況的重要依據,也是排查問題時的必要線索。絕大多數人都承認日誌的重要性,可是又有多少人仔細想過該怎麼打日誌,日誌對性能的影響究竟有多大呢?git

新的Log4j 2.0版本有了大幅的性能提高、新的插件系統,以及配置設置方面的不少改善。Log4j 1.x 在高併發狀況下出現死鎖致使cpu使用率異常飆升,而Log4j2.0基於LMAX Disruptor的異步日誌在多線程環境下性能會遠遠優於Log4j 1.x和logback ——官方測試結果。github

本次升級是以thrift服務化項目爲例子進行的,後續會在其餘項目中進行,本次工做內容爲:web

  • Log4j1.x 升級到 Log4j2(若是不想了解原理,能夠直接跳到:三、升級方式

二、log4j2說明

2.1 特性

  • API分離: Log4j2將API與實現分離開來(log4j-api: 做爲日誌接口層,用於統一底層日誌系統,log4j-core : 做爲上述日誌接口的實現,是一個實際的日誌框架) 改進的特定: Log4j2的性能在某些關鍵領域比Log4j 1.x更快,並且大多數狀況下與Logback至關。
  • 多個API支持:Log4j2提供最棒的性能的同時,還支持SLF4J和公共日誌記錄API。
  • 自動配置加載:像Logback同樣,一旦配置發生改變,Log4j2能夠自動載入這些更改後的配置信息,又與Logback不一樣,配置發生改變時不會丟失任何日誌事件。
  • 高級過濾功能:與Logback相似,Log4j2能夠支持基於上下文數據、標記,正則表達式以及日誌事件中的其餘組件的過濾。
  • 插件架構:全部能夠配置的組件都以Log4j插件的形式來定義。無需修改任何Log4j代碼就能夠建立新的Appender、Layout、Pattern Convert 等等。Log4j自動識別預約義的插件,若是在配置中引用到這些插件,Log4j就自動載入使用。
  • 屬性支持:屬性能夠在配置文件中引用,也能夠直接替代或傳入潛在的組件,屬性在這些組件中可以動態解析。屬性能夠是配置文件,系統屬性,環境變量,線程上下文映射以及事件中的數據中定義的值。用戶能夠經過增長本身的Lookup插件來定製本身的屬性。 log4j2配置: 不支持properties文件,但卻能夠以json文件做爲配置

2.2 性能

吞吐量測試 輸入圖片說明正則表達式

平均耗時 輸入圖片說明spring

其中:apache

  • Loggers mixed sync/async: 同步與異步logger能夠混合使用,分別由標籤<logger> <asyncLogger> 指定
  • 異步Logger與異步Appender區別:AsyncAppender使用ArrayBlockingQueue來處理message,AsyncLogger使用LMAX Disruptor
  • AsyncAppender的作法是:應用線程建立LogEvent將其塞入Queue,消費線程取出LogEvent寫磁盤。在這種框架的可擴展性很差,當加倍消費線程時各個線程的吞吐量會減半,因此總吞吐量並不會獲得增長。緣由是,併發queue是標準java庫的一部分,會使用鎖來保證數據傳遞的正確性。
  • LMAX Disruptor是一個無鎖數據結構,能夠在線程間傳遞消息。詳細介紹可訪問其網站:https://github.com/LMAX-Exchange/disruptor/wiki/Introduction

2.3 主要組件

輸入圖片說明

2.4 配置

2.4.1 xml 配置

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
 
    <Properties>
        <Property name="pattern_layout">%d %-5p (%F:%L) - %m%n</Property>
        <Property name="LOG_HOME">/var/***/logs</Property>
    </Properties>
 
    <Appenders>
        <Console name="console" target="SYSTEM_OUT" follow="true">
            <PatternLayout pattern="${pattern_layout}"/>
        </Console>
 
        <RollingRandomAccessFile name="file"
                                 fileName="${LOG_HOME}/${sys:app.key}.log"
                                 filePattern="${LOG_HOME}/${sys:app.key}.log.%d{yyyy-MM-dd}">
            <PatternLayout pattern="${pattern_layout}"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
            </Policies>
        </RollingRandomAccessFile>
 
        <RollingRandomAccessFile name="access_kpi"
                                 fileName="${LOG_HOME}/${sys:app.key}_access_kpi.log"
                                 filePattern="${LOG_HOME}/${sys:app.key}_access_kpi.log.%d{yyyy-MM-dd}">
            <PatternLayout pattern="${pattern_layout}"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
            </Policies>
        </RollingRandomAccessFile>
 
 
        <RollingRandomAccessFile name="jmonitorappender"
                                 fileName="${LOG_HOME}/${sys:app.key}.jmonitor.log"
                                 filePattern="${LOG_HOME}/${sys:app.key}.jmonitor.%d{yyyy-MM-dd}.log.gz">
            <PatternLayout pattern="${pattern_layout}"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
            </Policies>
        </RollingRandomAccessFile>
 
        <RollingRandomAccessFile name="jmonitorlogstoreappender"
                                 fileName="${LOG_HOME}/${sys:app.key}.jmonitor.logstore.log"
                                 filePattern="${LOG_HOME}/${sys:app.key}.jmonitor.logstore.%d{yyyy-MM-dd}.log.gz">
            <PatternLayout pattern="${pattern_layout}"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
            </Policies>
        </RollingRandomAccessFile>
 
        <Scribe name="errorLog">
            <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
            <Property name="hostname">${sys:app.key}</Property>
            <Property name="scribeHost">127.0.0.1</Property>
            <Property name="scribePort">4252</Property>
            <Property name="scribeCategory">cos_errorlog</Property>
            <Property name="printExceptionStack">false</Property>
            <Property name="addStackTraceToMessage">false</Property>
            <Property name="timeToWaitBeforeRetry">6000</Property>
            <Property name="sizeOfInMemoryStoreForward">100</Property>
            <PatternLayout
                    pattern="%d %p $${sys:app.host} $${sys:app.ip} errorlog appkey=$${sys:app.key} location=%F:%L rawlog=%replace{%replace{%m}{=}{:}}{\n|\t}{} rawexception=%replace{%replace{%ex}{=}{:}}{\n|\t}{}%n"/>
        </Scribe>
 
 
    </Appenders>
 
    <Loggers>
 
 
        <Logger name="access_kpi" level="INFO" includeLocation="true" additivity="false">
            <AppenderRef ref="access_kpi"/>
        </Logger>
 
 
 
        <!-- tair Loggers -->
        <Logger name="com.taobao.tair3.client"  level="WARN" includeLocation="true" additivity="false">
            <AppenderRef ref="file"/>
            <AppenderRef ref="errorLog"/>
        </Logger>
 
        <!-- 3rdparty Loggers -->
        <Logger name="org.springframework" level="WARN"/>
        <Logger name="org.apache.zookeeper" level="ERROR"/>
        <Logger name="org.springframework.web" level="WARN"/>
 
        <!-- Root Logger -->
        <Root level="INFO" includeLocation="true">
            <AppenderRef ref="file"/>
            <AppenderRef ref="console"/>
            <AppenderRef ref="errorLog"/>
        </Root>
    </Loggers>
</Configuration>

咱們先看看Configuration的一些特性:json

  • Configuration表明Log4j2的配置文件,它和LoggerContext組件一一對應(關於LoggerContext請看下文),它維護Log4j2各個組件之間的關係,其中,一個Configuration對應多個LoggerConfig組件。
  • Configuration能夠經過四種方式配置:a)配置文件(XML、JSON和YAML);b)建立ConfigurationFactory和Configuration實現;c)經過代碼調用Configuration的API構造;d)在Logger內部調用API函數構造。
  • Configuration可以在應用程序初始化的過程當中進行自動裝配,其配置內容按照必定的順序獲取,詳見:AutomaticConfiguration。
  • 當咱們給Configuration設置monitorInterval時,這可使得log4j2階段性的讀取配置文件,並從新構造Configuration。在這一過程當中,log4j2不會丟失日誌事件。

2.4.2 Configuration標籤

<Configuration status="WARN">
  ...
</Configuration>

該片斷代表log4j2配置文件的全部內容都在這個標籤內,其status屬性爲「WARN」說明:log4j2內部的日誌會將日誌級別大於WARN的日誌打印到Console。除了該字段,Configuration還包括其餘屬性,詳見:ConfigurationSyntax。api

2.4.3 Appenders標籤

<Appenders>
    <Console name="Console" target="SYSTEM_OUT">
      <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
    </Console>
</Appenders>

全部的Appender將在<Appenders>和</Appenders>之間定義。上述例子定義了ConsoleAppender並關聯PatternLayout,關於Appender和Layout請看上述相關小節。

2.4.4 Logger標籤

<Loggers>
    <Logger name="com.foo.Bar" level="trace" additivity="false" includeLocation="true">
      <AppenderRef ref="Console"/>
    </Logger>
    <Root level="error">
      <AppenderRef ref="Console"/>
    </Root>
</Loggers>

全部的Logger將在<Loggers>和</Loggers>之間定義。上述例子經過<Root>定義了全部Logger的根結點(RootLogger),並經過<AppenderRef>標籤關聯名稱爲「Console」的Appender,關於Logger請看上述相關小節。

此處有必要說明additivity字段,經過配置該字段,咱們能夠規定是否將日誌事件傳遞到Logger的父結點處理,其默認值爲true(即默認交給parent Logger處理)。

Logger默認不會獲取location信息,所以,若咱們的Layout或Filter等須要location信息,咱們必須給相應的設置「includeLocation=true」

2.4.5 Filters標籤

<Filters>
    <Marker marker="EVENT" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
    <DynamicThresholdFilter key="loginId" defaultThreshold="ERROR" onMatch="ACCEPT" onMismatch="NEUTRAL">
      <KeyValuePair key="User1" value="DEBUG"/>
    </DynamicThresholdFilter>
</Filters>

log4j2還有一個很重要的組件——Filter,詳見Filter小節。此處經過<Filters>和</Filters>代表這是一個組合Filter,裏邊包括MarkerFilter和DynamicThresholdFilter。onMatch表示和onMismatch表示通過Filter過濾後的結果,該結果有三個取值:ACCEPT、NEUTRAL和DENY。log4j2在處理LogEvents時,會經過該Filter進行過濾,若返回結果爲ACCEPT,則直接處理(略過其它Filter和日誌級別的過濾);若返回DENY則直接終止該LogEvents;若返回NEUTRAL,則不作決策,讓後續代碼作處理。

此處,Filter是經過Configuration的直接子元素配置,所以,LogEvents若被該Filter過濾以後則不會傳遞給Logger處理。

2.5 異步日誌

Log4j2提供了異步Logger,經過不一樣線程實現I/O操做,目的在於爲咱們的應用程序提升性能。咱們先來看一看它主要在哪些方面作改進:

  • Asynchronous Loggers。異步日誌器是Log4j2新增的日誌器,它的目的是讓咱們的應用程序在調用Logger.log()打印日誌時立馬返回。咱們能夠在程序中所有使用異步日誌器,也可使用混合的日誌器,前者能給咱們的程序帶來很大的性能提高,然後者讓咱們的程序足夠靈活。
  • LMAX Disruptor技術。異步日誌器在其內部實現採用了Disruptor技術,相對於使用BlockingQueue,它提升了吞吐量和下降延時。
  • Asynchronous Appender。該組件在Log4j1.x已經存在,可是Log4j2實現的異步Appender使得每次寫入磁盤時,都會進行flush操做,效果和配置「immediateFlush=true」同樣。該異步Appender內部採用ArrayBlockingQueue的方式,所以不須要引入disruptor依賴。
  • RandomAccessFileAppender。該Appender採用ByteBuffer+RandomAccessFile替代了BufferedOutputStream,官方給出的測試數據是它將速度提高了20-200%。
  • AsyncLoggers雖然帶來了極大的性能提高,咱們應該常用。不過,它也有一些缺點,所以,咱們要根據具體的應用場景決定使用同步仍是異步的方式,詳見:Trade-offs。

三、升級方式

如下開始說明*服務化項目如何由:Log4j1.x 升級到 Log4j2

3.1 排除對log4j的依賴

須要肯定項目pom文件中依賴的其餘的jar中也再也不依賴log4j及slf4j-log4j12,具體方式能夠經過IDE提供的功能或者直接使用mvn dependency:tree肯定依賴關係。

因爲引用的jar中不少依然使用的爲log4j,所以已經升級過log4j2的項目,每次在新增依賴的時候,必定須要肯定一下,引用的jar是否含有對低版本的依賴,而且exclusion掉。

<exclusions>
    <exclusion>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
    </exclusion>
    <exclusion>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
    </exclusion>
</exclusions>

3.2 添加對log4j2的依賴

<properties>
    <org.slf4j-version>1.7.12</org.slf4j-version>
    <log4j2-version>2.3</log4j2-version>
</properties>
 
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>${org.slf4j-version}</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-1.2-api</artifactId>
    <version>${log4j2-version}</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>${log4j2-version}</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>${log4j2-version}</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>${log4j2-version}</version>
</dependency>
<dependency>
    <groupId>com.lmax</groupId>
    <artifactId>disruptor</artifactId>
    <version>3.2.0</version>
</dependency>
<dependency>
     <groupId>com.sankuai.meituan</groupId>
     <artifactId>scribe-log4j2</artifactId>
     <version>1.0.9</version>
</dependency>

3.3 JVM參數

在JVM啓動參數中增長 -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector 開啓異步日誌。(目前針對scribe的appender爲同步appender,若是不開啓異步機制會致使線程block)

3.4 XML配置

刪除原log4j.xml配置文件,新增log4j2.xml,注意:須要保證log4j2.xml在resource根目錄內,不然會致使配置文件加載不到(即log4j2.xml須要在class根目錄內)

3.4.1 注意事項

  1. includeLocation:Logger默認不會獲取location信息,所以,若咱們的Layout或Filter等須要location信息,咱們必須給相應的設置「includeLocation=true」
  2. additivity:經過配置該字段,咱們能夠規定是否將日誌事件傳遞到Logger的父結點處理,其默認值爲true
  3. file文件的路徑,因爲啓動腳本及服務器變量配置等的不肯定性,所以該處建議直接配置絕對路徑,可使用<Property name=」LOG_HOME」>/var/*/logs</Property>配置在xml中,也能夠經過JVM參數 -Dapp.logdir=$LOG_HOME等方式
  4. AsyncLogger爲異步日誌,須要添加JVM參數-DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
  5. 服務化項目啓動腳本里若是對啓動日誌作重定向了「>> $LOGDIR/$LOGFILE 2>&1」,請不要使用Console輸出日誌,不然會致使重定向的日誌文件將重複打印全部日誌信息
  6. 服務化項目不須要配置access_kpi的日誌打印
    • Scribe說明(線上異常監控接入說明#線上異常監控接入說明-log4j2.0)
    • hostname:這裏獲取的是java啓動時配置的系統參數
    • scribeCategory:這裏寫死「cos_errorlog」,這樣數據組才知道這套日誌須要發送給sg-errlog系統
    • pattern:按照數據組的要求打印日誌,同時將「等號」和「回車」等替換爲對應的佔位符
    • scribeHost:scribeHost:測試環境爲10.4.232.70,若是服務器上有采集監聽,則能夠配置127.0.0.1

3.5 Log定義

private static final Logger LOGGER = LoggerFactory.getLogger(Boot.class);

使用slf4j進行log的定義,注意須要保證項目中再也不依賴於slf4j1。若是啓動時有以下提示,說明依然依賴了多個slf4j

四、參考資料

http://logging.apache.org/log4j/2.x/manual/migration.html#Configuring_Log4j_2

http://logging.apache.org/log4j/2.x/guidelines.html

http://logging.apache.org/log4j/2.x/performance.html

http://www.infoq.com/cn/articles/things-of-java-log-performance

http://www.infoq.com/cn/news/2014/08/apache-log4j2

相關文章
相關標籤/搜索