在微服务复杂的链式调用中,我们会比单体架构更难以追踪与定位问题。因此,在设计的时候,需要特别注意。一种比较好的方案是,当 RESTful API 接口出现非 2xx 的 HTTP 错误码响应时,采用全局的异常结构响应信息。其中,code 字段用来表示某类错误的错误码,在微服务中应该加上”{biz_name}/“前缀以便于定位错误发生在哪个业务系统上。我们来看一个案例,假设”用户中心”某个接口没有权限获取资源而出现错误,我们的业务系统可以响应”UC/AUTH_DENIED”,并且通过自动生成的 UUID 值的 request_id 字段,在日志系统中获得错误的详细信息。

HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"code": "INVALID_ARGUMENT",
"message": "{error message}",
"cause": "{cause message}",
"request_id": "01234567-89ab-cdef-0123-456789abcdef",
"host_id": "{server identity}",
"server_time": "2014-01-01T12:00:00Z"
}

错误码前缀 + request_id 是”日志级”追踪的两块基石,但微服务排障的完整能力还需要第三块:分布式链路追踪(trace)——把 request_id 升级为跨服务的 span 树,让”慢在哪一跳、错在哪一跳”可视化。本文把三层能力(错误契约、日志关联、链路追踪)拼成完整排障体系。

第一层:错误契约(原文方案的价值)

  • 全局统一错误结构:code/message/cause/request_id/host_id/server_time 六字段
  • code 带业务前缀(UC/AUTH_DENIED):客服与前端一眼定位责任系统,跨团队扯皮成本归零
  • request_id 由入口网关生成并写入响应头(X-Request-Id),全链路透传——用户报障只需提供时间点或截图里的 request_id
  • cause 只给内部排查用(生产响应可裁剪),message 面向用户友好化

第二层:日志关联(request_id 的落地)

  • 透传机制:HTTP 头 X-Request-Id / MQ 消息头 / RPC attachment 三条通道都要传;线程池异步场景用 TTL/装饰器保证不丢(ThreadLocal 跨线程失效是常见断点)
  • 日志规范:所有日志行带 request_id(MDC/SLF4J),ELK/Loki 中一条 query 拉出全链日志
  • 断链排查清单:异步线程未传递、MQ 消费未取头、第三方回调无 id(入口重新生成并记录映射)

第三层:分布式链路追踪(span 树)

  • 模型:trace(一次请求全链)= span(单服务内一段工作)树;span 带 traceId/spanId/parentSpanId/耗时/标签/事件
  • 标准:OpenTelemetry 统一了 API/SDK/协议(W3C traceparent 头),取代 OpenTracing+OpenCensus 分裂时代
  • 落地:SDK/agent 自动埋点(HTTP/RPC/DB/MQ 客户端服务端)+ 手动埋点(关键业务段);后端 Zipkin/Jaeger/Tempo,UI 看瀑布图
  • 采样:全量采集成本高,生产常用头采样(trace 级决策保证链完整)+ 错误/慢 trace 强制保留

瀑布图回答三个日志回答不了的问题:哪一跳慢(跨服务耗时分布)、并行是否真并行、哪一跳缺失(超时被吞的下游)。

三层协作的排障动线

  1. 用户报障/告警触发 → 拿 request_id 或 traceId
  2. 链路 UI 看瀑布:定位慢/错的那一跳(服务+方法+耗时)
  3. 用同一 id 在日志系统拉该跳详细日志与堆栈(cause 字段辅助)
  4. 指标系统(Prometheus)对照该服务同时段错误率/延迟/饱和度,判断是个例还是面
  5. 修复后以 trace 回归验证耗时结构恢复

日志给细节、链路给结构、指标给面——三者用同一个 id 串联,才是”快速追踪与定位”的完整含义;缺任何一层都会出现”有日志看不出全链、有链路看不到细节”的断层。

建设顺序建议

小团队先做第一二层(错误契约 + request_id 透传,成本极低收益极大),流量与团队规模上来后再上第三层(OTel + Tempo/Jaeger);切忌跳过 id 透传直接上 trace 系统——断链的 trace 比没有 trace 更误导。