You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot 4+Logbook 4异步响应日志缺失trace-id求助

Spring Boot 4 + Logbook 4.0.4异步HTTP响应日志缺失trace-id问题修复方案

问题背景

将应用从Spring Boot 3.5/Logbook 3.12迁移至Spring Boot 4/Logbook 4.0.4后,Spring Web MVC控制器的异步HTTP响应日志中,Logbook生成的日志缺失trace-id。异步响应通过控制器返回Mono类型实现(符合Spring官方规范),后续将返回类型改为CompletableFuture<T>后问题仍存在,排除Reactor依赖影响。

已尝试无效操作

  • 添加ContextPropagatingTaskDecorator Bean
  • 在单元测试前调用Hooks.enableAutomaticContextPropagation()

使用依赖

  • org.springframework.boot:spring-boot-starter-actuator
  • org.springframework.boot:spring-boot-micrometer-tracing-opentelemetry
  • io.micrometer:micrometer-tracing-bridge-otel

根因推测

javax.servlet.AsyncListener::onComplete方法未在Micrometer的context-snapshot中被调用。LogbookFilter会注册实现javax.servlet.AsyncListener的LogbookAsyncListener,当集成Micrometer Tracing时,Spring Boot需正确对Servlet栈埋点,确保该方法在重建的上下文快照中执行,否则Logbook无法获取trace-id。


可行修复方案

方案1:包装AsyncListener实现上下文恢复

自定义AsyncListener包装类,在onComplete方法中手动恢复trace上下文,确保Logbook执行时能获取到trace-id:

@Component
public class TracingAsyncListenerWrapper implements AsyncListener {

    private final AsyncListener delegate;
    private final ContextSnapshot snapshot;

    public TracingAsyncListenerWrapper(AsyncListener delegate) {
        this.delegate = delegate;
        this.snapshot = ContextSnapshot.captureAll();
    }

    @Override
    public void onComplete(AsyncEvent event) throws IOException {
        try (ContextSnapshot.Scope scope = snapshot.restore()) {
            delegate.onComplete(event);
        }
    }

    @Override
    public void onTimeout(AsyncEvent event) throws IOException {
        delegate.onTimeout(event);
    }

    @Override
    public void onError(AsyncEvent event) throws IOException {
        delegate.onError(event);
    }

    @Override
    public void onStartAsync(AsyncEvent event) throws IOException {
        delegate.onStartAsync(event);
    }

    @Bean
    public Filter tracingAsyncListenerFilter() {
        return (request, response, chain) -> {
            if (request instanceof HttpServletRequest httpServletRequest) {
                AsyncContext asyncContext = httpServletRequest.startAsync();
                // 替换原有AsyncListener为包装后的实例
                Collection<AsyncListener> listeners = new ArrayList<>(asyncContext.getListeners());
                listeners.forEach(asyncContext::removeListener);
                listeners.forEach(listener -> asyncContext.addListener(new TracingAsyncListenerWrapper(listener)));
            }
            chain.doFilter(request, response);
        };
    }
}

方案2:调整Filter执行顺序

确保Micrometer的TracingFilter在LogbookFilter之前执行,保证上下文先被初始化:

# application.properties
spring.servlet.filter.order.micrometer-tracing=5
spring.servlet.filter.order.logbook=10

方案3:升级依赖版本

检查Micrometer Tracing或Spring Boot的最新补丁版本,确认是否有针对异步Servlet上下文传播的修复,尝试升级至对应版本。

方案4:自定义Logbook异步监听器

继承LogbookAsyncListener,在构造时保存当前trace-id,执行onComplete时手动恢复上下文:

public class TracingLogbookAsyncListener extends LogbookAsyncListener {

    private final String traceId;

    public TracingLogbookAsyncListener(Logbook logbook, HttpServletRequest request, HttpServletResponse response) {
        super(logbook, request, response);
        this.traceId = TraceContext.current().traceId();
    }

    @Override
    public void onComplete(AsyncEvent event) throws IOException {
        try (Scope scope = TraceContext.newBuilder().traceId(traceId).build().makeCurrent()) {
            super.onComplete(event);
        }
    }
}

同时自定义LogbookFilter,替换默认的LogbookAsyncListener为上述自定义实现。


内容的提问来源于stack exchange,提问作者Krzysztof Tomaszewski

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.01 12:32:26