最有效方式是修改app\exceptions\handler的render()和report()方法;render()中用$request->expectsjson()区分网页与api请求,网页返回blade视图,api返回标准json响应;$exception变量仅在500.blade.php中可用,修改错误页后须执行php artisan view:clear。

直接改 App\Exceptions\Handler 类的 render() 和 report() 方法,是最有效、最可控的方式。其他方案(比如中间件拦截异常、全局 set_exception_handler)要么覆盖不全,要么绕过框架日志和上下文机制,容易漏报或格式错乱。
怎么用 render() 区分网页和 API 请求
$request->expectsJson() 是唯一可靠判断方式,它同时检查 Accept 头、X-Requested-With 和路径是否含 /api/;别用 $request->isJson(),它只看 Content-Type,而很多前端发 JSON 时根本没带这个头。
- 网页请求返回 Blade 视图(如
resources/views/errors/404.blade.php) - API 请求必须返回标准 JSON + 正确状态码,例如:
if ($throwable instanceof ModelNotFoundException && $request->expectsJson()) { return response()->json(['message' => 'Not found'], 404); } - 别在
render()里调用dd()或抛新异常,否则可能触发递归崩溃
为什么 QueryException 总显示 “Integrity constraint violation”
因为 Laravel 把原始数据库错误包在了 $exception->getPrevious() 里,直接打 $exception->getMessage() 只能看到通用提示,查不到具体字段和值。
- 在
report()中加判断:if ($exception instanceof QueryException && $previous = $exception->getPrevious()) { \Log::error('DB error detail', ['raw' => $previous->getMessage()]); } - MySQL 错误码 1062(重复键)、1452(外键约束)属于业务可预期错误,不该当 500 上报,应提前捕获并转为 400 或自定义异常
- 线上环境禁止开启
DB::enableQueryLog(),它会吃内存且泄露 SQL 参数
哪些异常该加进 $dontReport
只针对真正“不值得记日志”的高频、可预期、有明确语义的异常,比如表单验证失败、权限拒绝、资源未找到。
- 在
register()方法里添加:$this->dontReport([ ValidationException::class, AuthorizationException::class, ModelNotFoundException::class, ]);
- 自己写的
InsufficientBalanceException这类业务异常,如果需要监控,就别放$dontReport;如果只是流程跳转用,可以加 -
$dontReport不是“忽略异常”,而是跳过日志记录——它不影响render()行为,也不影响响应生成
自定义错误页面为什么没生效
Laravel 会自动查找resources/views/errors 下对应状态码的 Blade 模板(如 404.blade.php),但有几个硬性前提:
- 模板文件名必须严格匹配 HTTP 状态码(
500.blade.php,不能叫server-error.blade.php) - 模板中不能含
dd()、dump()等调试函数,否则二次异常会 fallback 到默认页 - 修改后必须运行
php artisan view:clear,否则缓存导致更改不生效 -
$exception变量仅在500.blade.php中可用,其他状态码页(如 404)拿不到异常实例
真实项目里最容易被忽略的是:API 异常走 render(),网页异常走视图文件,两者逻辑完全隔离。混用或漏判 $request->expectsJson(),会导致用户看到 JSON 响应嵌在 HTML 里,或者 404 页面返回 200 状态码。











