日志记录是应用故障排除的重要组成部分,也是可观测性的三大支柱之一,另外两个是指标和追踪(Trace)。没有人喜欢在生产环境中瞎操作,当事故发生时,开发者会很乐意看到日志文件。日志通常以人类可读的格式输出。
结构化日志是一种技术,其中日志输出以定义良好的格式编写,通常是机器可读的。这种格式可以输入到日志管理系统中,从而实现强大的搜索和分析功能。结构化日志最常用的格式之一是 JSON。
Spring Boot 3.4 开箱即支持结构化日志。它支持 Elastic Common Schema(ECS)和 Logstash 格式,也可以使用自己的格式进行扩展。
结构化日志的 Hello World {#结构化日志的-hello-world}
在 start.springboot.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 都在 JSON 中包含了 Mapped Diagnostic Context 的内容。
创建自己的日志消息:
@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 会自动在 JSON 中包含用户 ID:
{ ... ,"message":"Hello structured logging!","userId":"1" ... }
你还可以使用 Fluent 日志 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.name
、service.version
、service.environment
和 service.node.name
字段。有了这些字段,你就可以在日志系统中根据节点名称、服务版本等进行过滤。
自定义日志格式 {#自定义日志格式}
如上所述,Spring Boot 开箱即支持 Elastic Common Schema 和 Logstash 格式。要添加自己的格式,必须执行以下步骤:
- 创建
StructuredLogFormatter
接口的自定义实现 - 在
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,你以返回任何想要的字符串。在本例中,我们使用 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 的 文档 来了解更多详细的信息。
Ref:https://spring.io/blog/2024/08/23/structured-logging-in-spring-boot-3-4