跳至内容

错误处理

介绍

当您启动一个新的 Laravel 项目时,错误和异常处理已经为您配置好了;但是,在任何时候,您都可以使用withExceptions应用程序中的方法bootstrap/app.php来管理应用程序如何报告和呈现异常。

$exceptions传递给闭包的对象withExceptions的一个实例Illuminate\Foundation\Configuration\Exceptions,负责管理应用程序中的异常处理。我们将在本文档中深入探讨此对象。

配置

debug配置文件中的选项决定config/app.php了实际向用户显示多少有关错误的信息。默认情况下,此选项设置为遵循APP_DEBUG存储在.env文件中的环境变量的值。

在本地开发期间,应将APP_DEBUG环境变量设置为true在生产环境中,此值应始终为false。如果在生产环境中将该值设置为true,则可能会将敏感配置值暴露给应用程序的最终用户。

处理异常

报告异常

在 Laravel 中,异常报告用于记录异常或将其发送到SentryFlare等外部服务。默认情况下,异常将根据您的日志配置进行记录。但是,您可以自由地以任何您想要的方式记录异常。

如果需要以不同的方式报告不同类型的异常,可以report在应用程序中使用 exception 方法bootstrap/app.php来注册一个闭包,当需要报告特定类型的异常时,该闭包就会被执行。Laravel 会通过检查闭包的类型Prompts来确定闭包报告的异常类型:

1use App\Exceptions\InvalidOrderException;
2 
3->withExceptions(function (Exceptions $exceptions) {
4 $exceptions->report(function (InvalidOrderException $e) {
5 // ...
6 });
7})

当你使用 该方法注册自定义异常报告回调时report,Laravel 仍将使用应用程序的默认日志配置来记录异常。如果你希望阻止异常传播到默认日志堆栈,则可以stop在定义报告回调或false从回调返回时使用 该方法:

1use App\Exceptions\InvalidOrderException;
2 
3->withExceptions(function (Exceptions $exceptions) {
4 $exceptions->report(function (InvalidOrderException $e) {
5 // ...
6 })->stop();
7 
8 $exceptions->report(function (InvalidOrderException $e) {
9 return false;
10 });
11})

要针对给定的异常定制异常报告,您还可以利用可报告异常

全局日志上下文

如果可用,Laravel 会自动将当前用户 ID 作为上下文数据添加到每个异常的日志消息中。你可以context在应用程序bootstrap/app.php文件中使用 exception 方法定义自己的全局上下文数据。此信息将包含在应用程序写入的每个异常日志消息中:

1->withExceptions(function (Exceptions $exceptions) {
2 $exceptions->context(fn () => [
3 'foo' => 'bar',
4 ]);
5})

异常日志上下文

虽然为每条日志消息添加上下文很有用,但有时特定异常可能具有您希望包含在日志中的独特上下文。通过context在应用程序的某个异常上定义一个方法,您可以指定与该异常相关的任何数据,这些数据应添加到异常的日志条目中:

1<?php
2 
3namespace App\Exceptions;
4 
5use Exception;
6 
7class InvalidOrderException extends Exception
8{
9 // ...
10 
11 /**
12 * Get the exception's context information.
13 *
14 * @return array<string, mixed>
15 */
16 public function context(): array
17 {
18 return ['order_id' => $this->orderId];
19 }
20}

Helpersreport

有时您可能需要报告异常,但继续处理当前请求。report辅助函数允许您快速报告异常,而无需向用户呈现错误页面:

1public function isValid(string $value): bool
2{
3 try {
4 // Validate the value...
5 } catch (Throwable $e) {
6 report($e);
7 
8 return false;
9 }
10}

删除重复报告的异常

如果您在整个应用程序中使用该report功能,您可能会偶尔多次报告相同的异常,从而在日志中创建重复的条目。

如果您想确保单个异常实例仅报告一次,您可以dontReportDuplicates在应用程序bootstrap/app.php文件中调用异常方法:

1->withExceptions(function (Exceptions $exceptions) {
2 $exceptions->dontReportDuplicates();
3})

现在,当report使用相同的异常实例调用帮助程序时,只会报告第一次调用:

1$original = new RuntimeException('Whoops!');
2 
3report($original); // reported
4 
5try {
6 throw $original;
7} catch (Throwable $caught) {
8 report($caught); // ignored
9}
10 
11report($original); // ignored
12report($caught); // ignored

异常日志级别

当消息写入应用程序的日志时,消息会以指定的日志级别写入,该级别指示所记录消息的严重性或重要性。

如上所述,即使您使用该report方法注册了自定义异常报告回调,Laravel 仍将使用应用程序的默认日志配置记录异常;但是,由于日志级别有时会影响记录消息的渠道,您可能希望配置记录某些异常的日志级别。

为此,您可以level在应用程序bootstrap/app.php文件中使用 exception 方法。此方法接收异常类型作为其第一个参数,并将日志级别作为其第二个参数:

1use PDOException;
2use Psr\Log\LogLevel;
3 
4->withExceptions(function (Exceptions $exceptions) {
5 $exceptions->level(PDOException::class, LogLevel::CRITICAL);
6})

