直接修改 App\Exceptions\Handler::report() 可安全添加请求ID、用户ID、URL、IP等上下文,需规避命令行场景、避免额外查询与全量堆栈、按环境配置过滤字段,并用 try/catch 防止上报逻辑异常导致原始错误丢失。

直接改 App\Exceptions\Handler::report() 就能加上下文,但必须注意两点:不破坏原有日志结构、不引入运行时开销。其他“打补丁式”方案(比如监听器、中间件)容易漏掉异常源头或干扰事务一致性。
在 report() 中注入请求与用户信息
默认的 report() 只记录异常本身,缺少请求 ID、当前用户、URL、IP 等关键上下文。这些信息在排查分布式调用或复现偶发错误时极为重要。
- 用
request()->fullUrl()和request()->ip()获取入口信息,但需先判断app()->runningInConsole(),避免命令行场景报错 - 用户信息优先取
auth()->id(),而不是auth()->user()—— 后者可能触发未预期的 Eloquent 查询 - 添加唯一追踪 ID:
request()->header('x-request-id', uniqid('req_', true)),便于串联 Nginx、Laravel、DB 日志 - 不要在
report()里调用Log::channel(...),而是统一走parent::report($exception)的默认通道,否则可能绕过 Monolog 的处理器链(如 Slack、Sentry)
捕获模型操作失败时的 SQL 与绑定参数
当 ModelNotFoundException 或 QueryException 抛出时,仅看堆栈无法知道是哪条 SQL 导致的。需要把查询上下文一并塞进日志上下文,而不是只记异常消息。
- 检查
$exception是否为Illuminate\Database\QueryException,再读$exception->getSql()和$exception->getBindings() - 避免直接拼接
$exception->getTraceAsString()全量堆栈 —— 它可能超长、含敏感变量;改用collect($exception->getTrace())->take(3)->map(...)截取关键调用帧 - 对
ModelNotFoundException,额外记录$exception->getModel()和$exception->getId()(如果可用),比“找不到记录”更有诊断价值 - 别在
report()里执行新 DB 查询(例如查用户角色),这会拖慢异常上报,甚至引发二次异常
区分环境写入不同字段
开发环境可以打全量上下文,生产环境则要精简:去掉 trace、隐藏敏感字段、压缩日志体积。硬编码判断 app()->environment('local') 不够灵活,应依赖配置驱动。
- 在
config/logging.php中定义一个'context_fields'配置项,按环境返回不同键名数组,例如生产环境只保留['request_id', 'url', 'method', 'user_id'] - 用
array_intersect_key($context, config('logging.context_fields'))动态过滤,避免 if-else 分支污染逻辑 - 对密码、token、银行卡号等字段,提前在中间件或全局 request filter 中脱敏,不在
report()里做字符串替换 —— 那样容易漏、难维护 - 不要把
$_SERVER整个 dump 进日志,它含 CGI 变量、PHP 路径等无意义字段,且可能泄露部署细节
最易被忽略的是:report() 方法本身也可能抛出异常(比如日志通道不可写、Sentry SDK 初始化失败)。Laravel 不会二次捕获它 —— 这会导致原始异常丢失。真要加复杂逻辑,务必用 try/catch 包裹,并在内层 catch 中降级为 error_log() 输出到 PHP 错误日志。


















