客户报单卡了 3 秒,火线上你要回答:卡在网关、柜台、风控还是撮合?日志散在五个进程的文件里,靠人肉对时间戳拼链路,时间还对不齐(上一篇的时钟问题)。trace 体系的第一性原理其实很简单:给每笔业务一个唯一 ID,全程透传,所有日志按 ID 聚合。难的是透传过程里每一个会丢 ID 的缝。
ID 从哪来,怎么传
request_id 在协议网关生成——客户端请求的最早入口——由时间戳、节点号、随机数拼成,全局唯一且大致有序。生成后塞进上下文对象,随调用链往下传:
- 同步 RPC:框架的隐式传参机制(Java 的 ThreadLocal、C++ 的 TLS),业务代码无感知;
- 异步线程池:最大的缝。线程切换时 ThreadLocal 不跟着走,trace 断在半截。修法是包装任务对象,提交时把当前上下文快照进去,执行时恢复。每个异步改造点都要检查这一层;
- MQ 消息:把 request_id 放进消息头,消费方取出来续上(《消息中间件在交易链路的使用》篇的消费端同样要做这件事);
- 批量链路:清算任务没有 request_id 的概念,用"任务 ID + 业务键"双键关联——任务日志里记任务 ID,任务处理的具体业务(比如某笔资金流水)记业务键,两边都能串到《清算任务编排》那条依赖链上。
日志规范:格式统一,关键节点必打
格式先统一,否则聚合无从谈起:时间戳 | 级别 | 节点 | request_id | 模块 | 内容,一行一条,结构化字段固定位置。能不能 JSON 化看采集体系,字段约定比格式选择重要。
埋点的纪律:入口打全量报文,风控打结果和原因码,撮合打回报序号,出口打响应。这四个点是事后复盘的完整骨架,缺一个环节,trace 就有盲区。另外两类日志必须分文件:交易流水型日志(每笔固定一条,按日轮转,审计要查的)和调试日志(量多、随意)。混在一个文件里,审计提取和排障搜索互相干扰。
生产排障的顺序
反过来从单条 trace 捞是最低效的做法——日志量面前会先被淹没。正确顺序是先粗后细:
- 看错误码直方图:哪一层、哪种错误码在涨?八成的问题在这一步就定位到层了;
- 看关键计数器:委托到达率、风控拒绝率、回报发送量,和基线比;
- 最后才捞单条 trace:带着 request_id 还原这条委托的完整路径,看断在哪一拍。
这套顺序对应的是一个朴素事实:生产事故大多是面状的(某层挂了、某类单全废),不是点状的。点状问题(单笔卡死)留到第三步。
边界:敏感数据与日志级别
两条红线。第一条是敏感数据不进日志:证件号、银行卡号、密码、通讯地址的明细,日志系统的权限面比核心库宽得多,谁都能 grep 的东西不能放明文。脱敏必须在日志 SDK 里强制执行(比如证件号只留后四位),靠开发自觉写代码时记得掩码,迟早漏一个。第二条是日志级别运行时可调:排障需要临时开 debug,改配置重启进程在生产环境是不可接受的(《变更管理》篇的窗口约束),要用配置中心或 JMX 动态下发,并且"开了记得关"——debug 常驻的生产节点,磁盘和 IO 会被教做人。
保留期对齐审计要求(《审计日志》篇的 WORM 与哈希链),交易流水型日志的保存年限不是运维决定,是合规决定。
trace 体系的价值在事故里才兑现:平时觉得每个请求多带几十字节是浪费,出一次大事就知道,能把定位时间从小时压到分钟的东西,没有替代品。