1、概览 {#1概览}
日志是任何软件应用程序的基本功能。它通过记录错误、警告和其他事件,帮助跟踪应用程序在运行期间的行为。
默认情况下,Spring Boot 应用程序会生成非结构化、人类可读的日志。虽然这些日志对开发人员很有用,但它们不容易被日志聚合工具解析或分析。结构化日志解决了这一限制。
本文将带你了解如何使用 Spring Boot 3.4.0 版中引入的功能实现结构化日志。
2、Maven 依赖 {#2maven-依赖}
首先,在 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 默认的日志 {#3spring-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、配置 {#4配置}
从 Spring Boot 3.4.0 版开始,内置了结构化日志,并支持 Elastic Common Schema (ECS) 、Graylog Extended Log Format (GELF) 和 Logstash JSON 等格式。
我们可以直接在 application.properties
文件中配置结构化的日志格式。
4.1、Elastic Common Schema {#41elastic-common-schema}
Elastic Common Schema(ECS) 是一种基于 JSON 的标准化日志格式,可无缝集成 Elasticsearch 和 Kibana 。要在应用程序中配置 ECS,需要在 application.properties
文件中添加它的属性:
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 扩展日志格式 {#42graylog-扩展日志格式}
Graylog Extend Log Format (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
文件中定义主机和服务版本来进一步增强输出:
# 主机 HOST
logging.structured.gelf.host=MyService
# 服务版本号
logging.structured.gelf.service.version=1
这将通过添加主机和服务键值对来扩展日志。
4.3、Logstash 格式 {#43logstash-格式}
开箱即用的 Logstash 格式也受支持。要按照这种格式构建日志,需要在 application.properties
中指定它:
logging.structured.format.file=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 格式的日志聚合器(Log Aggregation)可以轻松分析上述格式。
4.4、其他信息 {#44其他信息}
我们可以使用 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 类为日志添加更多属性。
此外,还可以使用 Fluent 风格的日志 API 来实现类似的目的:
public void additionalDetailsUsingFluentApi() {
LOGGER.atInfo()
.setMessage("Hello Structure logging!")
.addKeyValue("userId", "1")
.addKeyValue("userName", "Baeldung")
.log();
}
这种方法更简洁,而且能自动处理上下文清理,减少出错的可能性。
4.5、自定义日志格式 {#45自定义日志格式}
此外,我们还可以定义自己的自定义结构日志格式,并在 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);
}
输出的日志是 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、记录日志到文件 {#46记录日志到文件}
我们之前的示例直接将日志记录到控制台。不过,我们可以通过修改配置,在控制台中保持人类可读的日志格式,并将结构化日志写入文件:
logging.structured.format.file=ecs
logging.file.name=log.json
如上,我们使用 file
属性而不是 console
属性。这会在项目根目录下创建一个包含结构化日志的 log.json
文件。
5、总结 {#5总结}
本文介绍了如何通过 application.properties
配置文件来定义 Spring Boot 中的结构化日志,以及如何实现自定义的日志格式。
Ref:https://www.baeldung.com/spring-boot-structured-logging