Logback 快速入門 / 使用詳解

SimpleIto發表於2021-08-13

官方文件: http://logback.qos.ch/manual/index.html

一、簡介

Java 開源日誌框架,以繼承改善 log4j 為目的而生,是 log4j 創始人 Ceki Gülcü 的開源產品。
它聲稱有極佳的效能,佔用空間更小,且提供其他日誌系統缺失但很有用的特性。
其一大特色是,在 logback-classic 中本地(native)實現了 SLF4J API(也表示依賴 slf4j-api)

二、架構 / Logback知識

1. 專案分為三個模組:

  • logback-core:其他倆模組基礎模組,作為通用模組 其中沒有 logger 的概念
  • logback-classic:日誌模組,完整實現了 SLF4J API
  • logback-access:配合Servlet容器,提供 http 訪問日誌功能

2. 在 logback 中主要概念(Logger、Appender、Layout、Encoder)

· Logger

日誌記錄器,logback-classic 的部分
每個 Logger 都附加到一個 LoggerContext 上,該 Context 負責構造 Logger 以及將其安排在層級結構中。

  1. 命名及層級關係
    Logger 名稱區分大小寫,並遵循層級命名規則。
    層級關係用 "." 表示,如:"com.foo" 是 "com.foo.Bar" 的父Logger

    且所有 Logger 都可通過LoggerFactory#getlogger(String name)來獲取,且相同名稱返回的例項相同

  2. 根 Logger
    是所有層級結構的頂部Logger,可通過名稱檢索獲取
    Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);

  3. Level 與 繼承關係
    Logger 可以被分配級別(TRACE、DEBUG、INFO、WARN、ERROR),可在 ch.qos.logback.classic 中檢視
    若沒有給 Logger 分配級別,則它將從最近的分配了等級的祖先處繼承等級
    例:

    Logger name Assigned level Effective level
    root DEBUG DEBUG
    X INFO INFO
    X.Y none INFO
    X.Y.Z ERROR ERROR
  4. Level 與 log 規則(basic selection rule)
    Logger 只會啟用等級 ≥ Logger等級的日誌請求
    等級排序(嚴重程度,而非優先順序):TRACE < DEBUG < INFO < WARN < ERROR
    如:

    Logger logger = LoggerFactory.getLogger("com.foo");
    logger.setLevel(Level. INFO);
    
    // 啟用,因為 WARN >= INFO
    logger.warn("Low fuel level.");
    // 禁用, 因為 DEBUG < INFO. 
    logger.debug("Starting search for nearest gas station.");
    

· Appender

Logback 通過Appender#doAppend(E event)將日誌事件列印到目的地(允許附加多個Appender即即多個目的地
目前官方已提供 console、檔案、遠端socket服務、JMS、遠端UNIX Syslog程式、MySQL/PostgreSQL/Orcale等資料庫的 appender 支援.
注意:

  • 一個 Logger 可以通過Logger#addAppender方法可被附加多個 appender
  • appender 同樣適用於繼承結構,且是以追加的方式而非覆蓋;
    但繼承行為可被 Logger 的 additivity 標誌影響是否繼承(通過Logger#setAdditive設定)
  • additivity 標誌本身也是可繼承的

繼承示例如下:

Logger Name Attached Appenders Additivity Flag Output Targets
root A1 not applicable A1
x A-x1, A-x2 true A1, A-x1, A-x2
x.y none true A1, A-x1, A-x2
x.y.z A-xyz1 true A1, A-x1, A-x2, A-xyz1
security A-sec false A-sec
security.access none true A-sec

· Layout

表示日誌輸出格式,其通過介面ch.qos.logback.core#doLayout(E event): String將日誌事件格式化為String 返回。
Logback 官方提供 PatternLayout,允許以類似c語言printf來指定輸出格式。
可通過將 layout 與 appender 關聯,來實現自定義輸出格式和目的地;但並不是每個 appender 都需要 layout,比如負責序列化的 SocketAppender 自然不需要 doLayout 轉字串

· Encoder

Encoder的概念在 Logback 0.9.19 中被引入,通過Encoder#encode可將 LoggingEvent 轉為 byte[]
目前 Logback 僅提供了 PatternLayoutEncoder 這一個可用的實現,其邏輯很簡單:內部構造/包裝了 PatternLayout 例項,呼叫PatternLayout#doLayout得到格式化字串後,再呼叫String#getBytes返回 byte[]

引入原因:因為Layout#doLayout介面只能將 LoggingEvent 轉為 String,這在某些情況下不太靈活,而現在 Encoder 能完全控制位元組格式。
比如,在以前的版本中常在FileAppender中巢狀PatternLayout來使用,但現在僅需依賴Encoder

三、使用

1.logback 配置

Logback 初始化時,根據以下順序嘗試配置:

  1. 類路徑下嘗試尋找 logback-test.xml
  2. 若沒有,類路徑下嘗試尋找 logback.groovy
  3. 若沒有,類路徑下嘗試尋找 logback.xml
  4. 若沒有,嘗試基於 Java SPI 機制尋找 com.qos.logback.classic.spi.Configurator 介面的實現
  5. 若以上都沒有,Logback 會使用最基本的 BasicConfigurator 配置自己。
    這將使用 TTLLLayout(類似 PatternLayout) 以"%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"模式格式化日誌,並將 ConsoleAppender 附加到 root Logger,這會輸出到控制檯,且 root 被指定為 DEBUG 等級。
    預設配置等效為以下xml配置:
    <configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <!-- encoders are assigned the type
    ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
    <encoder>
    <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
    </appender>
    
    <root level="debug">
    <appender-ref ref="STDOUT" />
    </root>
    </configuration>
    

可通過以下程式碼列印當前配置:StatusPrinter.print((LoggerContext) LoggerFactory.getILoggerFactory())
輸出:

15:25:46,635 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
15:25:46,635 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
15:25:46,636 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.xml]
15:25:46,647 |-INFO in ch.qos.logback.classic.BasicConfigurator@5f16132a - Setting up default configuration.

