簡(jiǎn)介
Apache Log4j 2 is an upgrade to Log4j that provides significant improvements over its predecessor, Log4j 1.x, and provides many of the improvements available in Logback while fixing some inherent problems in Logback’s architecture.
log4j2是log4j 1.x 的升級(jí)版,參考了logback的一些優(yōu)秀的設(shè)計(jì),并且修復(fù)了一些問題,因此帶來了一些重大的提升,主要有:

異常處理,在logback中,Appender中的異常不會(huì)被應(yīng)用感知到,但是在log4j2中,提供了一些異常處理機(jī)制。
性能提升, log4j2相較于log4j 1和logback都具有很明顯的性能提升,后面會(huì)有官方測(cè)試的數(shù)據(jù)。
自動(dòng)重載配置,參考了logback的設(shè)計(jì),當(dāng)然會(huì)提供自動(dòng)刷新參數(shù)配置,最實(shí)用的就是我們?cè)谏a(chǎn)上可以動(dòng)態(tài)的修改日志的級(jí)別而不需要重啟應(yīng)用——那對(duì)監(jiān)控來說,是非常敏感的。
無垃圾機(jī)制,log4j2在大部分情況下,都可以使用其設(shè)計(jì)的一套無垃圾機(jī)制,避免頻繁的日志收集導(dǎo)致的jvm gc。
一些概念
之前看官方文檔摘抄了一些概念,這里懶得翻譯了,使用log4j的都應(yīng)該清楚,這里只是mark下。

舉個(gè)栗子
<Configuration status="debug" packages="org.apache.logging.log4j.test">
<Properties>
<Property name="filename">target/test.log</Property>
</Properties>
<Filter type="ThresholdFilter" level="trace"/>
<Appenders>
<Appender type="Console" name="STDOUT">
<Layout type="PatternLayout" pattern="%m MDC%X%n"/>
<Filters>
<Filter type="MarkerFilter" marker="FLOW" onMatch="DENY" onMismatch="NEUTRAL"/>
<Filter type="MarkerFilter" marker="EXCEPTION" onMatch="DENY" onMismatch="ACCEPT"/>
</Filters>
</Appender>
<Appender type="Console" name="FLOW">
<Layout type="PatternLayout" pattern="%C{1}.%M %m %ex%n"/><!-- class and line number -->
<Filters>
<Filter type="MarkerFilter" marker="FLOW" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
<Filter type="MarkerFilter" marker="EXCEPTION" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
</Appender>
<Appender type="File" name="File" fileName="${filename}">
<Layout type="PatternLayout">
<Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
</Layout>
</Appender>
</Appenders>
<Loggers>
<Logger name="org.apache.logging.log4j.test1" level="debug" additivity="false">
<Filter type="ThreadContextMapFilter">
<KeyValuePair key="test" value="123"/>
</Filter>
<AppenderRef ref="STDOUT"/>
</Logger>
<Logger name="org.apache.logging.log4j.test2" level="debug" additivity="false">
<AppenderRef ref="File"/>
</Logger>
<Root level="trace">
<AppenderRef ref="STDOUT"/>
</Root>
</Loggers>
</Configuration>
異步日志
log4j2最大的特點(diǎn)就是異步日志,其性能的提升主要也是從異步日志中受益,我們來看看如何使用log4j2的異步日志。
Log4j2提供了兩種實(shí)現(xiàn)日志的方式,一個(gè)是通過AsyncAppender,一個(gè)是通過AsyncLogger,分別對(duì)應(yīng)前面我們說的Appender組件和Logger組件。注意這是兩種不同的實(shí)現(xiàn)方式,在設(shè)計(jì)和源碼上都是不同的體現(xiàn)。
AsyncAppender方式
The AsyncAppender accepts references to other Appenders and causes LogEvents to be written to them on a separate Thread. Note that exceptions while writing to those Appenders will be hidden from the application. The AsyncAppender should be configured after the appenders it references to allow it to shut down properly.
By default, AsyncAppender uses java.util.concurrent.ArrayBlockingQueue which does not require any external libraries. Note that multi-threaded applications should exercise care when using this appender as such: the blocking queue is susceptible to lock contention and our tests showed performance may become worse when more threads are logging concurrently. Consider using lock-free Async Loggers for optimal performance.
AsyncAppender是通過引用別的Appender來實(shí)現(xiàn)的,當(dāng)有日志事件到達(dá)時(shí),會(huì)開啟另外一個(gè)線程來處理它們。需要注意的是,如果在Appender的時(shí)候出現(xiàn)異常,對(duì)應(yīng)用來說是無法感知的。 AsyncAppender應(yīng)該在它引用的Appender之后配置,默認(rèn)使用 java.util.concurrent.ArrayBlockingQueue實(shí)現(xiàn)而不需要其它外部的類庫(kù)。 當(dāng)使用此Appender的時(shí)候,在多線程的環(huán)境下需要注意,阻塞隊(duì)列容易受到鎖爭(zhēng)用的影響,這可能會(huì)對(duì)性能產(chǎn)生影響。這時(shí)候,我們應(yīng)該考慮使用無所的異步記錄器(AsyncLogger)。
舉個(gè)栗子
<?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有一些配置項(xiàng),如下:

