知識庫 / Spring / Spring Boot RSS 訂閱

Spring Boot 結構化日誌

Logging,Spring Boot
HongKong
5
10:52 AM · Dec 06 ,2025

1.  概述

日誌是任何軟件應用程序中必不可少的功能。它通過記錄運行時期間的錯誤、警告和其他事件,來跟蹤應用程序的行為。

默認情況下,Spring Boot應用程序會生成非結構化的、可讀的日誌。雖然這些日誌對開發者有幫助,但它們難以被日誌聚合工具解析和分析。結構化日誌解決了這一限制。

在本教程中,我們將學習如何通過利用Spring Boot 3.4.0版本中引入的功能,來實現結構化日誌。

2. Maven 依賴

首先,讓我們通過在 pom.xml 中添加 spring-boot-starter 來啓動一個 Spring Boot 項目:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>3.4.0</version>
</dependency>

上述依賴項為典型的 Spring Boot 應用提供自動配置和日誌記錄的支持。

3. Spring Boot 默認日誌

以下是 Spring Boot 的默認日誌:

INFO 22059 --- [ main] c.b.s.StructuredLoggingApp  : No active profile set, falling back to 1 default profile: "default"
INFO 22059 --- [ main] c.b.s.StructuredLoggingApp   : Started StructuredLoggingApp in 2.349 seconds (process running for 3.259)

雖然這些日誌具有一定的參考價值,但無法被像 Elasticsearch 這樣工具輕鬆地導入並進行指標分析。 結構化日誌格式,如 JSON,通過標準化日誌內容來解決這個問題。

4. 配置

從 Spring Boot 3.4.0 版本開始,結構化日誌記錄已內置,並支持 Elastic Common Schema (ECS)、Graylog Extended Log Format (GELF) 和 Logstash JSON 等格式。

我們可以直接在 application.properties 文件中配置結構化日誌記錄。

4.1. 彈性通用模式 (ECS)

彈性通用模式 (ECS) 是一種標準化的基於 JSON 的日誌格式,它能夠無縫地集成 Elasticsearch 和 Kibana。為了在我們的應用程序中配置 ECS,讓我們將其屬性添加到我們的 <em >application.properties</em > 文件中:

logging.structured.format.console=ecs

這是一個示例輸出:

{
  "@timestamp": "2024-12-19T01:17:47.195098997Z",
  "log.level": "INFO",
  "process.pid": 16623,
  "process.thread.name": "main",
  "log.logger": "com.baeldung.springstructuredlogging.StructuredLoggingApp",
  "message": "Started StructuredLoggingApp in 3.15 seconds (process running for 4.526)",
  "ecs.version": "8.11"
}

輸出包含鍵值對,可以輕鬆地在 Elasticsearch 和 Kibana 中解析。

此外,通過添加諸如服務名稱、環境和節點名稱等字段,可以增強 ECS 日誌的可觀測性。

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

這是新的輸出:

{
  "@timestamp": "2024-12-19T01:25:15.123108416Z",
  "log.level": "INFO",
  "process.pid": 18763,
  "process.thread.name": "main",
  "service.name": "BaeldungService",
  "service.version": "1",
  "service.environment": "Production",
  "service.node.name": "Primary",
  "log.logger": "com.baeldung.springstructuredlogging.StructuredLoggingApp",
  "message": "Started StructuredLoggingApp in 3.378 seconds (process running for 4.376)",
  "ecs.version": "8.11"
}

輸出包含我們在 application.properties文件中定義的服務信息。

4.2. Graylog 擴展日誌格式

Graylog 擴展日誌格式 (GELF) 是一種基於 JSON 的支持的結構化日誌格式。 讓我們在我們的 application.properties 文件中啓用它:

logging.structured.format.console=gelf

GELF 格式與 ECS 格式相似,但具有不同的屬性名稱:

{
  "version": "1.1",
  "short_message": "Started StructuredLoggingApp in 2.77 seconds (process running for 3.89)",
  "timestamp": 1734572549.172,
  "level": 6,
  "_level_name": "INFO",
  "_process_pid": 23929,
  "_process_thread_name": "main",
  "_log_logger": "com.baeldung.springstructuredlogging.StructuredLoggingApp"
}

就像 ECS 配置一樣,我們還可以通過在 application.properties文件中定義主機和服務版本來進一步增強輸出:

logging.structured.gelf.host=MyService
logging.structured.gelf.service.version=1

這擴展了日誌記錄,添加了主機和服務鍵值對。

4.3. Logstash 格式

Logstash 格式也已內置支持。為了將日誌結構化為該格式,請在 application.properties 中指定它:

logging.structured.format.console=logstash

以下是示例結構化日誌:

{
  "@timestamp": "2024-12-19T02:49:33.017851728+01:00",
  "@version": "1",
  "message": "Started StructuredLoggingApp in 2.749 seconds (process running for 3.605)",
  "logger_name": "com.baeldung.springstructuredlogging.StructuredLoggingApp",
  "thread_name": "main",
  "level": "INFO",
  "level_value": 20000
}

上述格式可以輕鬆通過支持 Logstash 格式的日誌聚合進行分析。

4.4. 補充信息

我們可以使用映射診斷上下文(Mapped Diagnostic Context,MDC)類向結構化日誌添加更多信息。例如,我們可以將 userId 添加到日誌中,以便按 userId 過濾日誌:

private static final Logger LOGGER = LoggerFactory.getLogger(CustomLog.class);
public void additionalDetailsWithMdc() {
    MDC.put("userId", "1");
    MDC.put("userName", "Baeldung");
    LOGGER.info("Hello structured logging!");
    MDC.remove("userId");
    MDC.remove("userName");
}

在上面的代碼中,我們隨後逐個刪除每個條目,以清理 MDC 上下文,防止內存泄漏。

讓我們查看帶有用户詳細信息的日誌輸出:

{
  "@timestamp": "2024-12-19T07:52:30.556819106+01:00",
  "@version": "1",
  "message": "Hello structured logging!",
  "logger_name": "com.baeldung.springstructuredlogging.CustomLog",
  "thread_name": "main",
  "level": "INFO",
  "level_value": 20000,
  "userId": "1",
  "userName": "Baeldung"
}

在這裏,我們向日志消息添加了更多標記。我們可以根據 userId 輕鬆地過濾日誌。 還可以使用 MDC 類向日志添加更多屬性。

此外,我們可以使用流式日誌 API 實現類似的目的:

public void additionalDetailsUsingFluentApi() {
    LOGGER.atInfo()
      .setMessage("Hello Structure logging!")
      .addKeyValue("userId", "1")
      .addKeyValue("userName", "Baeldung")
      .log();
}

這種方法更簡潔,並且自動處理上下文清理,從而減少了錯誤的可能性。

4.5. 自定義日誌格式

此外,我們還可以定義自己的自定義日誌格式,並在 application.properties 中使用它。這在受支持的日誌格式無法滿足我們的用例時非常有用。

首先,我們需要實現 StructuredLogFormatter 接口並覆蓋其中的 format() 方法:

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

在此,我們的自定義格式為文本格式,而不是標準JSON。這提供了靈活性,因為我們可以根據任何格式(包括JSON、XML等)來構建我們的日誌。

接下來,讓我們在 application.properties 中定義我們的自定義配置:

logging.structured.format.console=com.baeldung.springstructuredlogging.MyStructuredLoggingFormatter

這裏,我們定義了全限定類名 MyStructuredLoggingFormatter

以下是日誌輸出:

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

輸出為文本格式,其中鍵值對錶示日誌詳情。

自定義格式在不支持的格式無法滿足我們的需求時,具有優勢。

此外,我們可以使用 JSONWriter 將其寫入自定義 JSON 格式:

private final JsonWriter<ILoggingEvent> writer = JsonWriter.<ILoggingEvent>of((members) -> {
    members.add("time", ILoggingEvent::getInstant);
    members.add("level", ILoggingEvent::getLevel);
    members.add("thread", ILoggingEvent::getThreadName);
    members.add("message", ILoggingEvent::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();

接下來,讓我們將 writer() 方法集成到 format() 方法中:

@Override
public String format(ILoggingEvent event) {
    return this.writer.writeToString(event);
}
{
  "outputted log": "輸出日誌",
  "is structured in JSON format": "以 JSON 格式結構化",
  "is structured in JSON format": "以 JSON 格式結構化"
}
{
  "time": "2024-12-19T08:55:13.284101533Z",
  "level": "INFO",
  "thread": "main",
  "message": "No active profile set, falling back to 1 default profile: \"default\"",
  "application": {
    "name": "StructuredLoggingDemo",
    "version": "1.0.0-SNAPSHOT"
  },
  "node": {
    "hostname": "node-1",
    "ip": "10.0.0.7"
  }
}

根據使用場景,編寫自定義格式可以提供更大的靈活性,以處理 Spring Boot 默認不支持的日誌聚合功能。

4.6. 將日誌寫入文件

我們之前的示例將日誌直接寫入控制枱。但是,我們可以通過修改我們的配置,在控制枱保持人類可讀的日誌格式,並將結構化日誌寫入文件。

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

在這裏,我們使用文件屬性而不是控制枱屬性。這會創建一個 log.json文件,其中包含項目根目錄中的結構化日誌。

5. 結論

在本文中,我們學習瞭如何使用 application.properties 配置來配置應用程序以進行結構化日誌記錄。我們還看到了支持的結構化日誌格式的不同示例。

最後,我們看到了如何編寫自定義格式並使用它通過配置。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.