2.log 整體流程

  1. 獲取 filter 鏈決策
  2. 應用 basic selection rule
  3. 建立 LoggingEvent 物件
  4. 呼叫 appenders
  5. 調 Layout 格式化
  6. 輸出到目的地

四、配置詳解

1.配置檔案

一些寫法/背景知識:

  1. 可在值中以 ${KEY:-defaultValue} 的形式引入屬性,查詢順序如下:

    • 首先在本地作用域查詢(詳見<property>
    • 若沒有,在上下文作用域查詢
    • 若沒有,在JVM系統屬性查詢
    • 最後,在系統環境變數中查詢

    既支援名稱(KEY)中的巢狀,也支援值/預設值的巢狀引用

  2. 預定義屬性:

    • HOSTNAME:系統hostname,是配置時在 context 域中被定義被定義
    • CONTEXT_NAME:上下文名
  3. 一些標籤涉及到類的標籤(如<definer>、<appender>、<encoder>),其子標籤除了規定之外,還可定義與 JavaBean 屬性同名,這將呼叫相應setter注入。

頂級標籤

<configuration>
屬性:

  • debug:獲知 Logback 內部狀態,官方推薦在<configuration>上設定debug屬性,而非在程式碼中呼叫 StatusPrinter。例:<configuration debug="true">
    這也等同於設定狀態監聽器 OnConsoleStatusListener
    例:
    <configuration>
    	<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />  
    	<!-- … -->
    </configuration>
    
  • scan / scanPeriod:可定期掃描配置檔案的更改,並在更改時自動應用。預設每分鐘一次
    例:<configuration scan="true" scanPeriod="30 seconds" >
  • packagingData:可令每一行StackTrace輸出其對應jar包。需注意,其計算成本高昂
    例:<configuration packagingData="true">
    輸出示例:
    14:28:48.835 [btpool0-7] INFO  c.q.l.demo.prime.PrimeAction - 99 is not a valid value
    java.lang.Exception: 99 is invalid
    at ch.qos.logback.demo.prime.PrimeAction.execute(PrimeAction.java:28) [classes/:na]
    at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:431) [struts-1.2.9.jar:1.2.9]
    at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:236) [struts-1.2.9.jar:1.2.9]
    at org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:432) [struts-1.2.9.jar:1.2.9]
    

<configuration>子標籤

  • <include>:從另外的檔案引入配置,目標檔案必須將其標籤放入<included>下。(SpringBoot基礎配置便是如此,可參考spring-boot:2.4.0包中base.xml)

    • file
    • resource
    • url
    • optional:[true | false],預設若找不到目標檔案,將輸出警告;可設定為可選避免該行為
  • <contextName>:設定上下文名稱,可作為區分不同多個應用程式到同一target的區分,預設為"default"
    示例:

