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=gelfGELF 格式與 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 配置來配置應用程序以進行結構化日誌記錄。我們還看到了支持的結構化日誌格式的不同示例。
最後,我們看到了如何編寫自定義格式並使用它通過配置。