[TOC]
1. 版本選擇
- 推薦使用log4j-2.11
- 推薦使用slf4j作為日志門面
2. pom依賴配置和升級方案
pom配置
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.24</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-1.2-api</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.11.0</version>
</dependency>
注意辉阶,不可同時使用log4j-slf4j-impl和log4j-to-slf4j畏纲,否則會引發(fā)循環(huán)依賴。詳情見log4j官方文檔。
如果使用了upcommonlog,橋接也要替換為2.x版本
<dependency>
<groupId>com.unionpay.common.upcommon-log</groupId>
<artifactId>uplog-bridge-log4j2.X</artifactId>
<version>1.2.1</version>
</dependency>
如果使用異步日志永毅,需要加入disruptor依賴
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.2</version>
</dependency>
為了防止干擾,可以將其他組件引入的log4j 1的依賴exclude掉
3. log4j2配置最佳實踐
示例配置1——最簡配置
說明:在這種配置下人弓,應用日志全部打印在同一個日志文件中沼死,應用中使用的公共組件日志因為沒有配置logger所以不會打印(會打印在root logger配置的console中)。建議應用根據(jù)該文件作適當修改崔赌,以適配具體的應用日志需求
配置文件如下
<?xml version="1.0" encoding="UTF-8"?>
<!-- monitorInterval配置成一個正整數(shù)漫雕,則每隔這么久的時間(秒),log4j2會刷新一次配置峰鄙。如果不配置則不會動態(tài)刷新 -->
<Configuration status="INFO" monitorInterval="30">
<Properties>
<Property name="baseLogDir">logs</Property>
<Property name="pattern">%d{yyyyMMdd-HHmmss.SSS} [%level] %c{1} - %msg%n</Property>
</Properties>
<!-- 先定義所有的appender -->
<Appenders>
<!-- 這個輸出控制臺的配置 -->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout>
<Pattern>${pattern}</Pattern>
</PatternLayout>
</Console>
<!-- 應用info日志 -->
<RollingRandomAccessFile name="APPINFO_APPENDER" fileName="${baseLogDir}/appinfo.log"
filePattern="${baseLogDir}/appinfo.log.%d{yyyyMMddHH}.%i.gz">
<PatternLayout>
<Pattern>${pattern}</Pattern>
</PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="500MB" />
<TimeBasedTriggeringPolicy interval="1" modulate="true" />
</Policies>
<Filters>
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY" />
</Filters>
<!-- max=20標識一小時內(nèi)最多產(chǎn)生20個日志文件 -->
<DefaultRolloverStrategy max="20">
<!-- 對于指定的路徑下的指定后綴的文件浸间,只保留3天的日志文件,那么最多會有3天*24小時*20個日志文件 -->
<!-- 注意應用需要根據(jù)業(yè)務需求和磁盤大小評估需要保留的日志個數(shù)吟榴,對于500M的日志文件來說魁蒜,要根據(jù)應用日志的情況,觀察單個日志壓縮后文件大小吩翻,并計算總大小需要的空間 -->
<Delete basePath="${baseLogDir}" maxDepth="1">
<IfFileName glob="*.gz" />
<IfLastModified age="3d" />
</Delete>
</DefaultRolloverStrategy>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<!-- root是默認的logger兜看,也是所有l(wèi)ogger的父級logger,如果需要狭瞎,可以在這里配置一個文件appender以打印外部組件日志 -->
<AsyncRoot level="WARN">
<AppenderRef ref="Console" />
</AsyncRoot>
<!-- 應用日志细移,采用異步模式,name根據(jù)實際的包名修改熊锭;生產(chǎn)環(huán)境中additivity建議設(shè)置為false以避免在root logger中重復打印 -->
<AsyncLogger name="com.unionpay" level="INFO" includeLocation="false" additivity="false">
<AppenderRef ref="APPINFO_APPENDER" />
</AsyncLogger>
</Loggers>
</Configuration>
示例配置2——日志分類
說明:在這種配置下弧轧,應用的info日志打印在appinfo.log中,應用的warn及以上日志打印在apperror.log中碗殷,其他公共組件的日志打印在server.log中精绎。建議應用根據(jù)該配置作適當修改,以適配具體的應用日志需求
配置文件如下
<?xml version="1.0" encoding="UTF-8"?>
<!-- monitorInterval配置成一個正整數(shù)锌妻,則每隔這么久的時間(秒)代乃,log4j2會刷新一次配置。如果不配置則不會動態(tài)刷新 -->
<Configuration status="INFO" monitorInterval="30">
<Properties>
<!-- 應用需要修改為合適的log路徑 -->
<Property name="baseLogDir">logs</Property>
<Property name="pattern">%d{yyyyMMdd-HHmmss.SSS} [%level] %c{1} - %msg%n</Property>
</Properties>
<!-- 先定義所有的appender -->
<Appenders>
<!-- 這個輸出控制臺的配置 -->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout>
<Pattern>${pattern}</Pattern>
</PatternLayout>
</Console>
<!-- 系統(tǒng)日志仿粹,可以作為root logger的appender搁吓,供打印一些中間件的日志 -->
<RollingRandomAccessFile name="SYS_APPENDER" fileName="${baseLogDir}/server.log"
filePattern="${baseLogDir}/server.log.%d{yyyyMMddHH}.%i.gz">
<PatternLayout>
<Pattern>${pattern}</Pattern>
</PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="200MB" />
<TimeBasedTriggeringPolicy interval="1" modulate="true" />
</Policies>
<Filters>
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY" />
</Filters>
<!-- max=6標識一小時內(nèi)最多產(chǎn)生6個日志文件 -->
<DefaultRolloverStrategy max="6">
<!-- 對于指定的路徑下的指定后綴的文件原茅,只保留1天的日志文件,那么最多會有24小時*6個日志文件 -->
<Delete basePath="${baseLogDir}" maxDepth="1">
<IfFileName glob="*.gz" />
<IfLastModified age="1d" />
</Delete>
</DefaultRolloverStrategy>
</RollingRandomAccessFile>
<!-- 應用info日志 -->
<RollingRandomAccessFile name="APPINFO_APPENDER" fileName="${baseLogDir}/appinfo.log"
filePattern="${baseLogDir}/appinfo.log.%d{yyyyMMddHH}.%i.gz">
<PatternLayout>
<Pattern>${pattern}</Pattern>
</PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="500MB" />
<TimeBasedTriggeringPolicy interval="1" modulate="true" />
</Policies>
<Filters>
<!-- 當前appender只打印info日志堕仔,warn及以上日志忽略员咽,由后面的appender決定是否需要打印 -->
<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL" />
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY" />
</Filters>
<!-- max=20標識一小時內(nèi)最多產(chǎn)生20個日志文件 -->
<DefaultRolloverStrategy max="20">
<!-- 對于指定的路徑下的指定后綴的文件,只保留3天的日志文件贮预,那么最多會有3天*24小時*20個日志文件 -->
<!-- 注意應用需要根據(jù)業(yè)務需求和磁盤大小評估需要保留的日志個數(shù),對于500M的日志文件來說契讲,要根據(jù)應用日志的情況仿吞,觀察單個日志壓縮后文件大小,并計算總大小需要的空間 -->
<Delete basePath="${baseLogDir}" maxDepth="1">
<IfFileName glob="*.gz" />
<IfLastModified age="3d" />
</Delete>
</DefaultRolloverStrategy>
</RollingRandomAccessFile>
<!-- 應用錯誤日志 -->
<RollingRandomAccessFile name="APPERROR_APPENDER" fileName="${baseLogDir}/apperror.log"
filePattern="${baseLogDir}/apperror.log.%d{yyyyMMddHH}.%i.gz">
<PatternLayout>
<Pattern>${pattern}</Pattern>
</PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="500MB" />
<TimeBasedTriggeringPolicy interval="1" modulate="true" />
</Policies>
<Filters>
<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY" />
</Filters>
<!-- max=10標識一小時內(nèi)最多產(chǎn)生10個日志文件 -->
<DefaultRolloverStrategy max="10">
<!-- 對于指定的路徑下的指定后綴的文件捡偏,只保留3天的日志文件唤冈,那么最多會有3*24小時*10個日志文件 -->
<Delete basePath="${baseLogDir}" maxDepth="1">
<IfFileName glob="*.gz" />
<IfLastModified age="3d" />
</Delete>
</DefaultRolloverStrategy>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<!-- root是默認的logger厦幅,也就是公共的logger五督,供記錄一些不常打印的系統(tǒng)參數(shù)或者其他組件參數(shù) -->
<AsyncRoot level="WARN">
<AppenderRef ref="Console" />
<AppenderRef ref="SYS_APPENDER" />
</AsyncRoot>
<!-- 常打印的應用日志心墅,建議獨立配置宜狐,并采用異步模式丰辣。name根據(jù)實際的包名修改剂癌,生產(chǎn)環(huán)境中additivity建議設(shè)置為false以避免在root logger中重復打印 -->
<AsyncLogger name="com.unionpay" level="INFO" includeLocation="false" additivity="false">
<AppenderRef ref="APPINFO_APPENDER" />
<AppenderRef ref="APPERROR_APPENDER" />
</AsyncLogger>
</Loggers>
</Configuration>
4. log4j調(diào)優(yōu)和注意事項
4.1 日志模式-同步/異步
log4j2提供了AsyncAppender和AsyncLogger以及全局異步怖现,開啟方式如下
- 同步模式:默認配置即為同步模式蕴茴,即沒有使用任何AsyncAppender和AsyncLogger
- 全局異步:配置按照同步方式配琉预,通過添加jvm啟動參數(shù)即可開啟全局異步董饰,無需修改配置和應用
- 混合異步:使用異步Logger和同步Logger的混合配置,且不開啟全局異步圆米,即Logger配置中部分AsyncLogger卒暂,部分Logger
日志模式使用注意事項
- 如果使用異步,建議使用AsyncLogger實現(xiàn)而不是AsyncAppender
- 如果使用同步娄帖,AsyncLogger也祠、AsyncAppender和全局異步只能使用一種,不可以同時配置AsyncAppender和AsyncLogger近速,或者配置了異步的情況下啟用了全局異步
4.2 日志滾動和清除策略
log4j2提供了基于文件大小的滾動策略和基于時間的滾動策略诈嘿,也可以二者并用,這里給出基于大小的滾動策略配置和基于大小/時間雙滾動策略配置削葱。
基于大小的滾動策略
-
按照大小滾動永淌,啟用壓縮,并最多保留N個文件
<!--此處舉例為每500M滾動一個文件佩耳,且最多保留20個文件遂蛀,具體需要根據(jù)應用的日志量和希望保留日志大小以及磁盤空間進行評估--> <RollingRandomAccessFile name="APPINFO_APPENDER" fileName="${baseLogDir}/appinfo.log" filePattern="${baseLogDir}/appinfo.log.%i.gz"> <PatternLayout> <Pattern>${pattern}</Pattern> </PatternLayout> <Policies> <SizeBasedTriggeringPolicy size="500MB" /> </Policies> <Filters> <!-- 當前appender只打印info日志,warn及以上日志忽略干厚,由后面的錯誤日志記錄 --> <ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL" /> <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY" /> </Filters> <!-- max=20表示最多保留20個日志文件 --> <DefaultRolloverStrategy max="20"/> </RollingRandomAccessFile>
此種策略的空間占用計算公式為(這里計算的是最大空間需求)
日志空間需求=日志滾動閾值(例如500M)+日志留存?zhèn)€數(shù)*日志滾動閾值*1/壓縮比(對于gz壓縮比一般會是幾十李滴,具體根據(jù)應用日志壓縮后計算)
例如假設(shè)壓縮比為50螃宙,則500M的文件壓縮后只有10M,那么上述配置總空間大小是
日志空間需求=500M+20*500*1/50=700M
基于大小/時間雙滾動滾動策略
-
按照大小和時間滾動所坯,啟用壓縮谆扎,單位時間內(nèi)控制最多保留日志個數(shù)并控制總的日志留存時間
<!--此處舉例為每小時滾動一個文件且每500M滾動一個文件,控制每小時最多保留20個文件芹助,總的文件保留3天--> <!--具體需要根據(jù)應用的日志量和希望保留日志大小以及磁盤空間進行評估--> <RollingRandomAccessFile name="APPINFO_APPENDER" fileName="${baseLogDir}/appinfo.log" filePattern="${baseLogDir}/appinfo.log.%d{yyyyMMddHH}.%i.gz"> <PatternLayout> <Pattern>${pattern}</Pattern> </PatternLayout> <Policies> <SizeBasedTriggeringPolicy size="500MB" /> <TimeBasedTriggeringPolicy interval="1" modulate="true" /> </Policies> <Filters> <!-- 當前appender只打印info日志堂湖,warn及以上日志忽略,由后面的錯誤日志記錄 --> <ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL" /> <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY" /> </Filters> <!-- max=20表示一小時內(nèi)最多保留20個日志文件 --> <DefaultRolloverStrategy max="20"> <!-- 對于指定的路徑下的指定后綴的文件状土,只保留3天的日志文件无蜂,那么最多會有3天*24小時*20個日志文件 --> <!-- 注意應用需要根據(jù)業(yè)務需求和磁盤大小評估需要保留的日志個數(shù),對于500M的日志文件來說蒙谓,要根據(jù)應用日志的情況斥季,觀察單個日志壓縮后文件大小,并計算總大小需要的空間 --> <Delete basePath="${baseLogDir}" maxDepth="1"> <IfFileName glob="*.gz" /> <IfLastModified age="3d" /> </Delete> </DefaultRolloverStrategy> </RollingRandomAccessFile>
此種策略的空間占用計算公式為(這里計算的是最大空間需求)
日志留存?zhèn)€數(shù)=保留時間*單位時間內(nèi)最大日志個數(shù)
日志空間需求=日志滾動閾值(例如500M)+日志留存?zhèn)€數(shù)*日志滾動閾值*1/壓縮比(對于gz壓縮比一般會是幾十累驮,具體根據(jù)應用日志壓縮后計算)
例如假設(shè)壓縮比為50酣倾,則500M的文件壓縮后只有10M,那么上述配置總空間大小是
日志留存?zhèn)€數(shù)=3*24*20=1440
日志空間需求=500M+1440*500*1/50=15G
注意谤专,控制總的日志留存時間的機制躁锡,需要log4j-2.5及以上的版本支持,建議使用2.11版本
4.3 其他注意事項和調(diào)優(yōu)
- 推薦在Configuration中添加monitorInterval以支持動態(tài)刷新
- 推薦使用異步日志而不是同步日志置侍,可以是混合異步也可以是全局異步
- 不推薦配置AsyncAppender稚铣,如果需要混合異步,使用AsyncLogger
- PatternLayout不要使用%L墅垮、%C惕医、%method等含有“位置信息”的配置項,非常影響性能算色。同時logger配置中需要加上inclueLocation="false"抬伺,這樣即使配置了位置信息也會被忽略
- 推薦使用RollingRandomAccessFile做為appender
- 推薦基于大小和時間的雙重文件滾動策略,并配合壓縮
4.4 log4j-disruptor等待策略
log4j2的日志使用了disruptor灾梦,其內(nèi)部使用了基于ringbuffer的環(huán)形隊列峡钓,并且也有生產(chǎn)者消費者的概念。在消費者等待消息事件(也就是日志消息)時若河,其內(nèi)部有一處等待策略的配置能岩,配置項可以是Block/Timeout/Sleep/Yield,默認Timeout萧福,不同策略的含義如下
- Block拉鹃,在I/O線程等待時使用鎖和條件觸發(fā)機制,當cpu的優(yōu)先級高于吞吐量和延遲的時候推建議使用。官方推薦在資源受限/虛擬化環(huán)境中使用
- Timeout膏燕,是Block策略的變種钥屈,它將定期從等待中被喚醒,確保了如果錯過了通知坝辫,disruptor消費者不會被永久阻塞篷就,但會有較小的延遲(10ms)
- Sleep,等待IO事件先自旋近忙,然后調(diào)用Thread.yield()竭业,最后park,在性能和cpu之間取得一個折中
- Yield及舍,等待IO事件先自旋未辆,然后調(diào)用用Thread.yield()等待日志事件,相較于Sleep可能會消耗更多的cpu
log4j2默認策略是Timeout击纬,在實際測試中,我們嘗試測試出不同策略下的cpu占用和延遲時間情況钾麸,但測試結(jié)果并沒有明顯的數(shù)據(jù)對比更振,因此這里僅供參考,應用如果修改饭尝,需要結(jié)合場景做全面的測試肯腕。例如如果發(fā)現(xiàn)cpu占用較高,可以嘗試修改為Block或者其他策略并測試觀察钥平。
修改disruptor wait策略的方法為(以修改為Block為例)
-Dlog4j2.asyncLoggerWaitStrategy=Block
5. slf4j代碼優(yōu)化建議
此處針對的是使用了slf4j的代碼实撒,不適用于其它情況
我們知道,slf4j打印日志可以使用占位符的方式涉瘾,例如一般打印日志的代碼:
logger.debug("this is log4j2, current time:" + System.currentTimeMillis());
在使用了slf4j的情況下知态,該句的功能等價于
logger.debug("this is log4j2, current time:{}", System.currentTimeMillis());
- 上面兩行的代碼功能相同,但是前一句在每次執(zhí)行時立叛,無論我們的日志級別是不是debug即以上负敏,每次都會生成一個新的字符串,字符串的字面值是前綴加上系統(tǒng)當前時間秘蛇。即使我們的日志級別配置成warn其做,該句也會產(chǎn)生一個新的字符串
- 后一句,當我們?nèi)罩炯墑e是debug或者小于debug的時候赁还,才會真的創(chuàng)建一個完整的字符串妖泄,否則內(nèi)存中只會有包含了占位符的唯一一個字符串
如果這種情況非常多,那么直接拼接字符串的方式對于內(nèi)存的浪費就非常明顯了艘策,這時候使用占位符的方式蹈胡,可以明顯的改善字符串的生成數(shù)量。當然也不是說任何地方都要使用占位符,因為占位符拼接成字符串审残,也是有開銷的梭域,起碼要遍歷占位符后面的參數(shù)。因此一般建議:
- 對于一定要打印的日志搅轿,使用字符串拼接的方式(必要時引入StringBuilder輔助而不是一直加)
- 對于調(diào)整級別才需要打印的日志病涨,使用占位符的方式而不是直接拼接