Logbook 4在 Spring Boot 4中未记录异步响应的trace-id
在从Spring Boot 3.5/Logbook 3.12迁移到Spring Boot 4/Logbook 4.0.4之后,我发现Logbook生成的日志在Spring Web MVC控制器的异步HTTP响应中缺少trace-id。
在我的情况下,异步响应是使用Reactor的 Mono 返回类型实现的控制器方法,这是被允许的——参见: https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-ann-async.html#mvc-ann-async-reactive-types
我猜这是Spring Boot、Micrometer Tracing与 Logbook集成方面的一个问题。理论上Logbook对 Micrometer一无所知,Spring Boot也可以在没有Micrometer的情况下工作。因此我猜新版本中的Micrometer Tracing在异步HTTP响应领域缺少了某些东西。
添加下面的Spring bean也不起作用:
@Bean
ContextPropagatingTaskDecorator contextPropagatingTaskDecorator() {
return new ContextPropagatingTaskDecorator();
}
在单元测试前面添加下面这行代码也同样不起作用:
Hooks.enableAutomaticContextPropagation();
我使用以下依赖:
- org.springframework.boot:spring-boot-starter-actuator
- org.springframework.boot:spring-boot-micrometer-tracing-opentelemetry
- io.micrometer:micrometer-tracing-bridge-otel
如果我把控制器方法的返回类型从 Mono<T> 改为 CompletableFuture<T>,从而消除Reactor,问题仍然存在。因此这与Reactor无关。
我认为这里的根本原因在于方法 javax.servlet.AsyncListener::onComplete 没有在重新创建的上下文快照(Micrometer术语)中被调用。在这种情况下 LogbookFilter 注册了一个 LogbookAsyncListener 类的实例,该实例实现了 javax.servlet.AsyncListener 接口。所以这是Logbook与 Servlet容器集成的一个领域。我的猜测是,当存在Micrometer Tracing时,Spring Boot应该正确对Servlet堆栈进行打点,这样 javax.servlet.AsyncListener 应该在重新创建的上下文快照中被调用。
有什么办法可以修复吗?
解决方案
我来回答我自己的问题:在这种情况下的一个变通方案是准备一个自定义bean类来替换LogbookFilter。自定义过滤器类必须:
- 基于来自 https://raw.githubusercontent.com/zalando/logbook/refs/heads/main/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LogbookFilter.java 的代码拷贝
- 放到
org.zalando.logbook.servlet,因为代码必须创建包私有类的实例(例如RemoteRequest) - 有一个新的成员字段:
ContextSnapshotFactory contextSnapshotFactory(Micrometer Context-Propagation) - 对异步请求的处理做了修改:
if (request.isAsyncStarted()) {
var wrapped = contextSnapshotFactory.captureAll().wrap(() -> {
try {
write(response, writing);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
request.getAsyncContext().addListener(
new LogbookAsyncListener(event -> wrapped.run()));
}
如果 LobookFilter 类能提供可注入工厂,用于生成 LogbookAsyncListener 实例或 AsyncOnCompleteListener 实例,那么就可以提供自定义实现,但现在Logbook不支持它。此外Logbook类是final,因此需要如上完整地替换。