最简单稳定的方式是在handler::report()中补全用户和请求信息,通过防御性判断(如runninginconsole、request存在性、auth检查)安全注入上下文,并结构化记录trace与队列任务链路。

直接在 App\Exceptions\Handler::report() 里补全用户和请求信息,是最简单、最稳定的方式——不用改中间件、不依赖 request 生命周期,也不怕队列任务里 context 丢失。
为什么不能只靠 Log::error() 默认行为
默认的 report() 方法调用 Log::error() 时,只传了异常消息和堆栈,auth()->id() 和 request()->fullUrl() 这类上下文根本没进去。更麻烦的是:如果异常发生在队列任务、命令行或异步进程里,request() 可能为 null,auth() 也可能未初始化,直接调用会报错或写入空值。
在 report() 中安全注入用户与请求上下文
核心是加一层防御性判断,且只在上下文可用时才读取:
- 用
app()->runningInConsole()排除 Artisan 命令场景 - 用
request() && request()->fullUrl()判断 HTTP 请求是否有效 - 用
auth()->check() && auth()->id()确保用户已登录再取 ID - 把所有上下文塞进一个数组,作为第二个参数传给
Log::error()
示例代码:
public function report(Throwable $throwable): void
{
$context = [];
if (! app()->runningInConsole()) {
if (request() && request()->fullUrl()) {
$context['url'] = request()->fullUrl();
$context['method'] = request()->method();
}
if (auth()->check()) {
$context['user_id'] = auth()->id();
$context['user_email'] = auth()->user()?->email;
}
}
$context['ip'] = request()?->ip();
$context['environment'] = app()->environment();
Log::error($throwable->getMessage(), array_filter($context));
parent::report($throwable);
}
API 异常要额外注意 trace 截断
API 场景下,前端不需要完整堆栈,但调试又得看关键几行。直接调 $throwable->getTraceAsString() 容易撑爆日志或暴露敏感路径。建议:
- 用
array_slice($throwable->getTrace(), 0, 5)取前 5 层调用栈 - 或用
substr($throwable->getTraceAsString(), 0, 1000)限长 - 避免在
context数组里塞原始 trace 字符串,改用结构化字段如'trace_top'
这样既保留定位能力,又不会污染日志格式或拖慢写入。
别漏掉队列任务里的上下文链路
队列任务执行时,request() 和 auth() 都不可用,但你可以在 dispatch 任务时显式传入 request()->id 或 auth()->id(),并在任务 handle() 里手动记录到日志 context 中。否则,report() 里写的那些判断全失效——这是最容易被忽略的一环。











