Spring Boot 3.4 中的結構化日誌

工程 | 莫里茨·哈爾布里特 | 2024年8月23日 | ...

日誌是解決應用程式問題中一個由來已久的組成部分,也是可觀測性的三大支柱之一,與指標和跟蹤並列。沒有人喜歡在生產環境中盲目操作,當發生事故時,開發人員很樂意擁有日誌檔案。日誌通常以人類可讀的格式輸出。

結構化日誌是一種將日誌輸出以明確定義的、通常是機器可讀的格式編寫的技術。這種格式可以被饋送到日誌管理系統,並實現強大的搜尋和分析功能。JSON 是結構化日誌最常用的格式之一。

藉助 Spring Boot 3.4,開箱即用地支援結構化日誌記錄。它支援 Elastic Common Schema (ECS)Logstash 格式,但也可以擴充套件以支援您自己的格式。

讓我們直接開始,看看它是如何工作的!

結構化日誌 Hello World

start.spring.io 上建立一個新專案。您無需新增任何依賴項,但請確保至少選擇 Spring Boot 3.4.0-M2。

要在控制檯上啟用結構化日誌記錄,請將此新增到您的 application.properties 檔案中

logging.structured.format.console=ecs

這將指示 Spring Boot 以 Elastic Common Schema (ECS) 格式進行日誌記錄。

現在啟動應用程式,您將看到日誌已格式化為 JSON

{"@timestamp":"2024-07-30T08:41:10.561295200Z","log.level":"INFO","process.pid":67455,"process.thread.name":"main","service.name":"structured-logging-demo","log.logger":"com.example.structured_logging_demo.StructuredLoggingDemoApplication","message":"Started StructuredLoggingDemoApplication in 0.329 seconds (process running for 0.486)","ecs.version":"8.11"}

很酷,對吧?現在讓我們深入探討更高階的主題。

結構化日誌到檔案

您還可以啟用結構化日誌到檔案。例如,這可以用於在控制檯上列印人類可讀的日誌,並將結構化日誌寫入檔案以供機器處理。

要啟用此功能,請將此新增到您的 application.properties 檔案中,並確保刪除 logging.structured.format.console=ecs 設定

logging.structured.format.file=ecs
logging.file.name=log.json

現在啟動您的應用程式,您將看到控制檯上有人類可讀的日誌,而 log.json 檔案包含機器可讀的 JSON 內容。

新增額外欄位

結構化日誌的一個強大功能是開發人員可以以結構化的方式將資訊新增到日誌事件中。例如,您可以將使用者 ID 新增到每個日誌事件中,然後稍後按該 ID 進行過濾,以瞭解該特定使用者所做的操作。

Elastic Common Schema 和 Logstash 都包含 Mapped Diagnostic Context 的內容在 JSON 中。為了在實踐中看到這一點,讓我們建立自己的日誌訊息

@Component
class MyLogger implements CommandLineRunner {
    private static final Logger LOGGER = LoggerFactory.getLogger(MyLogger.class);

    @Override
    public void run(String... args) {
        MDC.put("userId", "1");
        LOGGER.info("Hello structured logging!");
        MDC.remove("userId");
    }
}

在記錄日誌訊息之前,此程式碼還將使用者 ID 設定在 MDC 中。Spring Boot 會自動將使用者 ID 包含在 JSON 中

{ ... ,"message":"Hello structured logging!","userId":"1" ... }

您還可以使用 fluent logging API 在不依賴 MDC 的情況下新增其他欄位

@Component
class MyLogger implements CommandLineRunner {

    private static final Logger LOGGER = LoggerFactory.getLogger(MyLogger.class);

    @Override
    public void run(String... args) {
        LOGGER.atInfo().setMessage("Hello structured logging!").addKeyValue("userId", "1").log();
    }

}

Elastic Common Schema 定義了許多欄位名稱,Spring Boot 內建支援服務名稱、服務版本、服務環境和節點名稱。要設定這些欄位的值,您可以在 application.properties 檔案中使用以下內容

logging.structured.ecs.service.name=MyService
logging.structured.ecs.service.version=1
logging.structured.ecs.service.environment=Production
logging.structured.ecs.service.node-name=Primary

檢視 JSON 輸出時,現在會有 service.nameservice.versionservice.environmentservice.node.name 欄位。透過這些,您現在可以在您的日誌系統中按節點名稱、服務版本等進行過濾。

自定義日誌格式

如上所述,Spring Boot 開箱即用地支援 Elastic Common Schema 和 Logstash 格式。要新增您自己的格式,您需要執行以下步驟

  1. 建立 StructuredLogFormatter 介面的自定義實現
  2. application.properties 中引用您的自定義實現

首先,讓我們建立自己的自定義實現

class MyStructuredLoggingFormatter implements StructuredLogFormatter<ILoggingEvent> {

    @Override
    public String format(ILoggingEvent event) {
        return "time=" + event.getTimeStamp() + " level=" + event.getLevel() + " message=" + event.getMessage() + "\n";
    }

}

正如您所見,結構化日誌支援不僅限於 JSON,您可以返回任何您想要的 String。在此示例中,我們選擇使用 key=value 對。

現在我們需要讓 Spring Boot 瞭解我們的自定義實現。為此,請將此新增到 application.properties 檔案中

logging.structured.format.console=com.example.structured_logging_demo.MyStructuredLoggingFormatter

是時候啟動應用程式並驚歎於日誌輸出了!

time=1722330118045 level=INFO message=Hello structured logging!

哇,看看這個!多麼漂亮的日誌輸出!

如果您想編寫 JSON,Spring Boot 3.4 中有一個方便的新 JsonWriter,您可以使用它

class MyStructuredLoggingFormatter implements StructuredLogFormatter<ILoggingEvent> {

    private final JsonWriter<ILoggingEvent> writer = JsonWriter.<ILoggingEvent>of((members) -> {
        members.add("time", (event) -> event.getInstant());
        members.add("level", (event) -> event.getLevel());
        members.add("thread", (event) -> event.getThreadName());
        members.add("message", (event) -> event.getFormattedMessage());
        members.add("application").usingMembers((application) -> {
            application.add("name", "StructuredLoggingDemo");
            application.add("version", "1.0.0-SNAPSHOT");
        });
        members.add("node").usingMembers((node) -> {
           node.add("hostname", "node-1");
           node.add("ip", "10.0.0.7");
        });
    }).withNewLineAtEnd();

    @Override
    public String format(ILoggingEvent event) {
        return this.writer.writeToString(event);
    }

}

當然,您也可以使用任何其他 JSON 庫(例如 Jackson)來建立 JSON,您不必使用 JsonWriter

生成的日誌訊息看起來像這樣

{"time":"2024-07-30T09:14:49.377308361Z","level":"INFO","thread":"main","message":"Hello structured logging!","application":{"name":"StructuredLoggingDemo","version":"1.0.0-SNAPSHOT"},"node":{"hostname":"node-1","ip":"10.0.0.7"}}

總結

我們希望您喜歡 Spring Boot 3.4 中的這項新功能!文件 也已更新以支援結構化日誌記錄。

請告訴我們您的想法,如果您發現任何問題,我們的 issue tracker 始終開放!

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助您加速進步。

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一份簡單的訂閱。

瞭解更多

即將舉行的活動

檢視 Spring 社群所有即將舉行的活動。

檢視所有