Spring Boot 3.4 中的結構化日誌 (Structured logging)

工程 | Moritz Halbritter | 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");
    }
}

在記錄日誌訊息之前,此程式碼還會在 MDC 中設定使用者 ID。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 社群中所有即將舉行的活動。

查看全部