一、簡介
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 以及將其安排在層級結構中。
-
命名及層級關係
Logger 名稱區分大小寫,並遵循層級命名規則。
層級關係用 "." 表示,如:"com.foo" 是 "com.foo.Bar" 的父Logger且所有 Logger 都可通過
LoggerFactory#getlogger(String name)
來獲取,且相同名稱返回的例項相同 -
根 Logger
是所有層級結構的頂部Logger,可通過名稱檢索獲取
Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
-
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 -
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 初始化時,根據以下順序嘗試配置:
- 類路徑下嘗試尋找 logback-test.xml
- 若沒有,類路徑下嘗試尋找 logback.groovy
- 若沒有,類路徑下嘗試尋找 logback.xml
- 若沒有,嘗試基於 Java SPI 機制尋找 com.qos.logback.classic.spi.Configurator 介面的實現
- 若以上都沒有,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 整體流程
- 獲取 filter 鏈決策
- 應用 basic selection rule
- 建立 LoggingEvent 物件
- 呼叫 appenders
- 調 Layout 格式化
- 輸出到目的地
四、配置詳解
1.配置檔案
一些寫法/背景知識:
-
可在值中以 ${KEY:-defaultValue} 的形式引入屬性,查詢順序如下:
- 首先在本地作用域查詢(詳見
<property>
) - 若沒有,在上下文作用域查詢
- 若沒有,在JVM系統屬性查詢
- 最後,在系統環境變數中查詢
既支援名稱(KEY)中的巢狀,也支援值/預設值的巢狀引用
- 首先在本地作用域查詢(詳見
-
預定義屬性:
- HOSTNAME:系統hostname,是配置時在 context 域中被定義被定義
- CONTEXT_NAME:上下文名
-
一些標籤涉及到類的標籤(如
<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或多個
- <layout>:0或1個
-
<logger>:0或多個,配置 Logger
- name(必須)
- level:[TRACE | DEBUG | INFO | WARN | ERROR | ALL | OFF],或者 INHERITED / NULL 表示從上級繼承。
- additivity:[true | false]
- 子標籤:
- <appender-ref> :0或多個,表附加appender
- ref:指定先前
的名稱
- ref:指定先前
- <appender-ref> :0或多個,表附加appender
-
<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])
-
一些關鍵詞可使用轉義符
\
來輸出
常用轉換詞
-
日誌事件的訊息: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
-
屬性:property{key}
-
正則替換:replace(p){r,t} 將訊息模式p中的r替換為t
如:%replace(%logger %msg){'\.', '/'}
將所有"."替換為"/" -
MDC(Mapped Diagnostic Contex)欄位:
%X{filedName}
-
自定義轉換詞:
- 自定義實現 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); } }
- 宣告轉換詞
<configuration> <conversionRule conversionWord="nanos" converterClass="chapters.layouts.MySampleConverter" /> <!-- … --> </configuration>
- 自定義實現 ClassicConverter,重寫 convert(ILoggingEvent) 方法
效能考慮,應避免使用的轉換詞
- 類名: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才有意義
五、常見需求
- 根據環境變數動態附加Appender
參考:How to select Logback appender based on property file or environment variable- 使用 logback 官方支援的
語法,但同樣需要引入 Janino library。參考:configuration - 為 Appender 設定 <filter>,並使用環境變數語法動態配置Filter的Level【有些曲線救國,應該仍有效能損耗
- 使用 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>
- 使用 logback 官方支援的
六、踩坑
- 對於巢狀 .xml 配置檔案,預設域"local"重複宣告的話是無效的,不會覆蓋。
比如在我的logback.xml中宣告,無法覆蓋Spring自帶的配置。