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

观测(Observation)停止后调用openScope会发生什么?(过滤器日志关联丢失)

问题背景

SpringBoot 3.1.0 - 内嵌Tomcat


场景复现代码

1. 过滤器中抛出异常

public class MyFilter extends GenericFilterBean {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
      // ...
      throw new IllegalArgumentException("IllegalArgumentException");
    }
}

2. 自定义ErrorController

public class MyErrorController extends AbstractErrorController {
    @RequestMapping
    public ResponseEntity<Map<String, Object>> handler(HttpServletRequest request) {
        // ...
        Object ex = request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
        throw new MyErrorControllerException("requestInfo", (Throwable) ex, status); 
    }
}

3. 自定义ExceptionHandler

@ExceptionHandler(Throwable.class)
public ResponseEntity<MessageSourceResult> handler(Throwable ex, HttpServletRequest request, HttpServletResponse response) {
    Observation observation = (Observation) request.getAttribute(ServerHttpObservationFilter.class.getName() + ".observation");
    if (null != observation) {

        // 疑问:若要在此处获取traceId,应如何操作?以下代码是否正确?

        try (Observation.Scope scope = observation.openScope()) {
            response.setHeader("X-B3-TraceId", tracer.currentTraceContext().context().traceId());
            log.warn("Missing log correlation...");
            // ...
        }
    }
    
}

ServerHttpObservationFilter核心逻辑

ServerHttpObservationFilter在finally块中调用observation.stop()

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    Observation observation = createOrFetchObservation(request, response);
    try (Observation.Scope scope = observation.openScope()) {
        filterChain.doFilter(request, response);
    } catch (Exception ex) {
        observation.error(unwrapServletException(ex));
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        throw ex;
    } finally {
      // 仅当异步处理完成或从未启动时停止观测
        if (!request.isAsyncStarted()) {
            Throwable error = fetchException(request);
            if (error != null) {
                observation.error(error);
            }
            observation.stop();
        }
    }
}

核心问题

  1. 在自定义ExceptionHandler中,上述获取traceId的代码是否正确?正确的操作方式是什么?
  2. 观测(Observation)停止后调用openScope()会产生什么影响?

问题解答

1. 获取traceId的代码问题

你当前的代码存在风险,因为observation已经被ServerHttpObservationFilter在finally块中调用stop()终止了,此时打开它的Scope无法保证能正确获取到traceId。

正确的做法是:

  • 直接通过Tracer获取当前上下文的traceId,无需依赖已停止的Observation Scope:
if (tracer.currentTraceContext().hasContext()) {
    String traceId = tracer.currentTraceContext().context().traceId();
    response.setHeader("X-B3-TraceId", traceId);
    log.warn("Missing log correlation...", ex);
}
  • 如果必须关联原有请求的观测上下文,应该在ServerHttpObservationFilter停止Observation前保存traceId到请求属性,后续从属性中直接读取。

2. Observation停止后调用openScope()的影响

当Observation调用stop()后,其生命周期已结束,此时调用openScope()会带来以下问题:

  • 上下文信息丢失:已停止的Observation不再持有有效的trace、span等关联数据,打开Scope后无法获取原请求的日志关联信息。
  • 无效的上下文绑定:已终止的Observation的Scope无法将原请求上下文绑定到当前线程,后续通过Tracer获取的可能是空上下文或其他请求的上下文。
  • 不符合生命周期规范:Micrometer Observation的设计中,stop()是生命周期的终点,后续对该实例的任何操作都属于非法调用,可能引发未定义行为。
  • 潜在资源泄漏:虽然try-with-resources会自动关闭Scope,但已停止的Observation的Scope可能无法正常清理内部状态,存在微小的资源泄漏风险。

内容的提问来源于stack exchange,提问作者Perry Zhao

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 04:17:12