ThinkPHP需手动实现分布式链路追踪:在中间件入口优先读X-Trace-ID请求头生成trace_id并绑定Log::setContext(),日志format必须含{context},HTTP调用须显式透传X-Trace-ID头。

ThinkPHP 本身不带分布式链路追踪能力,Log::setContext() 只管本地日志上下文,跨请求、跨服务时 trace_id 会断掉。想让日志串得起来,必须手动控制 ID 生成、透传、注入和格式化四个环节,缺一不可。
如何在入口生成并绑定 trace_id 到日志上下文
别等进到控制器再生成,必须在中间件里完成,且要优先读上游传来的 X-Trace-ID,不是业务参数里的 trace_id。
-
$traceId = $request->header('X-Trace-ID') ?: uniqid('t_', true)—— 用header()读,不用input(),防污染 - 生成后立刻调用
Log::setContext(['trace_id' => $traceId]),否则日志里压根不会出现该字段 - 不要存到
session()或cookie(),它们在 HTTP 调用中无法透传给下游服务 - 验证是否生效:在中间件里加一句
Log::debug('trace_id set'),然后查日志文件看有没有trace_id字段
为什么日志里始终看不到 trace_id
大概率是日志格式没配对,Log::setContext() 不是“打了就显示”,它依赖日志驱动的 format 字符串解析。
- 检查
config/log.php中对应通道的'format'配置,必须包含{context}占位符,例如:'format' => '[{datetime}] {level} {message} {context}' - 别用
Log::info('msg', ['trace_id' => $id])这种写法——它只是把数组当附加参数打进去,不会合并进上下文 - 如果用了 Monolog 驱动,确认
Monolog\Handler\StreamHandler的 formatter 是支持 context 的版本(如LineFormatter并启用includeContext = true) - 本地开发可临时加
var_dump(Log::getLog())查当前上下文内容
HTTP 请求发出时如何确保 trace_id 透传给下游
ThinkPHP 的 think\Http、GuzzleHttp\Client 等默认都不带自定义 header,漏掉这步,链路就在你这断了。
立即学习“PHP免费学习笔记(深入)”;
- 用
think\facade\Http时,必须显式调用withHeaders(['X-Trace-ID' => $traceId]),例如:Http::withHeaders(['X-Trace-ID' => $traceId])->post($url, $data) - 用
GuzzleHttp\Client时,在headers数组里补上:'X-Trace-ID' => $traceId - 调用第三方 SDK(如阿里云 OSS、微信支付)时,它们通常不支持 header 注入,得改造成手动 HTTP 请求,或包一层代理中间件
- 注意:如果用了
think-swoole,还需确保协程上下文里能正确取出$traceId,别被协程变量覆盖
多级服务间 trace_id 被覆盖或不一致
常见于 A → B → C 流程中,B 收到请求后没原样转发 X-Trace-ID,或者 C 自己又生成了一个新 ID,结果链路分裂成两段。
- 每个服务都必须遵循同一规则:收到就存、发出去就带,不加工、不替换
- 下游服务收到请求后,第一件事就是
$request->header('X-Trace-ID'),而不是uniqid()新建 - 若用 SkyWalking PHP Agent,注意它默认全量采集,会绕过你的中间件逻辑;需设
SW_AGENT_DISABLE=1,改用手动 tracer 控制 - 验证是否真透传成功:在 C 服务日志里查
X-Trace-ID值,和 A 发出时是否完全一致(包括大小写、前缀、长度)
最易被忽略的是日志格式配置和 HTTP 请求头显式透传这两步——前者导致 ID 写不进日志,后者导致 ID 传不到下游,表面看一切正常,实际链路早已断裂。



