按类型忽略异常

在构建应用程序时,可能会出现一些您不想报告的异常类型。要忽略这些异常,您可以dontReport在应用程序bootstrap/app.php文件中使用 exception 方法。任何提供给此方法的类都不会被报告;但是,它们可能仍然具有自定义渲染逻辑:

1use App\Exceptions\InvalidOrderException;
2 
3->withExceptions(function (Exceptions $exceptions) {
4 $exceptions->dontReport([
5 InvalidOrderException::class,
6 ]);
7})

或者,你可以简单地用该接口“标记”一个异常类Illuminate\Contracts\Debug\ShouldntReport。当一个异常被标记为该接口时,Laravel 的异常处理程序将永远不会报告该异常:

1<?php
2 
3namespace App\Exceptions;
4 
5use Exception;
6use Illuminate\Contracts\Debug\ShouldntReport;
7 
8class PodcastProcessingException extends Exception implements ShouldntReport
9{
10 //
11}

Laravel 内部已经为你忽略了某些类型的错误,例如由无效的 CSRF 令牌生成的 404 HTTP 错误或 419 HTTP 响应导致的异常。如果你想让 Laravel 停止忽略特定类型的异常,你可以stopIgnoring在应用程序bootstrap/app.php文件中使用 exception 方法:

1use Symfony\Component\HttpKernel\Exception\HttpException;
2 
3->withExceptions(function (Exceptions $exceptions) {
4 $exceptions->stopIgnoring(HttpException::class);
5})

渲染异常

默认情况下,Laravel 异常处理器会将异常转换为 HTTP 响应。不过,您可以为指定类型的异常注册自定义渲染闭包。您可以通过render在应用程序bootstrap/app.php文件中使用 exception 方法来实现。

传递给该方法的闭包render应该返回一个 的实例Illuminate\Http\Response,该实例可以通过response辅助函数生成。Laravel 将通过检查闭包的类型Prompts来确定闭包会渲染哪种类型的异常:

1use App\Exceptions\InvalidOrderException;
2use Illuminate\Http\Request;
3 
4->withExceptions(function (Exceptions $exceptions) {
5 $exceptions->render(function (InvalidOrderException $e, Request $request) {
6 return response()->view('errors.invalid-order', status: 500);
7 });
8})

你也可以使用render方法来覆盖 Laravel 或 Symfony 内置异常的渲染行为,例如NotFoundHttpException。如果传递给 该render方法的闭包没有返回值,则将使用 Laravel 的默认异常渲染:

1use Illuminate\Http\Request;
2use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
3 
4->withExceptions(function (Exceptions $exceptions) {
5 $exceptions->render(function (NotFoundHttpException $e, Request $request) {
6 if ($request->is('api/*')) {
7 return response()->json([
8 'message' => 'Record not found.'
9 ], 404);
10 }
11 });
12})

将异常渲染为 JSON

当渲染异常时,Laravel 会根据Accept请求的标头自动确定应将异常渲染为 HTML 还是 JSON 响应。如果你想自定义 Laravel 如何确定渲染 HTML 还是 JSON 异常响应,可以使用该shouldRenderJsonWhen方法:

1use Illuminate\Http\Request;
2use Throwable;
3 
4->withExceptions(function (Exceptions $exceptions) {
5 $exceptions->shouldRenderJsonWhen(function (Request $request, Throwable $e) {
6 if ($request->is('admin/*')) {
7 return true;
8 }
9 
10 return $request->expectsJson();
11 });
12})

自定义异常响应

极少数情况下,你可能需要自定义 Laravel 异常处理器渲染的整个 HTTP 响应。为此,你可以使用如下respond方法注册一个响应自定义闭包:

1use Symfony\Component\HttpFoundation\Response;
2 
3->withExceptions(function (Exceptions $exceptions) {
4 $exceptions->respond(function (Response $response) {
5 if ($response->getStatusCode() === 419) {
6 return back()->with([
7 'message' => 'The page expired, please try again.',
8 ]);
9 }
10 
11 return $response;
12 });
13})

可报告和可渲染的异常

您无需在应用bootstrap/app.php文件中定义自定义报告和渲染行为,而是可以直接在应用的异常中定义reportrender方法。当这些方法存在时,框架会自动调用它们:

1<?php
2 
3namespace App\Exceptions;
4 
5use Exception;
6use Illuminate\Http\Request;
7use Illuminate\Http\Response;
8 
9class InvalidOrderException extends Exception
10{
11 /**
12 * Report the exception.
13 */
14 public function report(): void
15 {
16 // ...
17 }
18 
19 /**
20 * Render the exception as an HTTP response.
21 */
22 public function render(Request $request): Response
23 {
24 return response(/* ... */);
25 }
26}

如果您的异常扩展了已经可渲染的异常(例如内置的 Laravel 或 Symfony 异常),则可以false从异常的render方法返回以渲染异常的默认 HTTP 响应:

1/**
2 * Render the exception as an HTTP response.
3 */
4public function render(Request $request): Response|bool
5{
6 if (/** Determine if the exception needs custom rendering */) {
7 
8 return response(/* ... */);
9 }
10 
11 return false;
12}

