一个编号串起四个团队的日志
一句话总结
故障会议的第一个问题总是一样的——“那笔交易,走到哪了?”要在几分钟内回答这个问题,就必须所有环节都在日志里留下同一个编号(GUID),并且能把这些日志用同一种格式、同一个时区串起来。如果每个环节的编号不同,或者时刻各式各样,答案就会变成花几个小时的猜测。
为什么需要它
一笔转账会依次经过渠道(MCI)→ 枢纽(EAI)→ 账户系统(CORE)→ 必要时的外联前置机(FEP)。客户打电话说“钱扣了,却显示转账失败”,四个团队就各自翻查自己的日志。MCI 是 key=value,枢纽是竖线分隔,账户系统是 JSON,外联前置机是定长列。账户系统用 UTC 记录,其余用韩国时间。而最糟的情况——枢纽在调用账户系统时重新生成了编号。用渠道日志中的编号去搜索账户系统的日志,什么都搜不到。“我们这边看不到那笔交易”重复了四遍,最后只能按时刻和金额找相似的行,靠手工串起来。
第 1 模块之所以把 GUID 定为“由最初创建的系统签发一次,所有环节原样携带”,原因就在这里。本模块处理的是:用遵守这个约定的日志能做什么,以及如何修复违反约定的中继。
工作原理
规范化。把格式不同的日志转换成相同的列(guid、hop、时刻、事件、响应码)。与其要求四个团队修改原始日志的格式,不如由读取的一方统一一次更快。不过,新建的系统从一开始就要用结构化日志(一行 JSON,固定的键)来写——不用正则表达式就能读取。
时区。日志时刻必须带有时区。2026-09-23T00:02:08.268Z 中的 Z 表示 UTC,换成韩国时间(UTC+9)就是 09:02:08.268。没有时区的时刻(2026-09-23 09:02:08)是“只有知道是哪个时区的人才能读懂”的时刻。如果不知道只有一个系统是 UTC 就把它们串起来,账户系统看上去就会比枢纽早 9 个小时处理。
按时间顺序串联。把同一个 GUID 的行按时间顺序排列,交易的路径就出来了。加上距第一个事件的经过时间,就能看出时间花在哪个环节。不过,不同服务器的时钟会有微小偏差(即使各自用 NTP 校准,也有几毫秒),所以毫秒级的跨环节顺序只作参考。同一台服务器内部两个事件的差值(账户系统的 RECV → APPLY)则是可信的。
丢失的交易。枢纽写了“已发送(OUT)”,而账户系统没有“已收到(RECV)”,那么这笔交易就在二者之间消失了。可能是网络中断,也可能是在账户系统前端被丢弃。这样的交易,枢纽应当已经以 E901 作答(第 4 模块),并且必须通过第 8 模块的查询来确定。只有用 GUID 串联,才能机械地提取出这份清单。
拆解缓慢的交易。把总耗时超过 3 秒的交易按环节拆分——账户系统内部耗费的时间(RECV → APPLY)、外部机构耗费的时间(REQ → RSP)。必须拆解单笔交易而不是看平均值,才能分清“账户系统慢的日子”和“某个机构慢的日子”。
传播规则。中继要把收到的 GUID 原样放到下一个环节。在报文环节,是报文头中 GUID 的位置,在 HTTP 环节,是请求头(本课程用 X-GUID)和正文。而且还有标准。W3C Trace Context 规定了通过 HTTP 传递跟踪上下文的 traceparent 头。形式为 버전-trace-id-parent-id-flags(其中首项的韩文意为“版本”),在版本 00 中,trace-id 是 16 字节(32 位小写十六进制),parent-id 是 8 字节(16 位),二者都是全 0 则无效。trace-id 整个交易只有一个,parent-id 每次调用重新生成——所以可以把一笔交易内的多次调用画成父子关系。由于把 LH-STD 的 GUID 定为与 trace-id 相同的形态(第 1 模块),把 GUID 原样放入 trace-id,报文环节和 HTTP 环节的跟踪就不会中断,而能够衔接起来。
日志必填列。每行环节事件中,至少要有时刻(含时区)、GUID、环节名称、事件、响应码。金额、账号之类的个人信息不要放入,或者要遮蔽——跟踪只需要一个编号就够了。
在现场相遇的样子
最常见的是中继重新生成 GUID 的事故。有人为了遵守“我们系统的交易编号规则”,丢掉收到的编号,换上了自己的编号。出发点是好的,但跟踪在那个环节就断了。如果确实需要自己的编号,就另外写一份,而收到的 GUID 要原样传递。第二种是没有时区的日志。从某一台服务器的时区设置被改动的那天起,日志就偏移了 9 个小时,没有人发觉。第三种是在错误路径中不记录 GUID 的日志。正常流程会打印 GUID,而异常处理块里的一行日志却漏掉了——真正需要的恰恰是那一行。
下一项实验要做什么
把一个工作日的四个环节的日志(MCI、EAI、CORE、FEP,四种格式,两种时区)规范化为同一种格式,并统一为韩国时间。制作绘制单个 GUID 路径的 trace.py、没有到达账户系统的交易清单,以及缓慢交易的环节拆解。最后修复重新生成 GUID 的中继(relay_buggy.py),使它原样携带收到的 GUID 并留下结构化日志,并在 HTTP 环节加上 traceparent。