Java中的日志框架
常見日志框架
JUL
|Log4j1
|Log4j2
|Logback
|JCL
|Slf4j
日志框架需要解決的問題
- 穩(wěn)定性高:不可影響主進程的正常運行且自身的日志內容準確無歧義
- 擴展性高:對于不同的日志輸出需求,有便捷的方式進行自定義擴展
- 開銷低:不可占用服務器的過多資源舶担,影響主進程的執(zhí)行速度
- 延遲低:對應的日志內容輸出不可延遲太久羞迷,否則失去觀察的意義
框架對比
框架 | 版本 | 優(yōu)點 | 缺點 | 作者 | 備注 |
---|---|---|---|---|---|
JUL | @since JDK 1.4 | JDK自帶日志工具類莱坎,無需額外依賴 | 功能單一 | Oracle | java.util.logging拇厢;提供了基礎的Handler(Appender)椭岩,F(xiàn)ormatter(Pattern)等組件化功能 |
Logback | 2006-2018 | 優(yōu)化了Log4j1中的缺陷及不足竟趾;配合Slf4j使用時不需要引入適配層 | 重載配置文件時可能丟失日志 | QOS.ch | 出現(xiàn)時間介于Log4j1與Log4j2冯事,是基于Slf4j標準的原生實現(xiàn),相比其他框架不需要引入適配層 |
Log4j1 | 1999-2012 | 標準的日志接口;模塊化的設計理念辕录; | 多線程下可能存在死鎖 | Apache | 2015年被Apache聲明不再維護睦霎,最后版本為2012年發(fā)布的log4j 1.2.17 |
Log4j2 | 2012-2019 | 更少的內存占用;更高的并發(fā)性能走诞;更完善的使用手冊 | 特性繁多副女,完全掌握需要一定學習成本 | Apache | 在1的版本上完全重寫,基于LMAX Disruptor庫使得并發(fā)性能大幅提升 |
JCL | 2005-2014 | Apache 官方項目 | 使用不當易存在內存泄漏 | Apache | Apache Commons Logging蚣旱;日志抽象接口層碑幅,最新版截止2014年;因設計理念及使用方式導致在某些情況下存在內存泄漏的問題 |
Slf4j | 2009-2019 >=1.6.0 | 易用塞绿,單jar包沟涨,使用范圍廣 | QOS.ch | Simple Logging Facade for Java日志抽象接口層 |
Slf4j
- 保證了項目內日志框架升級的便捷性,項目間日志框架的一致性
- 利用
Bridging legacy logging APIs
實現(xiàn)已有JCL异吻、JUL裹赴、Log4j多項目的歸并統(tǒng)一 - 參數(shù)化日志打印
- 無綁定、多綁定涧黄、版本異常等可以在加載期進行檢測提示
解決的問題
調用關系鏈
Log4j2
性能對比
基礎概念
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預定義了多種用途的Appender如Console Appender,F(xiàn)ile Appender笋妥,Http Appender等,其中Appender按執(zhí)行層級又可以分為二種:普通Appender與引用Appender窄潭,引用Appender即自身并不實現(xiàn)具體的輸出而是對普通Appender進行了一層包裝來實現(xiàn)異步春宣、過濾、轉發(fā)等目的
Logger
具體的日志對象嫉你,一個Logger對象可以包含[0, n)個Appender來同時輸出到不同流月帝;同時Logger對象還包含一些管理信息如Log Level及Log Filter等
結構圖解
常用配置項
<?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內部代碼日志級別幽污,調試時可設置為trace
- monitorInterval:配置變更檢測間隔嚷辅,單位秒;注意只有當monitorInterval之后并有新的logEvent時才會真正觸發(fā)reconfiguration
- dest:err|out|file|URL距误;log4j2內部代碼輸出流簸搞,對于不方便直接查看的環(huán)境可以導出調試信息至文件
Properties
類似POM文件中的Properties,定義通過kvp准潭,引用通過{prefix:name}
,如
{sys:some.property:-default_value}
表示取名為
some.property的系統(tǒng)參數(shù)寺擂,當不存在時使用
default_value`進行替換
Filter
Log Event過濾器,每個過濾器有三種返回結果Accept
|Deny
|Neutral
,分別表示直接接受怔软,直接拒絕垦细,向下傳遞;根據(jù)Filter的作用域又可以分為以下三種
- 全局Filter挡逼,配置節(jié)點與Properties\Appenders\Loggers同級
- Logger Filter括改,位于Logger中,針對某個具體的Logger進行過濾
- Appender Filter挚瘟,位于Appender中叹谁,針對某個具體的Appender進行過濾
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 | 過濾器乘盖,多個filter應使用filters標簽 | ||
fileName | String | 日志路徑焰檩,若不存在則自動創(chuàng)建 | ||
filePattern | String | 滾動文件名,支持占位符及自動壓縮 | ||
immediateFlush | boolean | true|false | true | 是否立即寫入磁盤 |
layout | Layout | %m%n | 日志內容格式订框,參考Pattern Layout | |
name | String | 同配置集中析苫,Appender的name必須唯一 | ||
policy | TriggeringPolicy | 滾動觸發(fā)策略,決定何時進行文件滾動 | ||
policy.OnStartupTriggeringPolicy.minSize | long | 1 | 滾動文件大小最小值 | |
policy.SizeBasedTriggeringPolicy.size | String | 20KB|MB|GB | filePattern中必須包含%i項穿扳,否則會導致文件直接被覆蓋 | |
policy.TimeBasedTriggeringPolicy.interval | int | 1 | 基于filePattern中的日期精度單位觸發(fā)滾動 | |
policy.TimeBasedTriggeringPolicy.modulate | boolean | true|false | 是否使用絕對時間 | |
policy.TimeBasedTriggeringPolicy.maxRandomDelay | int | 0 | 觸發(fā)滾動時隨機延遲N秒衩侥,避免多觸發(fā)下造成CPU波峰 | |
policy.CronTriggeringPolicy.schedule | String | cron表達式 | ||
policy.CronTriggeringPolicy.evaluateOnStartup | boolean | 是否啟動時候立即執(zhí)行 | ||
strategy | RolloverStrategy | 滾動執(zhí)行策略,決定怎么進行文件滾動 | ||
strategy.DefaultRolloverStrategy.fileIndex | String | min|max | max | 備份的文件按時間降序或升序排號矛物,默認升序即編號最大的時間最近 |
strategy.DefaultRolloverStrategy.min | int | 1 | 備份文件排號起點 | |
strategy.DefaultRolloverStrategy.max | int | 7 | 備份文件排號最大值茫死,超出最大值時候將刪除時間最遠的文件 | |
strategy.DefaultRolloverStrategy.compressionLevel | int | [0-9] | 0 | 只有當filePattern配置后綴為壓縮時生效,0-不壓縮履羞,1-9表示壓縮率 |
strategy.DefaultRolloverStrategy.tempCompressedFilePattern | String | 壓縮期間使用的臨時文件名 | ||
strategy.DefaultRolloverStrategy.delete | Delete | 執(zhí)行滾動時自定義的刪除行為 | ||
strategy.DefaultRolloverStrategy.posixViewAttribute | posixViewAttribute | 執(zhí)行滾動時自定義的文件權限 | ||
ignoreExceptions | boolean | true|false | true | 是否忽略appender的內部異常 |
filePermissions | String | 創(chuàng)建文件時賦予的權限峦萎,POSIX格式 | ||
fileOwner | String | 創(chuàng)建文件時賦予的用戶 | ||
fileGroup | String | 創(chuàng)建文件時賦予的用戶組 |
觸發(fā)策略Policy配置時類似Filter,可以使用Policies進行多項配置忆首,只要任一項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 | 關聯(lián)的appender.name | |
blocking | boolean | true | 內存隊列滿時是等待還是寫入errorRef |
shutdownTimeout | integer | 0 | |
bufferSize | integer | 1024 | 緩沖大小爱榔,單位字節(jié); |
errorRef | String | 執(zhí)行異常時候輸出的appender.name | |
filter | Filter | 同樣可以使用filters進行多項組合 | |
name | String | 唯一標識 | |
ignoreExceptions | boolean | true | 是否忽略內部異常 |
includeLocation | boolean | false | 是否記錄caller location即調用堆棧 |
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必須定義在所有關聯(lián)appender之后
Logger
- additivity:true|false;是否繼承父類logger浸锨,默認繼承
- name:string唇聘;唯一標識,除root logger外都必須配置
- level:log level揣钦;日志輸出級別雳灾,默認為error
- appenderRef:string;關聯(lián)appender的name
合并配置項
- 使用XInclude
<xi:include href="log4j-xinclude-appenders.xml" />
進行文件內合并 - 使用log4j.configurationFile參數(shù)進行跨文件合并:file1,file2
注意事項
- 當未提供log4j.configurationFile啟動參數(shù)時冯凹,將按內置優(yōu)先級依次查找配置文件谎亩,都未找到的情況下使用默認ConsoleAppender且Level設置為Error
- 調試log4j2的內部日志有二種常用方式:設置配置文件的status屬性為trace炒嘲;在啟動參數(shù)中加入log4j2.debug(僅支持debug級別);更多l(xiāng)og4j2支持的啟動參數(shù)請查閱這里
FAQ
為什么我使用了日志配置文件確依然沒有日志輸出匈庭?
答:
- 確認是否引入了slf4j的實現(xiàn)包夫凸,比如slf4j-log4j-impl;若沒有阱持,slf4j會提示無法找到對應實現(xiàn)類夭拌,若提供了多個slf4j實現(xiàn)包,則同樣會提示綁定沖突
- 確認是否正確提供了日志配置文件衷咽;若沒有鸽扁,log4j會提示找不到配置文件并啟動默認配置集(Console + Level.Error)
- 確認是否配置了bufferIO及緩沖區(qū);只有緩沖區(qū)滿才會提交到磁盤IO進行寫入操作
- 確認是否有磁盤文件創(chuàng)建權限镶骗;可以使用sudo啟動或預先以運行用戶的角色建立好日志文件路徑
我添加了依賴slf4j-log4j2-impl桶现,那么我還是否需要額外引入slf4j-api?
答:不需要鼎姊,slf4j的實現(xiàn)包具體依賴項以POM文件為準
分析如下三種函數(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是如何解決這個問題的?
答:static在同容器多應用的場景下可能存在引用沖突佳励;JCL默認使用MAP來存儲每個Logger的引用页藻,需要手動釋放可能存在使用不當導致內存泄漏;Slf4j沒有這樣的機制植兰,是否static完全交由使用者控制
Slf4j為什么沒有FATAL以及TRACE級別?
答:Slf4j的作者設計理念璃吧,認為FATAL類似ERROR楣导,TRACE類似DEBUG,存在概念上的混淆畜挨;如果確實需要標記為FATAL或TRACE可以使用Marker + Pattern來實現(xiàn)
分析以下配置文件最終生成的日志文件將會是什么樣的筒繁?
<?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