观测(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(); } } }
核心问题
- 在自定义ExceptionHandler中,上述获取traceId的代码是否正确?正确的操作方式是什么?
- 观测(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
相关产品推荐
相关产品推荐