如果你的异常包含仅在满足特定条件时才需要的自定义报告逻辑,则可能需要指示 Laravel 有时使用默认的异常处理配置来报告异常。为此,你可以false从异常的report方法 return 中实现:

1/**
2 * Report the exception.
3 */
4public function report(): bool
5{
6 if (/** Determine if the exception needs custom reporting */) {
7 
8 // ...
9 
10 return true;
11 }
12 
13 return false;
14}

您可以输入Prompts该report方法所需的任何依赖项,它们将由 Laravel 的服务容器自动注入到该方法中。

限制报告的异常

如果您的应用程序报告了大量异常,您可能需要限制实际记录或发送到应用程序的外部错误跟踪服务的异常数量。

要获取随机异常采样率,您可以throttle在应用bootstrap/app.php文件中使用 exception 方法。该throttle方法接收一个应返回Lottery实例的闭包:

1use Illuminate\Support\Lottery;
2use Throwable;
3 
4->withExceptions(function (Exceptions $exceptions) {
5 $exceptions->throttle(function (Throwable $e) {
6 return Lottery::odds(1, 1000);
7 });
8})

也可以根据异常类型进行有条件的采样。如果您只想对特定异常类的实例进行采样,则可以Lottery仅返回该类的实例:

1use App\Exceptions\ApiMonitoringException;
2use Illuminate\Support\Lottery;
3use Throwable;
4 
5->withExceptions(function (Exceptions $exceptions) {
6 $exceptions->throttle(function (Throwable $e) {
7 if ($e instanceof ApiMonitoringException) {
8 return Lottery::odds(1, 1000);
9 }
10 });
11})

Limit您还可以通过返回实例而不是来限制记录或发送到外部错误跟踪服务的异常的速率Lottery。如果您想要防止突发异常淹没日志(例如,当您的应用程序使用的第三方服务宕机时),这非常有用:

1use Illuminate\Broadcasting\BroadcastException;
2use Illuminate\Cache\RateLimiting\Limit;
3use Throwable;
4 
5->withExceptions(function (Exceptions $exceptions) {
6 $exceptions->throttle(function (Throwable $e) {
7 if ($e instanceof BroadcastException) {
8 return Limit::perMinute(300);
9 }
10 });
11})

默认情况下,限制将使用异常的类作为速率限制键。您可以使用by上的方法指定自己的键来自定义它Limit

1use Illuminate\Broadcasting\BroadcastException;
2use Illuminate\Cache\RateLimiting\Limit;
3use Throwable;
4 
5->withExceptions(function (Exceptions $exceptions) {
6 $exceptions->throttle(function (Throwable $e) {
7 if ($e instanceof BroadcastException) {
8 return Limit::perMinute(300)->by($e->getMessage());
9 }
10 });
11})

当然,您可以针对不同的异常返回Lottery和实例的Mix:Limit

1use App\Exceptions\ApiMonitoringException;
2use Illuminate\Broadcasting\BroadcastException;
3use Illuminate\Cache\RateLimiting\Limit;
4use Illuminate\Support\Lottery;
5use Throwable;
6 
7->withExceptions(function (Exceptions $exceptions) {
8 $exceptions->throttle(function (Throwable $e) {
9 return match (true) {
10 $e instanceof BroadcastException => Limit::perMinute(300),
11 $e instanceof ApiMonitoringException => Lottery::odds(1, 1000),
12 default => Limit::none(),
13 };
14 });
15})

HTTP 异常

一些异常描述了来自服务器的 HTTP 错误代码。例如,这可能是“页面未找到”错误 (404)、“未授权错误”(401),甚至是开发者生成的 500 错误。为了在应用程序的任何位置生成这样的响应,您可以使用以下abort帮助程序:

1abort(404);

自定义 HTTP 错误页面

Laravel 可以轻松显示针对各种 HTTP 状态代码的自定义错误页面。例如,要自定义 404 HTTP 状态代码的错误页面,请创建一个resources/views/errors/404.blade.php视图模板。此视图将针对应用程序生成的所有 404 错误进行渲染。此目录中的视图应根据其对应的 HTTP 状态代码进行命名。函数Symfony\Component\HttpKernel\Exception\HttpException引发的实例abort将作为变量传递给视图$exception

1<h2>{{ $exception->getMessage() }}</h2>

你可以使用 Artisan 命令发布 Laravel 的默认错误页面模板vendor:publish。模板发布后,你可以根据自己的喜好进行自定义:

1php artisan vendor:publish --tag=laravel-errors

后备 HTTP 错误页面

您还可以为给定的一系列 HTTP 状态代码定义一个“回退”错误页面。如果发生的特定 HTTP 状态代码没有对应的页面,则会渲染此页面。为此,请在应用程序目录中定义一个4xx.blade.php模板和一个模板5xx.blade.phpresources/views/errors

定义后备错误页面时,后备页面不会影响404500和 的503错误响应,因为 Laravel 内部已为这些状态代码提供了专用页面。要自定义为这些状态代码渲染的页面,您应该为每个状态代码分别定义一个自定义错误页面。