3. 日志与追踪数据采集规范

排查订单ID缺失与乱序,最怕的不是“没有日志”,而是“日志各说各话”:网关记了订单号却没记TraceID,应用记了TraceID却把订单号放在自由文本里,消息队列只留下消息ID,数据库慢查询日志里干脆没有业务主键。数据采集规范的目标只有一个——让同一个订单在网关、应用、消息队列、数据库四个环节都留下可对齐的标识,并且能通过TraceID把一次请求的完整链路串起来。本节围绕埋点字段、传递方式和落地样例展开。

一、四个环节各自必须留下什么

订单ID的“存在性”和“可关联性”是两件事。订单ID存在,只能证明这条日志属于某个订单;只有同时具备TraceID(或等价的链路标识)和SpanID,才能证明这条日志属于哪一次请求。因此每个环节的埋点要求可以拆成三层:业务标识层(订单ID、用户ID)、链路标识层(TraceID、SpanID、ParentSpanID)、上下文层(服务名、实例IP、时间戳、日志级别、耗时)。

网关是链路的起点,必须在接收请求时生成或透传TraceID,并把订单ID从请求参数、Header或Body中提取出来写入访问日志。应用层是订单ID最容易被“加工丢失”的地方:订单号可能在Controller层被转成内部orderNo,到了Service层日志里就只剩orderNo,排查时无法与网关的订单ID对齐。消息队列环节的难点在于异步:生产者发送消息时要把TraceID和订单ID写入消息Header,消费者从Header恢复上下文,否则消费日志会成为链路中的“断点”。数据库环节则要保证慢SQL日志、事务日志中能带出业务主键,至少在执行语句的参数中保留订单ID。

订单请求全链路埋点与TraceID串联示意 ① 网关 生成/透传 TraceID 提取订单ID 写访问日志 traceId + orderId + spanId ② 应用服务 MDC/上下文透传 订单ID统一字段名 子Span记录耗时 orderId 不转义、不改名 ③ 消息队列 Header写入TraceID Header写入订单ID 消费端恢复上下文 避免异步断链 ④ 数据库 慢SQL带参数 事务日志留主键 执行耗时入Span order_id 可回溯 TraceID 贯穿全链路(同一次请求的唯一标识) 统一日志字段结构(示例) traceId:链路唯一标识 spanId:当前节点标识 orderId:业务主键 service / instance timestamp:毫秒时间 level:日志级别 costMs:耗时 parentSpanId:父节点 关键约束:orderId 在四个环节使用同一字段名与同一格式;traceId 由网关生成后全程透传,不得在应用内重新生成。 乱序分析依赖 timestamp 与 spanId 的父子关系,缺失分析依赖 orderId 与 traceId 的成对出现。

二、TraceID的生成与透传规则

TraceID的生成位置必须唯一。推荐在网关入口生成,格式采用32位十六进制字符串(等价于W3C Trace Context的trace-id),并通过HTTP Header traceparent向下游透传。应用服务收到请求后,从Header解析TraceID写入日志上下文(如Logback的MDC),而不是自己重新生成。一个常见的错误是:网关生成了TraceID,但应用层用UUID.randomUUID()又生成了一个,导致同一次请求在日志里出现两个互不相干的链路。

对于异步消息,TraceID必须随消息一起走。生产者在发送前把当前TraceID写入消息Header,消费者在消费入口读取Header并恢复到MDC,再继续向下游传递。如果消息中间件不支持自定义Header,退而求其次的做法是把TraceID和订单ID写入消息体,但要在消费端显式解析,不能依赖“消息体里有就行”。

// 应用层日志上下文写入示例(Logback MDC)
String traceId = request.getHeader("traceparent");
if (traceId == null || traceId.isEmpty()) {
    traceId = TraceIdGenerator.next(); // 仅兜底,正常应由网关生成
}
MDC.put("traceId", traceId);
MDC.put("orderId", orderId);
log.info("order created, orderId={}, traceId={}", orderId, traceId);
MDC.clear();

三、订单ID的字段规范与常见坑

订单ID在四个环节必须使用同一字段名、同一格式。建议统一为orderId,值为字符串类型,避免数据库自增ID与业务订单号混用。实际排障中高频出现的三类问题值得提前规避:

  • 字段名漂移:网关叫order_id,应用叫orderNo,消息队列叫orderCode,检索时无法用一条查询覆盖全链路。规范要求所有环节的日志字段名统一为orderId,数据库列名可保留order_id,但日志输出时必须映射为orderId。
  • 订单ID被加工丢失:应用层把外部订单号转换成内部流水号后,日志只记内部流水号。规范要求转换前后都记录,至少保留原始订单ID。
  • 异步消费断链:消费者日志里只有消息ID,没有TraceID和订单ID。规范要求消费入口第一行日志就打印这两个字段。

四、采集落地检查清单

规范是否真正落地,可以用一份可执行的检查清单来验证。以下清单按环节组织,每项都对应一个可观察的日志字段或配置项,而不是抽象要求。

环节检查项合格判据
网关TraceID生成与透传访问日志含traceId,下游Header含traceparent
网关订单ID提取访问日志含orderId,且与请求参数一致
应用MDC写入每条业务日志含traceId与orderId
应用字段名统一全服务检索orderId可命中,无orderNo混用
消息队列Header透传消息Header含traceId与orderId
消息队列消费端恢复消费日志首行含traceId与orderId
数据库慢SQL参数慢SQL日志含order_id参数值
数据库耗时入SpanSQL执行耗时可在链路中查看
采集规范的价值不在“字段多”,而在“字段对得上”。排查时最有效的验证方式是:拿一个真实订单ID,在日志平台按traceId聚合,检查网关、应用、消息队列、数据库四个环节是否都能命中同一条链路。若某一环节缺失,就回到该环节的埋点配置逐项核对。