使用 SLF4J 進行參數化記錄
1. 概述
日誌記錄是軟件開發的重要組成部分,它使我們能夠深入了解應用程序的行為。在本教程中,我們將回顧一個稱為Parameterized logging
的重要日誌記錄功能。通過利用參數化日誌記錄,我們可以提高日誌的全面性和效率。
Simple Logging Facade for Java (SLF4J) 是一個眾所周知的日誌庫,它提供了統一的日誌抽象。它允許開發人員使用單個 API 並插入任何兼容的日誌記錄框架,例如 Logback、log4j 或 SLF4J 簡單記錄器。 SLF4J API 實際上並不記錄日誌,我們可以在部署時插入我們想要的任何日誌框架。
2.Maven依賴
在深入了解日誌記錄本身之前,讓我們配置所需的依賴項。通常,我們需要包含兩個依賴項: slf4j-api
,它將提供統一的外觀,以及一個將執行日誌記錄的記錄器實現。在我們的示例中,我們將使用 Logback 作為記錄器實現,在這裡,我們可以採用不同的方法。我們只需要包含一個已經使用slf4j-api
logback-classic
依賴項。
讓我們將logback-classic
依賴項添加到 Maven pom.xml
中:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.8</version>
</dependency>
我們可以在 Maven 中央存儲庫中找到單獨的slf4j-api
和logback-classic
的最新版本。
3. 記錄器初始化
第一步是初始化我們的記錄器。根據項目設置,這可以手動或通過Lombok
完成。讓我們檢查一下這兩個變體。
手動初始化應始終使用org.slf4j
包中的Logger
和LoggerFactory
:
public static final Logger log = LoggerFactory.getLogger(LoggingPlayground.class);
通過在類級別使用@Slf4j
註釋, Lombok
將生成與上面手動初始化相同的代碼行。
為了保持一致並為可能遷移到Lombok,
我們可以為所有手動初始化的記錄器使用log
名稱。
4. 參數化日誌記錄
從術語的角度來看,參數化日誌記錄是指將提供的參數注入到日誌消息中的過程。過去,舊版本的庫並不總是提供具有多個值的參數化日誌記錄的統一方法。這就是為什麼我們可以看到純字符串連接、 String.format()
和其他技巧的使用。這些技術不再是必要的,我們可以在消息中使用大括號{}
來使用任意數量的參數。
我們只能記錄一個參數:
log.info("App is running at {}", LocalDateTime.now());
我們也可以記錄多個參數,並且佔位符將按順序豐富。只需記住確保花括號的數量與我們傳遞的參數數量相匹配。值得慶幸的是,大多數 IDE 都會突出顯示此類不匹配的情況。
以下是如何記錄多個參數的示例:
log.info("App is running at {}, zone = {}, java version = {}, java vm = {}", LocalDateTime.now(), ZonedDateTime.now()
.getZone(), System.getProperty("java.version"), System.getProperty("java.vm.name"));
上述代碼的輸出將是:
15:41:48.749 [main] INFO cbplogging.LoggingPlayground - App is running at 2023-07-20T15:41:48.749435, zone = Europe/Helsinki, java version = 11.0.15, java vm = Java HotSpot(TM) 64-Bit Server VM
當庫不支持多個參數時,這種情況的常見方法之一是使用Object[]
記錄數據,但不應將其與較新的版本一起使用:
log.info("App is running at {}, zone = {}, java version = {}, java vm = {}",
new Object[] { ZonedDateTime.now(), ZonedDateTime.now().getZone(), System.getProperty("java.version"), System.getProperty("java.vm.name") });
輸出將與四個單獨對象的輸出相同。
5. 流暢的日誌記錄
從 SLF4J 2.0 開始,Fluent Logging 提供了另一種與現有框架向後兼容的方法。 Fluent 提供了一個構建器 API 來逐步構建日誌記錄事件。因此,我們也可以使用此功能來實現參數化日誌記錄。每個日誌級別都有專用的構建器。每個構建器創建都應包含一個log()
調用以實際打印消息。
例如,我們可以使用addArgument()
方法並向消息中的每個佔位符添加參數值:
log.atInfo().setMessage("App is running at {}, zone = {}")
.addArgument(LocalDateTime.now())
.addArgument(ZonedDateTime.now().getZone())
.log();
我們的輸出與非 Fluent 方法相同:
15:50:20.724 [main] INFO cbplFluentLoggingPlayground - App is running at 2023-07-20T15:50:20.724532900, zone = Europe/Helsinki
或者,我們可以使用addKeyValue()
並指定參數名稱及其值:
log.atInfo().setMessage("App is running at")
.addKeyValue("time", LocalDateTime.now())
.addKeyValue("zone", ZonedDateTime.now().getZone())
.setCause(exceptionCause)
.log();
為了使用addKeyValue()
方法,我們的記錄器配置應該能夠接受它。對於Logback
,我們應該更新日誌格式以包含%kvp
佔位符。如果未指定,則所有添加的數據將被忽略:
<appender name="out" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg %kvp%n</pattern>
</encoder>
</appender>
使用鍵值方法,我們的輸出與參數值略有不同:
15:52:35.835 [main] INFO cbplFluentLoggingPlayground - App is running at time="2023-07-20T15:52:35.834338500" zone="Europe/Helsinki"
6.帶有異常日誌記錄的參數化日誌記錄
經常出現的一個問題是,考慮到聲明的方法提供了傳遞參數或異常的能力,如何記錄帶有異常的多個參數。從 SLF4J 1.6 開始,
這個問題得到了解決,我們可以將參數化日誌記錄與異常日誌記錄結合起來。
默認情況下,SLF4J 將使用最新參數作為Throwable
的候選參數。如果提供的參數出現異常,SLF4J 將在日誌輸出中打印完整的堆棧跟踪。
例如,對於給定的日誌行:
log.info("App is running at {}, zone = {}, java version = {}, java vm = {}", LocalDateTime.now(), ZonedDateTime.now()
.getZone(), System.getProperty("java.version"), System.getProperty("java.vm.name"), exceptionCause);
輸出將是:
15:54:43.771 [main] INFO cbplogging.LoggingPlayground - App is running at 2023-07-20T15:54:43.771587300, zone = Europe/Helsinki, java version = 11.0.15, java vm = Java HotSpot(TM) 64-Bit Server VM
java.lang.Exception: java.lang.IllegalArgumentException: Something unprocessable
at com.baeldung.parameterized.logging.LoggingPlayground.main(LoggingPlayground.java:30)
Caused by: java.lang.IllegalArgumentException: Something unprocessable
... 1 common frames omitted
如果我們在其他參數之間傳遞Throwable
,它將被視為常規對象,並且不會打印堆棧跟踪。
Throwable
也可以通過setCause()
方法使用 Fluent 方法指定:
log.atInfo()
.setMessage("App is running at {}, zone = {}")
.addArgument(LocalDateTime.now())
.addArgument(ZonedDateTime.now().getZone())
.setCause(exceptionCause)
.log();
七、結論
在本文中,我們回顧瞭如何使用參數化日誌記錄來記錄多個參數,並探索了 Fluent 日誌記錄方法以獲得更大的靈活性。此外,我們還探討瞭如何將參數化日誌記錄與異常結合起來。
完整的示例可以在 GitHub 上找到。