关键在于traceid从请求入口贯穿至最终错误现场,需在网关生成并注入请求头,通过filter+mdc统一传递,跨线程透传,异常构造时即时绑定,结构化存储,且全链路格式、导出、采集严格对齐。

关键不在“捕获异常”,而在“让 traceId 从请求入口一路活到最后一个错误现场”——只要 traceId 不断、不丢、不歧义,日志一搜、链路一查、问题秒现。
确保 traceId 从网关稳稳落进每个服务上下文
traceId 必须在第一层(如 API 网关或反向代理)生成并注入请求头(如 X-Trace-ID),后续所有服务禁止自行生成。Spring Boot 中推荐用 Filter + MDC 实现:
- 在 OncePerRequestFilter 中解析 header,若不存在则用 UUID 生成,统一设入 MDC:
MDC.put("traceId", traceId) - Logback 日志 pattern 中加入
%X{traceId:-},确保每行日志自带 traceId - 跨线程场景(如线程池、CompletableFuture)需显式传递 MDC 内容,可用 TransmittableThreadLocal 或 OpenTelemetry 的 Context 做透传
异常构造阶段强制绑定当前 traceId 与执行上下文
不要等 finally 或全局 handler 才补 traceId——它必须在异常诞生那一刻就已内嵌。建议封装统一的业务异常基类:
-
AppError(code, message, context) 构造时自动读取当前 MDC.traceId 或 OpenTelemetry Context,并写入
extensions.context.traceId - 对下游 HTTP 调用失败、DB 查询超时等典型场景,捕获原始异常后立即增强:
throw new AppError("DB_TIMEOUT", { sql: "SELECT * FROM order", spanId: currentSpan.getSpanContext().spanId() }, cause) - 避免在
message字段拼接 traceId;它应严格存在于结构化字段中(如 extensions.context),便于日志系统提取与告警规则匹配
日志、链路、监控三端对齐 traceId 格式与生命周期
traceId 不是“有就行”,而是要“各处都认得、都能查、都不改”。需统一三项实践:
- 格式标准化:全系统采用 32 位小写十六进制字符串(如 a1b2c3d4e5f67890a1b2c3d4e5f67890),禁用短 ID、UUIDv4 带横线格式、或 Base64 编码
-
链路导出一致:OpenTelemetry SDK 与 Jaeger/Zipkin 导出器共用同一 traceId 生成器(如
RandomIdGenerator),避免 Span ID 和 traceId 来源割裂 -
日志采集不截断:Promtail / Filebeat 配置中关闭自动字段截断,确保
traceId字段完整进入 Loki 或 ES;Grafana 日志查询时直接用{job="app"} |~ `traceId:"[a-f0-9]{32}"`定位整条链