除此之外還有一些其他的細(xì)節(jié),如果感興趣可以參考官網(wǎng)文檔,這里就不一一列舉了。
AsyncLogger方式
AsyncLogger才是log4j2 的重頭戲,也是官方推薦的異步方式。它可以使得調(diào)用Logger.log返回的更快。你可以有兩種選擇:全局異步和混合異步。
全局異步就是,所有的日志都異步的記錄,在配置文件上不用做任何改動(dòng),只需要在jvm啟動(dòng)的時(shí)候增加一個(gè)參數(shù);
混合異步就是,你可以在應(yīng)用中同時(shí)使用同步日志和異步日志,這使得日志的配置方式更加靈活。因?yàn)長(zhǎng)og4j文檔中也說了,雖然Log4j2提供以一套異常處理機(jī)制,可以覆蓋大部分的狀態(tài),但是還是會(huì)有一小部分的特殊情況是無法完全處理的,比如我們?nèi)绻怯涗泴徲?jì)日志,那么官方就推薦使用同步日志的方式,而對(duì)于其他的一些僅僅是記錄一個(gè)程序日志的地方,使用異步日志將大幅提升性能,減少對(duì)應(yīng)用本身的影響?;旌袭惒降姆绞叫枰ㄟ^修改配置文件來實(shí)現(xiàn),使用AsyncLogger標(biāo)記配置。
舉個(gè)栗子
全局異步
配置文件不用動(dòng):
<?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>
在系統(tǒng)初始化的時(shí)候,增加全局參數(shù)配置:
System.setProperty("log4j2.contextSelector, "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector");
你可以在你第一次獲取Logger之前設(shè)置,也可以加載JVM啟動(dòng)參數(shù)里,類似
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日志的注意事項(xiàng)
在使用異步日志的時(shí)候需要注意一些事項(xiàng),如下:
不要同時(shí)使用AsyncAppender和AsyncLogger,也就是在配置中不要在配置Appender的時(shí)候,使用Async標(biāo)識(shí)的同時(shí),又配置AsyncLogger,這不會(huì)報(bào)錯(cuò),但是對(duì)于性能提升沒有任何好處。
不要在開啟了全局同步的情況下,仍然使用AsyncAppender和AsyncLogger。這和上一條是同一個(gè)意思,也就是說,如果使用異步日志,AsyncAppender、AsyncLogger和全局日志,不要同時(shí)出現(xiàn)。
如果不是十分必須,不管是同步異步,都設(shè)置immediateFlush為false,這會(huì)對(duì)性能提升有很大幫助。
4、如果不是確實(shí)需要,不要打印location信息,比如HTML的location,或者pattern模式里的%C or $class, %F or %file, %l or %location, %L or %line, %M or %method, 等,因?yàn)長(zhǎng)og4j需要在打印日志的時(shí)候做一次棧的快照才能獲取這些信息,這對(duì)于性能來說是個(gè)極大的損耗。
性能提升
關(guān)于性能測(cè)試,大家可以直奔官網(wǎng),哪里有很詳細(xì)的數(shù)據(jù),這里給個(gè)圖:


雖然我測(cè)下來,在immediateFlush設(shè)置為false的情況下,同步異步差不了多少,但可能是我的測(cè)試條件不符合官方的,從設(shè)計(jì)和原理上來說,異步日志,無疑是個(gè)最優(yōu)的選擇。
小結(jié)
總的來說,看了一遍log4j的官網(wǎng)文檔,對(duì)日志系統(tǒng)有了個(gè)比較全面的了解,以前只是copy配置來改改,沒關(guān)注過很多細(xì)節(jié),這次算是掃盲了一次。文章也只是做了個(gè)介紹,在實(shí)際使用中,還是要細(xì)細(xì)研究下配置。
另外,個(gè)人覺得異步模式無非就是在原來同步寫盤的前提下,增加消息隊(duì)列作為緩存,或者交個(gè)另一個(gè)線程去做,這理論上除了帶來一些額外的,較小的cpu和內(nèi)存的開銷,應(yīng)該會(huì)在高流量的時(shí)候帶來不小的性能提升,對(duì)比下來,log4j2無疑是當(dāng)下最值得使用的日志組件來,且可以使用其異步模式。
當(dāng)然了,也不能說異步就一定好,如果日志的流量不是特別大,磁盤性能又跟得上,沒有必要一定使用異步日志。