Spring Boot 3.4 中的结构化日志

工程 | 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" ... }

您还可以使用 流式日志记录 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 中的这一新功能!文档也已更新以包含结构化日志记录的内容。

请告诉我们您的想法,如果您发现任何问题,我们的问题跟踪器始终开放!

获取 Spring 时事通讯

订阅 Spring 时事通讯,保持联系

订阅

领先一步

VMware 提供培训和认证,助力您的进步。

了解更多

获取支持

Tanzu Spring 通过一个简单的订阅提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将到来的活动

查看 Spring 社区所有即将到来的活动。

查看全部