Java中的日志框架
常見日志框架
JUL|Log4j1|Log4j2|Logback|JCL|Slf4j
日志框架需要解決的問題
- 穩(wěn)定性高:不可影響主進(jìn)程的正常運(yùn)行且自身的日志內(nèi)容準(zhǔn)確無歧義
- 擴(kuò)展性高:對于不同的日志輸出需求,有便捷的方式進(jìn)行自定義擴(kuò)展
- 開銷低:不可占用服務(wù)器的過多資源,影響主進(jìn)程的執(zhí)行速度
- 延遲低:對應(yīng)的日志內(nèi)容輸出不可延遲太久,否則失去觀察的意義
框架對比
| 框架 | 版本 | 優(yōu)點(diǎn) | 缺點(diǎn) | 作者 | 備注 |
|---|---|---|---|---|---|
| JUL | @since JDK 1.4 | JDK自帶日志工具類,無需額外依賴 | 功能單一 | Oracle | java.util.logging;提供了基礎(chǔ)的Handler(Appender),F(xiàn)ormatter(Pattern)等組件化功能 |
| Logback | 2006-2018 | 優(yōu)化了Log4j1中的缺陷及不足;配合Slf4j使用時(shí)不需要引入適配層 | 重載配置文件時(shí)可能丟失日志 | QOS.ch | 出現(xiàn)時(shí)間介于Log4j1與Log4j2,是基于Slf4j標(biāo)準(zhǔn)的原生實(shí)現(xiàn),相比其他框架不需要引入適配層 |
| Log4j1 | 1999-2012 | 標(biāo)準(zhǔn)的日志接口;模塊化的設(shè)計(jì)理念; | 多線程下可能存在死鎖 | Apache | 2015年被Apache聲明不再維護(hù),最后版本為2012年發(fā)布的log4j 1.2.17 |
| Log4j2 | 2012-2019 | 更少的內(nèi)存占用;更高的并發(fā)性能;更完善的使用手冊 | 特性繁多,完全掌握需要一定學(xué)習(xí)成本 | Apache | 在1的版本上完全重寫,基于LMAX Disruptor庫使得并發(fā)性能大幅提升 |
| JCL | 2005-2014 | Apache 官方項(xiàng)目 | 使用不當(dāng)易存在內(nèi)存泄漏 | Apache | Apache Commons Logging;日志抽象接口層,最新版截止2014年;因設(shè)計(jì)理念及使用方式導(dǎo)致在某些情況下存在內(nèi)存泄漏的問題 |
| Slf4j | 2009-2019 >=1.6.0 | 易用,單jar包,使用范圍廣 | QOS.ch | Simple Logging Facade for Java日志抽象接口層 |
Slf4j
- 保證了項(xiàng)目內(nèi)日志框架升級的便捷性,項(xiàng)目間日志框架的一致性
- 利用
Bridging legacy logging APIs實(shí)現(xiàn)已有JCL、JUL、Log4j多項(xiàng)目的歸并統(tǒng)一 - 參數(shù)化日志打印
- 無綁定、多綁定、版本異常等可以在加載期進(jìn)行檢測提示
解決的問題


調(diào)用關(guān)系鏈

Log4j2
性能對比


基礎(chǔ)概念
Log Level
OFF|FATAL|ERROR|WARN|INFO|DEBUG|TRACE|ALL
Log Event
| Event Level | LoggerConfig Level | ||||||
|---|---|---|---|---|---|---|---|
| TRACE | DEBUG | INFO | WARN | ERROR | FATAL | OFF | |
| ALL | YES | YES | YES | YES | YES | YES | NO |
| TRACE | YES | NO | NO | NO | NO | NO | NO |
| DEBUG | YES | YES | NO | NO | NO | NO | NO |
| INFO | YES | YES | YES | NO | NO | NO | NO |
| WARN | YES | YES | YES | YES | NO | NO | NO |
| ERROR | YES | YES | YES | YES | YES | NO | NO |
| FATAL | YES | YES | YES | YES | YES | YES | NO |
| OFF | NO | NO | NO | NO | NO | NO | NO |
Appender
真正執(zhí)行日志輸出的類,log4j2預(yù)定義了多種用途的Appender如Console Appender,F(xiàn)ile Appender,Http Appender等,其中Appender按執(zhí)行層級又可以分為二種:普通Appender與引用Appender,引用Appender即自身并不實(shí)現(xiàn)具體的輸出而是對普通Appender進(jìn)行了一層包裝來實(shí)現(xiàn)異步、過濾、轉(zhuǎn)發(fā)等目的
Logger
具體的日志對象,一個(gè)Logger對象可以包含[0, n)個(gè)Appender來同時(shí)輸出到不同流;同時(shí)Logger對象還包含一些管理信息如Log Level及Log Filter等
結(jié)構(gòu)圖解