<contextName>myAppName</contextName>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
	<encoder>
	<pattern>%d %contextName [%t] %level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
  • <property>:配置或引入外部屬性,別名

    • name
    • value
    • file:引用檔案系統的 .properties 檔案
    • resource:引用類路徑上的 variables.properties 檔案
    • scope:
      • local:預設,會在配置解釋時定義,並在結束後清除。
      • context:上下文作用域,屬性將插入到LoggerContext中直到LoggerContext被清除,因此在所有日誌事件中都可用。
      • system:系統作用域,屬性將插入到JVM系統屬性中
  • <define>:程式碼動態定義屬性,以 PropertyDefiner#getPropertyValue 方法返回其值

    • name
    • class:PropertyDefiner實現全類名
  • <conversionRule>:自定義轉換詞

    • conversionWord:轉換詞
    • converterClass:ClassicConverter 實現類全類名
  • <appender>

    • *name:宣告Appender名稱
    • *class:要使用的Appender的完全限定名
    • <appender>、<layout>子標籤:
      • <layout>:0或1個
        • class:若未宣告,表示 PatternLayout
      • <encoder>:0或多個
        • class:若未宣告,表示 PatternLayoutEncoder
      • <filter>:0或多個
  • <logger>:0或多個,配置 Logger

    • name(必須)
    • level:[TRACE | DEBUG | INFO | WARN | ERROR | ALL | OFF],或者 INHERITED / NULL 表示從上級繼承。
    • additivity:[true | false]
    • 子標籤:
      • <appender-ref> :0或多個,表附加appender
        • ref:指定先前的名稱
  • <root>:配置根Logger

    • level:根Logger級別不能設定為 INHERITED / NULL
    • 子標籤:
      • <appender-ref>

