可通过继承 runtimeexception 并在构造时注入 traceid 实现异常携带分布式追踪上下文,需业务代码显式传入而非从 mdc 读取,日志和监控中主动使用该 traceid,跨线程时须手动透传。

可以通过继承 RuntimeException 并在构造时注入当前线程的 TraceID,实现异常携带分布式追踪上下文。关键在于让 TraceID 随异常传播,不依赖日志框架自动注入,而是由异常本身“记住”它。
TraceID 必须在抛出前已存在
微服务中 TraceID 通常由网关或第一个服务生成,并通过 MDC、ThreadLocal 或 RequestContextHolder(Spring)等机制在线程内透传。自定义异常无法主动获取它,必须由业务代码显式传入:
- 不要在异常构造器里尝试从 MDC 读取(MDC 可能已被清理或跨线程失效)
- 推荐在捕获原始异常并包装时,从当前上下文提取 TraceID 一并传入
- 例如:
throw new BizException("库存不足", traceId, e);
定义带 TraceID 的 RuntimeException 子类
只需添加一个私有字段 + 对应 getter,不需重写其他逻辑。保持轻量,避免影响性能或序列化兼容性:
public class BizException extends RuntimeException {
private final String traceId;
public BizException(String message, String traceId) {
super(message);
this.traceId = traceId;
}
public BizException(String message, String traceId, Throwable cause) {
super(message, cause);
this.traceId = traceId;
}
public String getTraceId() {
return traceId;
}
}
注意:不建议把 TraceID 写进 getMessage(),否则干扰日志解析;也不建议重写 printStackTrace(),维护成本高且易出错。
在日志和监控中主动使用该 TraceID
异常本身只是容器,价值体现在后续处理环节:
- 全局异常处理器(如 Spring 的
@ControllerAdvice)中,优先取exception.getTraceId(), fallback 到 MDC 或生成新 ID - 上报到 Sentry / SkyWalking / Prometheus 时,将
traceId作为 tag 或 field 显式传递 - 打印日志时,用
log.error("BizException[{}]: {}", ex.getTraceId(), ex.getMessage(), ex)
跨线程/异步调用需额外透传
若异常在子线程、CompletableFuture 或消息队列消费者中抛出,ThreadLocal 中的 TraceID 已丢失。此时自定义异常仍有效,但前提是:
- 创建子任务时,手动把当前 TraceID 提取出来,作为参数或闭包变量传入
- 在新线程里构造
BizException时再次传入该 ID - 不推荐在子线程里“恢复” MDC,因为可能污染其他任务上下文
不复杂但容易忽略:TraceID 是上下文快照,不是运行时状态。自定义异常的作用是固化那一刻的追踪线索,而非替代链路透传机制。











