直接修改 App\Exceptions\Handler 可实现可控可扩展的错误处理;自定义 404/500 页面需置于 resources/views/errors/ 下并命名为纯数字(如 404.blade.php),且仅在 APP_DEBUG=false 时生效;render() 中可按异常类型、请求格式等差异化响应;report() 中应过滤日志并集成 Sentry;自定义异常类须继承 Exception 并复写 render();PHP 致命错误需由 Nginx 等 Web 服务器兜底。

直接改 App\Exceptions\Handler 就能实现真正可控、可扩展的错误处理,不需要动视图路径或硬编码响应逻辑。
自定义 404/500 页面必须放对位置且关掉 debug
resources/views/errors/ 目录下只能放纯数字命名的 Blade 文件,比如 404.blade.php、500.blade.php。Laravel 不会识别 NotFound.blade.php 或放在 views/layouts/ 下的文件。
-
APP_DEBUG=true时,500.blade.php永远不生效——框架强制显示异常堆栈 - 只有
APP_ENV=production且APP_DEBUG=false才会走你写的页面 - 某些致命错误(如语法错误、类未加载)发生在 Laravel 启动前,连
500.blade.php都没机会渲染,这时看到的是框架 fallback 页面 - 改完视图后记得清缓存:
php artisan view:clear
在 Handler::render() 中拦截特定异常并返回定制响应
这是最灵活、最贴近业务逻辑的处理点。你可以按异常类型、请求格式(HTML/JSON)、路由前缀做差异化响应。
- 判断是否为 API 请求:
$request->expectsJson()或自定义isApiCall($request)辅助方法 - 捕获
ModelNotFoundException并返回404视图,而不是默认跳转到404.blade.php - 对
ValidationException做 JSON 包装,确保errors字段结构统一 - 注意:不要在
render()里抛新异常,否则会触发递归调用
report() 方法里控制哪些异常该记日志、哪些该忽略
不是所有异常都值得记录。Laravel 默认会把所有未捕获异常写进日志,但你可以通过 $dontReport 属性或重写 report() 来过滤。
- 把常见预期异常加入
$dontReport,比如AuthenticationException、AuthorizationException - 在
report()里加条件判断,只上报生产环境的QueryException或ConnectionException - 第三方服务集成(如 Sentry)应放在这里,而不是
render(),避免影响响应生成 - 别忘了调用
parent::report($exception),否则基础日志行为会丢失
自定义异常类要复写 render() 而不是 throw new Exception
直接 throw new \Exception('xxx') 会绕过所有定制逻辑,变成通用 500。你应该继承 Exception 并在子类中定义 render() 行为。
- 例如
PermissionException可以固定返回403JSON,无需在每个控制器里重复判断 - 自定义异常类的
render()方法会被Handler::render()自动识别并委托处理 - 别在自定义异常里做重定向操作(如
redirect()),它应该只负责“怎么响应”,不负责“怎么跳” - 如果需要带上下文数据(如错误码、trace ID),建议通过构造函数传入,并在
render()中提取
真正容易被忽略的是异常发生时机:有些错误在服务容器注册前就崩溃了,Handler 根本没机会运行;还有些是 PHP 致命错误,连 Throwable 都捕获不到。这时候得靠 Web 服务器层(如 Nginx 的 error_page)兜底,而不是只依赖 Laravel。


















