Java中的日志框架

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)行檢測提示

解決的問題

without slf4j
with slf4j

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

slf4j application

Log4j2

性能對比

log4j2 benchmark 1
log4j2 benchmark 2

基礎(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)圖解

log4j 2

常用配置項(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,引用通過{name};除此之外在引用時(shí)還可以通過特定的前綴來指定引用的值的格式`{prefix:name},如{base64:SGVsbG8gV29ybGQhCg==}`等價(jià)于`Hello World!`,`{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 可能產(chǎn)生的混亂

答: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

JUL Tutorials - vogella.com

StackOverflow - Why NOT JUL?

JCL - Apache Commons Logging

Slf4j - Simple Logging Facade for Java

Slf4j - 如何正確的排除依賴包中的日志框架

Slf4j - 如何提高日志的性能

Logger - Static or Not?

Log4j 1.x to Log4j 2.x

Log4j 2 Benchmarks

Log4j 2 Pattern Layout

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容