ThinkPHP链路追踪需在中间件生成trace_id并注入请求上下文,HTTP/RPC/消息队列调用时手动透传headers,日志通过setContext+formatter自动注入,异步任务需序列化上下文,字段命名须统一。

ThinkPHP 接口调用链路中如何透传 trace_id 和自定义字段
必须在请求入口就生成并注入上下文,否则后续所有子调用(HTTP、RPC、消息队列)都会丢失链路标识。ThinkPHP 默认不维护跨请求生命周期的追踪上下文,得自己接管。
-
trace_id建议在app/middleware/TraceMiddleware.php中生成(用uniqid('', true)或ramsey/uuid),并写入think\facade\Request的 header 或 attribute,别只存 session 或 cookie - 自定义字段(如
user_id、tenant_code)需从原始请求解析后,一并塞进上下文对象,不能等 Controller 里再拼——中间件之后的组件(如日志、HttpClient)可能已开始打点 - 若用了
thinkphp/helper的Http类发下游请求,必须手动把trace_id和自定义字段加到 headers:比如['X-Trace-ID' => $traceId, 'X-User-ID' => $userId]
ThinkPHP HttpClient 调用时 trace_id 为啥没透传到下游服务
因为默认的 think\HttpClient 实例不自动继承当前请求的 headers,它是个全新发起的客户端,上下文是空的。你看到下游日志里 X-Trace-ID 缺失,不是网络问题,是代码漏了显式透传。
- 不要依赖全局单例
HttpClient::send(),它无法拿到当前请求上下文;改用HttpClient::create()->withHeaders([...])显式注入 - 如果项目封装了统一的 API 调用类(比如
App\Service\ApiClient),务必在构造或调用前从think\facade\Request或自定义 Context 类里取trace_id和扩展字段 - 注意
withHeaders()是链式调用,必须在get()/post()前调用,且不能复用同一个实例跨请求传递(避免 header 污染)
Log 日志里看不到 trace_id?检查 context 注入时机和格式
Log 驱动(如 File 或 Monolog)默认不读取请求上下文,即使你 middleware 里设了 Request::header('X-Trace-ID'),log formatter 也看不到,除非你主动把上下文塞进日志 record。
- 在
app/provider/LogServiceProvider.php或config/log.php的 formatter 配置中,确保使用支持上下文的 handler,例如Monolog\Handler\StreamHandler配合Monolog\Formatter\LineFormatter,并启用$includeContext = true - 更直接的方式:在
app/middleware/TraceMiddleware.php的handle()结束前,调用think\facade\Log::setContext([...]),传入['trace_id' => $traceId, 'user_id' => $userId] - 避免用
Log::info('msg', ['trace_id' => ...])手动传——容易漏、难维护;统一走setContext+ formatter 自动注入
多级调用(HTTP → HTTP → DB)下自定义字段丢失的常见原因
链路断在第二跳或第三跳,往往不是技术限制,而是「以为透传了」但实际上没生效。尤其当调用链涉及异步任务(如 think-queue)或协程(swoole)时,PHP 生命周期重置会导致上下文彻底清空。
立即学习“PHP免费学习笔记(深入)”;
- 异步任务投递前,必须序列化当前上下文(包括
trace_id、tenant_code等),通过job的$data字段带过去,消费时再反序列化并重建Context对象 - Swoole 场景下,
Request对象在 worker 进程中不可复用,需改用Co\Http\Client并手动 set headers;同时禁用think\swoole的自动 request 绑定(防止覆盖) - DB 查询日志想带 trace_id?别动
think\db\Connection的trigger,直接在Db::listen()回调里读Log::getContext(),它已被前面 middleware 初始化过
最易被忽略的是:自定义字段名在上下游系统间不一致(比如上游传 X-Tenant-Code,下游却读 X-TenantID),这种错不会报异常,只会静默丢数据。透传字段建议统一用小写+短横线,并在团队内固化命名表。



















