Laravel队列任务中request_id丢失是因为其在新进程执行,不继承HTTP请求生命周期;解决方法是将trace_id显式传入任务构造函数并在日志处理器中通过全局变量或Monolog注入,确保跨任务和跨服务链路追踪。

为什么 Laravel 队列任务里 request_id 会丢?
因为队列任务在新进程(如 php artisan queue:work)中执行,不继承 HTTP 请求生命周期,request()->id 或中间件注入的上下文变量全失效。你看到日志里一堆 request_id: null 或随机 UUID,不是没传,是根本没带进来。
核心解决思路:把链路 ID 主动“塞进”任务载荷,而不是依赖运行时环境。
- HTTP 请求中生成唯一
trace_id(例如用Str::uuid()),存入Request或IlluminateSupportFacadesLog的上下文 - 投递队列任务时,显式将
trace_id作为参数传入构造函数或dispatch()方法 - 任务类内部不再查
request(),而是从自身属性或$this->trace_id读取
dispatch() 时怎么安全透传 trace_id?
别用闭包或匿名函数传参——序列化后上下文丢失;也别依赖全局静态变量,多 worker 并发下会串。
推荐两种可靠方式:
- 通过任务构造函数注入:
SendNotification::dispatch($user, $trace_id);
并在SendNotification类中声明public function __construct($user, $trace_id) { $this->trace_id = $trace_id; } - 用
onQueue()+ 自定义with()`链式调用(需重写__serialize())不如构造函数直接,但若已用Job基类,可在handle()开头统一从$this->job->getRawBody()解析——不推荐,耦合 RabbitMQ/Redis 协议细节
注意:Laravel 10+ 的 dispatchSync() 会保留当前上下文,但仅用于测试或极低并发场景,不能替代异步链路追踪。
日志里怎么让 trace_id 自动出现在每条记录中?
靠改 config/logging.php 的 tap 配置 + 自定义 Monolog 处理器最稳,不侵入业务代码。
新建一个 app/Logging/TraceIdProcessor.php:
use MonologProcessorProcessorInterface;
class TraceIdProcessor implements ProcessorInterface
{
public function __invoke(array $record): array
{
$record['extra']['trace_id'] = request()->hasHeader('X-Trace-ID')
? request()->header('X-Trace-ID')
: (app()->runningInConsole() && isset($GLOBALS['current_trace_id'])
? $GLOBALS['current_trace_id']
: 'unknown');
return $record;
}
}
然后在队列任务的 handle() 开头手动设一次全局变量:
$GLOBALS['current_trace_id'] = $this->trace_id;
- 不要用
Log::withContext()每次手动塞,容易漏、难维护 - 避免在
AppServiceProvider中监听JobProcessing事件去设上下文——事件触发时机晚于日志初始化,首条日志仍无trace_id - 如果你用的是
stack日志通道,确保所有子通道(single,daily)都经过同一tap
Redis 队列里能看到 trace_id 吗?怎么看?
能,但得知道它在哪——Laravel 默认把整个任务对象序列化成 JSON 存进 Redis 的 queues:default 列表,trace_id 就在 data.command 字段里(Laravel 9+ 是 data.job)。
调试时可以直接用 redis-cli 查:
LRANGE queues:default 0 1
你会看到类似:
{"uuid":"xxx","displayName":"App\Jobs\SendNotification","job":"Illuminate\Queue\CallQueuedHandler@call","maxTries":1,"delay":null,"timeout":60,"data":{"command":"O:27:"App\Jobs\SendNotification":3:{s:5:"trace_id";s:36:"123e4567-e89b-12d3-a456-426614174000";..."}}
- 别指望
artisan tinker里Queue::size()返回 trace_id——它只返回长度 - 如果用了 Horizon,它的 UI 默认不展示自定义字段,需在任务类加
public function displayName()手动拼接显示 - 线上排查时,优先用
php artisan queue:listen --verbose看实际消费时的原始日志,比翻 Redis 更快
真正麻烦的不是传,是跨服务调用时的透传——比如队列任务里再发 HTTP 请求给另一个 Laravel 服务,那个服务的中间件要能识别并复用这个 trace_id,否则链路就断了。这得靠约定 Header 和统一 SDK,不是单个 Laravel 应用能闭环的事。


















