Laravel队列中trace_id为null是因为queue:work启动新进程,HTTP上下文完全隔离;必须在dispatch时显式传入trace_id,并在handle中通过$GLOBALS['current_trace_id']供日志处理器读取。

链路追踪不是“加了就灵”的开关,它只在你明确知道要查什么、在哪查、怎么比的时候才真正起效。Laravel 默认不带跨进程链路透传能力,尤其在队列、HTTP/3 异步请求、协程调度等场景下,trace_id 断裂是常态,不是 bug。
为什么 Laravel 队列里 trace_id 总是 null
因为 php artisan queue:work 启动的是全新 PHP 进程,request() 对象、中间件注入的上下文、甚至 $_SERVER 环境变量都和原始 HTTP 请求完全隔离。你看到日志里 trace_id: null,不是没生成,是根本没带进来。
- 别依赖
request()->header('X-Trace-ID')在handle()里取——它永远为空 - 别用
Log::withContext()包一层就以为万事大吉——它只作用于当前请求生命周期 - 别在任务里调
Str::uuid()重生成——这会把一条链路切成多段,失去关联性
dispatch() 时怎么安全透传 trace_id
必须把 trace_id 当作业务参数显式塞进任务载荷,且确保序列化/反序列化过程不丢失。闭包、匿名函数、静态属性都不行,只有构造函数或 dispatch() 的参数列表是可靠通道。
- 推荐方式:在控制器中生成并传入构造函数
SendNotification::dispatch($user, $trace_id);,任务类声明public function __construct(User $user, string $trace_id) - 避免方式:用
onQueue()->with(['trace_id' => $id])—— 这依赖 Job 基类重写__serialize(),耦合协议细节,且在 Laravel 12 的协程 worker 下行为不稳定 - 补充动作:在
handle()开头立刻设全局变量$GLOBALS['current_trace_id'] = $this->trace_id;,供后续 Monolog 处理器读取
如何让日志每条都带上 trace_id
靠改 config/logging.php 的 tap 配置 + 自定义 Monolog Processor 最稳,不侵入业务逻辑,也不依赖中间件顺序。
新建 app/Logging/TraceIdProcessor.php:
use Monolog\Processor\ProcessorInterface;
class TraceIdProcessor implements ProcessorInterface
{
public function __invoke(array $record): array
{
$record['extra']['trace_id'] = request()->hasHeader('X-Trace-ID')
? request()->header('X-Trace-ID')
: ($GLOBALS['current_trace_id'] ?? 'unknown');
return $record;
}
}
- 在
config/logging.php的channels下任一通道(如stack或single)添加'tap' => [App\Logging\TraceIdProcessor::class] - 注意:
request()在队列里不可用,所以 fallback 到$GLOBALS['current_trace_id']是必须的 - 别用
Log::useContext()动态注入——它只对当前 logger 实例生效,无法覆盖 Telescope、DB 日志等子通道
OpenTelemetry 埋点后怎么看并发瓶颈
装了 open-telemetry/laravel 只是第一步,关键在导出后的数据怎么筛。默认 OTEL_TRACES_SAMPLER=always_on 会压垮存储,生产环境建议切为 traceidratio 并配 0.1 采样率。
- 重点看 Span 名为
queue.process和db.query的耗时分布:如果 P95 > 500ms,说明单任务处理过重;如果大量 Span 出现status_code=ERROR且 error.type 是ConnectionException,大概率是数据库连接池打满 - 对比相同 trace_id 下多个子 Span 的 start_time:若
redis.get和db.query时间严重重叠,说明没做 pipeline 或事务隔离,存在竞争 - Laravel 12 的异步任务调度(
scheduler:work)会生成独立 trace_id,需在调度层统一注入父 trace_id,否则无法关联到上游 HTTP 请求
链路 ID 断掉本身不可怕,可怕的是你把它当成“已埋点=可观测”,结果在 Grafana 里刷半天找不到那条超时请求的真实路径。真正的并发问题往往藏在 trace_id 断裂的缝隙里——比如某个队列任务漏传了 ID,导致它触发的 5 次 Redis 调用全部散落在不同 trace 中,你以为是缓存慢,其实是链路丢了。


