2.JVM系統屬性

  • logback.configurationFile:指定配置檔案位置,可以是 URL、類路徑資源或外部檔案系統。也可以在程式碼中設定(System#setProperty),但必須在建立任意Logger例項之前。
    副檔名必須是 .xml 或 .groovy
  • logback.statusListenerClass:可設定為希望註冊的 StatusListener 名稱

3. PatternLayout 的格式化訊息編寫規則

基礎規則

  • 轉換符 '%' 可後跟規定的word,來獲取資料欄位,如 logger名稱,日期,執行緒名等

  • 轉換符可後接格式修飾符(Format modifiers)來指定每個欄位最小、最大寬度 以及 是否左對齊
    示例:

    寫法 左對齊 最小寬度 最大寬度
    %20logger false 20 none
    %.30logger NA none 30
    %-20.30logger true 20 30
  • 括號"()" 可將部分內容分組,常配合格式修飾符使用
    如:
    %d{HH:mm:ss.SSS} [%thread]

    輸出:
    13:09:30 [main] DEBUG c.q.logback.demo.ContextListener - Trying platform Mbean server
    13:09:30 [pool-1-thread-1] INFO  ch.qos.logback.demo.LoggingTask - Howdydy-diddly-ho - 0
    

    %-30(%d{HH:mm:ss.SSS} [%thread])
    image

  • 一些關鍵詞可使用轉義符\來輸出

常用轉換詞

  • 日誌事件的訊息:m / msg / messages

  • 日誌級別:p / le / level

  • Logger 名
    語法:

    c{length}
    lo{length}
    logger{length}
    

    含義/示例: length表示推薦最大長度,logback會根據該長度適當縮寫Logger名,但始終會保證最右段不會縮寫

    示例 Logger原名稱 列印結果
    %logger mainPackage.sub.sample.Bar mainPackage.sub.sample.Bar
    %logger{0} mainPackage.sub.sample.Bar Bar
    %logger{5} mainPackage.sub.sample.Bar m.s.s.Bar
    %logger{16} mainPackage.sub.sample.Bar m.sub.sample.Bar
  • 上下文名:contextName

  • 日誌事件的執行緒名: t / thread

  • 從"應用啟動"到"產生該日誌事件"經過的毫秒數:r / relative

  • 日期
    語法:

    d{pattern}
    date{pattern}
    d{pattern, timezone}
    date{pattern, timezone}
    

    含義/示例:輸出日誌事件的日期,其 pattern 語法與 java.text.SimpleDateFormat 格式相容
    在沒有 timezone 的情況下,使用 Java 平臺預設時區

    示例 結果
    %d 2006-10-20 14:06:49,812
    %date{HH:mm:ss.SSS} 14:06:49.812
    %date{dd MMM yyyy;HH:mm:ss.SSS} 20 oct. 2006;14:06:49.812
    %date{HH:mm:ss.SSS, Australia/Perth}
  • 換行符:n

  • 異常:
    語法:

    ex{depth}
    exception{depth}
    throwable{depth}
    
    ex{depth, evaluator-1, ..., evaluator-n}
    exception{depth, evaluator-1, ..., evaluator-n}
    throwable{depth, evaluator-1, ..., evaluator-n}
    

    含義/示例:輸出與日誌事件關聯的異常的stacktrace資訊,預設輸出完整的stacktrace
    image

  • 屬性:property{key}

  • 正則替換:replace(p){r,t} 將訊息模式p中的r替換為t
    如:%replace(%logger %msg){'\.', '/'}將所有"."替換為"/"

  • MDC(Mapped Diagnostic Contex)欄位:%X{filedName}

  • 自定義轉換詞:

    1. 自定義實現 ClassicConverter,重寫 convert(ILoggingEvent) 方法
      public class MySampleConverter extends ClassicConverter {
      	long start = System.nanoTime();
      
      	@Override
      	public String convert(ILoggingEvent event) {
      		long nowInNanos = System.nanoTime();
      		return Long.toString(nowInNanos-start);
      	}
      }
      
    2. 宣告轉換詞
      <configuration>
      	<conversionRule conversionWord="nanos" converterClass="chapters.layouts.MySampleConverter" />
      	<!-- … -->
      </configuration>
      

效能考慮,應避免使用的轉換詞

  • 類名:C{length} / class{length}
  • 檔名:F / file
  • 行號:L / line
  • 方法名:M / method
  • 包含jar包資訊的異常stacktrace:xEx{depth} / xException{depth}…
    及root異常順序反轉的 rEx{depth} / rootException{depth}…

4. 著色(Coloring)

基於ANSI顏色程式碼,Logback可使用顏色關鍵詞來為分組設定顏色。內建了:"%highlight","%black", "%red", "%green","%yellow","%blue", "%magenta","%cyan", "%white", "%gray", "%boldRed","%boldGreen", "%boldYellow", "%boldBlue", "%boldMagenta""%boldCyan", "%boldWhite"
例:
<pattern>%d ${HOSTNAME} [%t] %highlight(%level) %logger{36} - %msg%n %ex{full}</pattern>

踩坑:官方說明使用 jansi 可以做到相容不相容ANSI的終端,但 Windows 下始終報錯,即便引入了推薦版本 仍然報錯

<dependency>
	<groupId>org.fusesource.jansi</groupId>
	<artifactId>jansi</artifactId>
	<version>1.17</version>
</dependency>

5.非同步Appender

可通過宣告AsyncAppender來執行非同步日誌列印,它只進行日誌事件分發,因此必須引用另一個實際Appender才有意義

五、常見需求

  1. 根據環境變數動態附加Appender
    參考:How to select Logback appender based on property file or environment variable
    1. 使用 logback 官方支援的 語法,但同樣需要引入 Janino library。參考:configuration
    2. Appender 設定 <filter>,並使用環境變數語法動態配置Filter的Level【有些曲線救國,應該仍有效能損耗
    3. 使用 SpringProfile
      例:
      application.yaml
      spring:
        profiles:
      	active:
      	  - ${ENV:classiclogs}
      
      logback.xml
      <configuration>
      	<!-- appender conf…-->
      	<springProfile name="jsonlogs">
      		<root level="info">
      			<appender-ref ref="stdout-json" />
      		</root>
      	</springProfile>
      	<springProfile name="classiclogs">
      		<root level="info">
      			<appender-ref ref="stdout-classic" />
      		</root>
      	</springProfile>
      </configuration>
      

六、踩坑

  1. 對於巢狀 .xml 配置檔案,預設域"local"重複宣告的話是無效的,不會覆蓋。
    比如在我的logback.xml中宣告,無法覆蓋Spring自帶的配置。

撰文參考:
http://logback.qos.ch/manual/index.html

相關文章