Laravel 9异常处理应精准识别、分类响应、分级记录:仅拦截自定义业务异常,保留框架对ValidationException等的原生处理;API响应须用expectsJson()或路径判断返回JSON;QueryException需提取底层错误并过滤敏感信息;高频可预期异常可加入$dontReport。

在 Laravel 9 中,异常处理的核心是 App\Exceptions\Handler 类。关键不是“捕获所有异常”,而是**精准识别、分类响应、分级记录**——业务异常要可读可监控,框架底层异常(如验证失败、资源未找到)则应保留 Laravel 原有友好行为,避免破坏表单错误提示或 404 页面。
只拦截你真要干预的业务异常
别一上来就重写 render() 并直接 return 自定义响应。Laravel 对 ValidationException、ModelNotFoundException、AuthorizationException 等已有成熟处理逻辑,绕过会导致:
- 表单提交失败时,
$errors变量不渲染,前端收不到字段级错误 -
Route::get('/post/{id}')找不到记录时返回白屏 500,而不是预期的 404 视图或 JSON
正确做法是在 render() 中做类型判断:
- 用
$exception instanceof BusinessException(你自定义的业务异常类)来识别需特殊处理的场景 - 对
ValidationException,可调用$exception->errors()提取字段错误,再包装成统一 API 格式,但别丢掉原$exception->response()的语义 - 对
ModelNotFoundException,网页请求返回 404 视图,API 请求用response()->json(['message' => 'Not found'], 404)
API 请求必须返回 JSON + 准确状态码
不能依赖 Accept: application/json 头判断,它容易被忽略或伪造。用 Laravel 内置方法更可靠:
-
$request->expectsJson()—— 同时检查Accept头、X-Requested-With: XMLHttpRequest和路径是否含/api/ - 对
QueryException,别一律返回 500:MySQL 错误码 1062(重复键)应转为 409 Conflict,1452(外键约束)建议转为 400 Bad Request - 中间件中抛出的异常可能跳过路由层,
$request->expectsJson()不可用,此时改用$request->is('api/*')判断路径前缀
日志里要看到真实数据库错误
QueryException 是 Laravel 包装后的异常,原始错误藏在底层驱动里:
- 直接记
$exception->getMessage()只会得到 “Integrity constraint violation”,毫无排查价值 - 在
report()方法中加判断:if ($exception instanceof QueryException && $prev = $exception->getPrevious()) { \Log::error('DB raw error', ['msg' => $prev->getMessage()]); } - 敏感字段(如密码、token、手机号)若出现在 SQL 或堆栈中,需手动过滤后再记录,不能原样打日志
哪些异常可以不记日志?
高频、可预期、无故障含义的异常,不该刷屏日志。在 register() 方法中配置 $dontReport:
- 默认已包含
ValidationException::class、ModelNotFoundException::class、AuthorizationException::class - 你自己写的
InsufficientBalanceException如果只是流程控制用途(比如跳转到充值页),也可加入$dontReport - 但如果该异常代表系统异常(如支付网关不可用),就不该屏蔽,应保留在日志中并触发告警


















