关键在于确认原始异常是否被拦截器静默吞掉或浅层包装导致MDC上下文丢失:需确保catch中显式re-throw并透传cause、禁用空catch与@SneakyThrows误用、避免异常路径中误调MDC.clear()、校验@ControllerAdvice优先级不干扰afterCompletion执行、用断点链验证trace_id全程连续。

排查这类问题,关键在于确认原始异常是否在多层拦截器中被“吃掉”或“重包”后丢失了链路上下文绑定信息(如 MDC 中的 trace_id、span_id),导致后续日志、监控、补偿等环节彻底失去追踪依据。这不是单纯堆栈丢失,而是异常传播过程中,调用链上下文与错误因果关系同步断裂。
检查异常是否被静默吞掉或浅层包装
多层拦截器(如 AuthInterceptor → TraceIdInterceptor → LogInterceptor)中,若某一层在 catch 块里只做日志、未 re-throw,或仅 throw new RuntimeException(msg),就会切断 cause 链:
- 在每个拦截器的 catch 块末尾必须显式 throw e 或 throw new XxxException(msg, e),确保原始异常对象作为 cause 透传
- 避免空 catch:
catch (Exception e) { log.warn("ignore", e); }→ 这会导致外层完全收不到异常,trace_id 在此处即告终结 - 检查是否用了 Lombok
@SneakyThrows:它会把受检异常转为 RuntimeException 包装,但默认不设 cause,需手动传入
验证 MDC 和 ThreadLocal 是否随异常传播而重置
分布式链路追踪依赖 MDC.put("TRACE_ID", ...) 绑定到当前线程,并期望在异常冒泡时仍可读取。但若拦截器在异常路径中执行了 MDC.clear()、MDC.remove("TRACE_ID") 或重置了 ThreadLocal,就会导致断层:
- 在所有拦截器的 afterCompletion 和 catch 块中搜索 MDC.clear()、MDC.remove(...)、ThreadLocal.remove()
- 特别注意:某些日志拦截器会在 preHandle 初始化 MDC,在 afterCompletion 清理——但如果 preHandle 后抛异常,afterCompletion 可能不执行,造成“初始化了却没清理”,而下一次请求复用线程时 MDC 残留旧 trace_id;反之,若异常路径中提前 clear,后续日志就全无 trace_id
- 建议统一使用 MDC.getCopyOfContextMap() 在异常捕获点快照上下文,并附加到 error 日志中,便于定位断层时刻状态
确认异常处理器是否覆盖了拦截器链的传播路径
@ControllerAdvice 或全局异常过滤器若配置不当,可能“截胡”本该由拦截器 afterCompletion 处理的异常,导致拦截器无法执行 cleanup 逻辑(如 MDC 还原、span 结束):
- 检查异常处理器是否设置了 @Order(Ordered.HIGHEST_PRECEDENCE),过高优先级会抢在拦截器 afterCompletion 前处理异常
- 确保拦截器的 afterCompletion 方法中对
boolean isCompleted参数做判断:仅当isCompleted == false(即发生异常)时,才执行 span.end()、MDC.clear() 等兜底动作 - 若使用 OpenTelemetry,确认
Span.current()在异常路径中是否仍有效——部分 SDK 在异常未被捕获时会自动结束 span,但若拦截器提前 clear 了 context,span 就会 orphaned(游离)
用最小化复现 + 断点链验证传播完整性
写一个极简测试请求,逐层打日志并记录关键状态:
- 在每个拦截器 preHandle 打印:
MDC.get("TRACE_ID") + " | preHandle@" + this.getClass().getSimpleName() - 在每个拦截器 catch 块打印:
e.getClass().getName() + ", cause=" + (e.getCause() != null ? e.getCause().getClass().getName() : "null") + ", MDC=" + MDC.get("TRACE_ID") - 在 Controller 层再抛一次异常,观察从 preHandle → catch → afterCompletion → @ExceptionHandler 的全程 trace_id 是否连续
- 若某层 catch 后 MDC 为空,且 cause 为 null,基本可断定该层吞掉了原始异常或重置了上下文

