常用配置項(xiàng)
<?xml version="1.0" encoding="UTF-8"?>;
<Configuration name="my configuration file" status="WARN" monitorInterval="30" desc="log4jdebug.log">
<Properties>
<Property name="name1">value</property>
<Property name="name2" value="value2"/>
</Properties>
<filters>
<MarkerFilter marker="FLOW" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
<MarkerFilter marker="EXCEPTION" onMatch="ACCEPT" onMismatch="DENY"/>
</filters>
<Appenders>
<RollingFile name="RollingFile" fileName="logs/app.log"
filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="250 MB"/>
</Policies>
</RollingFile>
...
</Appenders>
<Loggers>
<Logger name="name1">
<filter ... />
<AppenderRef ref="name1"/>
<AppenderRef ref="name2"/>
</Logger>
...
<Root level="level">
<AppenderRef ref="name"/>
</Root>
</Loggers>
</Configuration>
Configuration
- status:LogLevel;log4j2內(nèi)部代碼日志級別,調(diào)試時(shí)可設(shè)置為trace
- monitorInterval:配置變更檢測間隔,單位秒;注意只有當(dāng)monitorInterval之后并有新的logEvent時(shí)才會(huì)真正觸發(fā)reconfiguration
- dest:err|out|file|URL;log4j2內(nèi)部代碼輸出流,對于不方便直接查看的環(huán)境可以導(dǎo)出調(diào)試信息至文件
Properties
類似POM文件中的Properties,定義通過kvp,引用通過{prefix:name}
,如{sys:some.property:-default_value}
表示取名為some.property的系統(tǒng)參數(shù),當(dāng)不存在時(shí)使用default_value`進(jìn)行替換
Filter
Log Event過濾器,每個(gè)過濾器有三種返回結(jié)果Accept|Deny|Neutral,分別表示直接接受,直接拒絕,向下傳遞;根據(jù)Filter的作用域又可以分為以下三種
- 全局Filter,配置節(jié)點(diǎn)與Properties\Appenders\Loggers同級
- Logger Filter,位于Logger中,針對某個(gè)具體的Logger進(jìn)行過濾
- Appender Filter,位于Appender中,針對某個(gè)具體的Appender進(jìn)行過濾
Appenders
Rolling File Appender
| Parameter Name | Type | Values | Default | Description |
|---|---|---|---|---|
| append | boolean | true|false | true | 新日志附加至文件末尾或全量覆蓋 |
| bufferedIO | boolean | true|false | true | 是否開啟文件寫入緩存 |
| bufferSize | int | 8192 | 配合bufferedIO使用,單位字節(jié) | |
| createOnDemand | boolean | true|false | false | 是否開啟延遲創(chuàng)建文件 |
| filter | Filter | 過濾器,多個(gè)filter應(yīng)使用filters標(biāo)簽 | ||
| fileName | String | 日志路徑,若不存在則自動(dòng)創(chuàng)建 | ||
| filePattern | String | 滾動(dòng)文件名,支持占位符及自動(dòng)壓縮 | ||
| immediateFlush | boolean | true|false | true | 是否立即寫入磁盤 |
| layout | Layout | %m%n | 日志內(nèi)容格式,參考Pattern Layout | |
| name | String | 同配置集中,Appender的name必須唯一 | ||
| policy | TriggeringPolicy | 滾動(dòng)觸發(fā)策略,決定何時(shí)進(jìn)行文件滾動(dòng) | ||
| policy.OnStartupTriggeringPolicy.minSize | long | 1 | 滾動(dòng)文件大小最小值 | |
| policy.SizeBasedTriggeringPolicy.size | String | 20KB|MB|GB | filePattern中必須包含%i項(xiàng),否則會(huì)導(dǎo)致文件直接被覆蓋 | |
| policy.TimeBasedTriggeringPolicy.interval | int | 1 | 基于filePattern中的日期精度單位觸發(fā)滾動(dòng) | |
| policy.TimeBasedTriggeringPolicy.modulate | boolean | true|false | 是否使用絕對時(shí)間 | |
| policy.TimeBasedTriggeringPolicy.maxRandomDelay | int | 0 | 觸發(fā)滾動(dòng)時(shí)隨機(jī)延遲N秒,避免多觸發(fā)下造成CPU波峰 | |
| policy.CronTriggeringPolicy.schedule | String | cron表達(dá)式 | ||
| policy.CronTriggeringPolicy.evaluateOnStartup | boolean | 是否啟動(dòng)時(shí)候立即執(zhí)行 | ||
| strategy | RolloverStrategy | 滾動(dòng)執(zhí)行策略,決定怎么進(jìn)行文件滾動(dòng) | ||
| strategy.DefaultRolloverStrategy.fileIndex | String | min|max | max | 備份的文件按時(shí)間降序或升序排號,默認(rèn)升序即編號最大的時(shí)間最近 |
| strategy.DefaultRolloverStrategy.min | int | 1 | 備份文件排號起點(diǎn) | |
| strategy.DefaultRolloverStrategy.max | int | 7 | 備份文件排號最大值,超出最大值時(shí)候?qū)h除時(shí)間最遠(yuǎn)的文件 | |
| strategy.DefaultRolloverStrategy.compressionLevel | int | [0-9] | 0 | 只有當(dāng)filePattern配置后綴為壓縮時(shí)生效,0-不壓縮,1-9表示壓縮率 |
| strategy.DefaultRolloverStrategy.tempCompressedFilePattern | String | 壓縮期間使用的臨時(shí)文件名 | ||
| strategy.DefaultRolloverStrategy.delete | Delete | 執(zhí)行滾動(dòng)時(shí)自定義的刪除行為 | ||
| strategy.DefaultRolloverStrategy.posixViewAttribute | posixViewAttribute | 執(zhí)行滾動(dòng)時(shí)自定義的文件權(quán)限 | ||
| ignoreExceptions | boolean | true|false | true | 是否忽略appender的內(nèi)部異常 |
| filePermissions | String | 創(chuàng)建文件時(shí)賦予的權(quán)限,POSIX格式 | ||
| fileOwner | String | 創(chuàng)建文件時(shí)賦予的用戶 | ||
| fileGroup | String | 創(chuàng)建文件時(shí)賦予的用戶組 |
觸發(fā)策略Policy配置時(shí)類似Filter,可以使用Policies進(jìn)行多項(xiàng)配置,只要任一項(xiàng)Policy滿足條件則觸發(fā)
<Policies>
<OnStartupTriggeringPolicy />
<SizeBasedTriggeringPolicy size="20 MB" />
<TimeBasedTriggeringPolicy />
</Policies>
AsyncAppender
<?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>
| Parameter Name | Type | Default | Description |
|---|---|---|---|
| AppenderRef | String | 關(guān)聯(lián)的appender.name | |
| blocking | boolean | true | 內(nèi)存隊(duì)列滿時(shí)是等待還是寫入errorRef |
| shutdownTimeout | integer | 0 | |
| bufferSize | integer | 1024 | 緩沖大小,單位字節(jié); |
| errorRef | String | 執(zhí)行異常時(shí)候輸出的appender.name | |
| filter | Filter | 同樣可以使用filters進(jìn)行多項(xiàng)組合 | |
| name | String | 唯一標(biāo)識 | |
| ignoreExceptions | boolean | true | 是否忽略內(nèi)部異常 |
| includeLocation | boolean | false | 是否記錄caller location即調(diào)用堆棧 |
| BlockingQueueFactory | BlockingQueueFactory | ArrayBlockingQueue | This element overrides what type of BlockingQueue to use. Seebelow documentation for more details. |
RewriteAppender
重寫log event,主要用于數(shù)據(jù)過濾或脫敏
RoutingAppender
appender重定向,需要注意的是routing必須定義在所有關(guān)聯(lián)appender之后
Logger
- additivity:true|false;是否繼承父類logger,默認(rèn)繼承
- name:string;唯一標(biāo)識,除root logger外都必須配置
- level:log level;日志輸出級別,默認(rèn)為error
- appenderRef:string;關(guān)聯(lián)appender的name
合并配置項(xiàng)
- 使用XInclude
<xi:include href="log4j-xinclude-appenders.xml" />進(jìn)行文件內(nèi)合并 - 使用log4j.configurationFile參數(shù)進(jìn)行跨文件合并:file1,file2
注意事項(xiàng)
- 當(dāng)未提供log4j.configurationFile啟動(dòng)參數(shù)時(shí),將按內(nèi)置優(yōu)先級依次查找配置文件,都未找到的情況下使用默認(rèn)ConsoleAppender且Level設(shè)置為Error
- 調(diào)試log4j2的內(nèi)部日志有二種常用方式:設(shè)置配置文件的status屬性為trace;在啟動(dòng)參數(shù)中加入log4j2.debug(僅支持debug級別);更多l(xiāng)og4j2支持的啟動(dòng)參數(shù)請查閱這里
FAQ
為什么我使用了日志配置文件確依然沒有日志輸出?
答:
- 確認(rèn)是否引入了slf4j的實(shí)現(xiàn)包,比如slf4j-log4j-impl;若沒有,slf4j會(huì)提示無法找到對應(yīng)實(shí)現(xiàn)類,若提供了多個(gè)slf4j實(shí)現(xiàn)包,則同樣會(huì)提示綁定沖突
- 確認(rèn)是否正確提供了日志配置文件;若沒有,log4j會(huì)提示找不到配置文件并啟動(dòng)默認(rèn)配置集(Console + Level.Error)
- 確認(rèn)是否配置了bufferIO及緩沖區(qū);只有緩沖區(qū)滿才會(huì)提交到磁盤IO進(jìn)行寫入操作
- 確認(rèn)是否有磁盤文件創(chuàng)建權(quán)限;可以使用sudo啟動(dòng)或預(yù)先以運(yùn)行用戶的角色建立好日志文件路徑
我添加了依賴slf4j-log4j2-impl,那么我還是否需要額外引入slf4j-api?
答:不需要,slf4j的實(shí)現(xiàn)包具體依賴項(xiàng)以POM文件為準(zhǔn)
分析如下三種函數(shù)使用方式,哪種最優(yōu),好在哪里?
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
if(logger.isDebugEnabled()) {
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
}
logger.debug("Entry number: {} is {}.", i, String.valueOf(entry[i]);
答:第三種,減少字符串的合并操作
在log4j的參數(shù)化日志方法中,分析如下幾種情況的輸出
logger.debug("param-1: {}, param-2: {}, param-3: {}", "1", "2", "3")
logger.info("param-1: \\{}, param-2: {}, param-3: {}", "1", "2", "3")
logger.info("param-1: {}, param-2: {{}}, param-3: {}", "1", "2", "3")
答:
- param-1: 1, param-2: 2, param-3: 3
- param-1: {}, param-2: 1, param-3: 2
- param-1: 1, param-2: {2}, param-3: 3
Logger對象定義為static或variable有什么區(qū)別,適用于哪些場景?JCL及Slf4j是如何解決這個(gè)問題的?

答:static在同容器多應(yīng)用的場景下可能存在引用沖突;JCL默認(rèn)使用MAP來存儲(chǔ)每個(gè)Logger的引用,需要手動(dòng)釋放可能存在使用不當(dāng)導(dǎo)致內(nèi)存泄漏;Slf4j沒有這樣的機(jī)制,是否static完全交由使用者控制
Slf4j為什么沒有FATAL以及TRACE級別?
答:Slf4j的作者設(shè)計(jì)理念,認(rèn)為FATAL類似ERROR,TRACE類似DEBUG,存在概念上的混淆;如果確實(shí)需要標(biāo)記為FATAL或TRACE可以使用Marker + Pattern來實(shí)現(xiàn)
分析以下配置文件最終生成的日志文件將會(huì)是什么樣的?
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
<Appenders>
<RollingFile name="RollingFile" filePattern="logs/app-%d{yyyy-MM-dd-HH}-%i.log.gz">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<Policies>
<CronTriggeringPolicy schedule="0 0 * * * ?"/>
<SizeBasedTriggeringPolicy size="250 MB"/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
答:
資源鏈接
JUL - java.util.logging - ORACLE