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社区中所有即将举行的活动。

查看全部