Laravel 9.x 中间件异常不被全局处理器捕获,因其在路由匹配前执行,裸 throw 普通异常会绕过 Handler::render();应优先 return 响应,必须 throw 时需继承 HttpResponseException 或在 Handler 中显式处理。

Laravel 9.x 中间件异常没被全局处理器捕获,不是因为写错了,而是执行时机和框架设计决定的——中间件在路由匹配前就运行,抛出的异常会绕过 App\Exceptions\Handler::render(),直接冒泡到 HTTP 内核外。处理的关键是不依赖“抛异常→自动捕获”这条链,而改用显式控制流。
中间件里别裸 throw Exception
Laravel 9.x 默认不会把普通 Exception 或自定义类(如 new \Exception('xxx'))交给 Handler 处理。它只识别两类:
- Laravel 自带的 HTTP 异常类(如
AuthorizationException、ValidationException) - 继承
HttpResponseException的异常(比如abort(403)底层就是抛这个)
裸 throw 普通异常的结果:白屏 500、日志可能不进 storage/logs/laravel.log、API 返回 HTML 错误页。
推荐做法:优先 return 响应,而非 throw
对业务拦截类逻辑(权限不足、参数非法、未登录),直接返回标准响应最稳妥:
- Web 路由:
return response()->view('errors.403', [], 403); - API 路由:
return response()->json(['message' => 'Forbidden'], 403);
这样完全避开异常机制,响应格式可控,也不会触发报警或堆栈记录。
必须用 throw 时,确保 Handler 能识别
如果确实需要统一监控(如 Redis 连接失败、配置缺失),按步骤来:
- 定义自定义异常类,继承
Exception,命名带前缀避免冲突(如MiddlewareConfigException) - 在
app/Exceptions/Handler.php的render()方法中显式处理:if ($exception instanceof MiddlewareConfigException) { \Log::error('Middleware config error', ['message' => $exception->getMessage()]); return response()->json(['error' => 'Service unavailable'], 503); } - 同时检查
$dontReport数组,确认没把你的异常类加进去(否则日志都记不到)。
验证中间件是否真执行到了异常点
很多“异常没捕获”其实是假象:
- 前置中间件(如
VerifyCsrfToken、TrimStrings)已提前返回响应,后续中间件根本没运行 -
handle()方法里return $next($request)写在了 throw 前面,导致 throw 永远不执行 - 使用
dd()或Log::debug('in middleware')打点,确认代码路径真实走到了异常位置。
不复杂但容易忽略











