跳至内容

错误处理

介绍

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

提供给 withExceptions 闭包的 $exceptions 对象是 Illuminate\Foundation\Configuration\Exceptions 的一个实例,负责管理应用程序中的异常处理。在本文档中,我们将深入了解这个对象。

配置

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

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

处理异常

报告异常

在 Laravel 中,异常报告用于记录异常或将它们发送到外部服务,例如 SentryFlare。默认情况下,异常将根据您的 日志记录 配置进行记录。但是,您可以随意记录异常。

如果您需要以不同的方式报告不同类型的异常,可以在应用程序的 bootstrap/app.php 中使用 report 异常方法来注册一个闭包,该闭包应该在需要报告给定类型的异常时执行。Laravel 将通过检查闭包的类型提示来确定闭包报告的异常类型。

->withExceptions(function (Exceptions $exceptions) {
$exceptions->report(function (InvalidOrderException $e) {
// ...
});
})

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

->withExceptions(function (Exceptions $exceptions) {
$exceptions->report(function (InvalidOrderException $e) {
// ...
})->stop();
 
$exceptions->report(function (InvalidOrderException $e) {
return false;
});
})
lightbulb

要自定义给定异常的异常报告,您还可以使用 可报告异常.

全局日志上下文

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

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

异常日志上下文

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

<?php
 
namespace App\Exceptions;
 
use Exception;
 
class InvalidOrderException extends Exception
{
// ...
 
/**
* Get the exception's context information.
*
* @return array<string, mixed>
*/
public function context(): array
{
return ['order_id' => $this->orderId];
}
}

report 助手

有时您可能需要报告异常,但继续处理当前请求。report 助手函数允许您快速报告异常,而不会向用户渲染错误页面。

public function isValid(string $value): bool
{
try {
// Validate the value...
} catch (Throwable $e) {
report($e);
 
return false;
}
}

消除重复报告的异常

如果您在应用程序中使用 report 函数,则偶尔可能会多次报告同一个异常,在日志中创建重复条目。

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

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

现在,当使用同一个异常实例调用 report 助手时,只会在第一次调用时报告。

$original = new RuntimeException('Whoops!');
 
report($original); // reported
 
try {
throw $original;
} catch (Throwable $caught) {
report($caught); // ignored
}
 
report($original); // ignored
report($caught); // ignored

异常日志级别

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

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

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

use PDOException;
use Psr\Log\LogLevel;
 
->withExceptions(function (Exceptions $exceptions) {
$exceptions->level(PDOException::class, LogLevel::CRITICAL);
})

按类型忽略异常

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

use App\Exceptions\InvalidOrderException;
 
->withExceptions(function (Exceptions $exceptions) {
$exceptions->dontReport([
InvalidOrderException::class,
]);
})

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

<?php
 
namespace App\Exceptions;
 
use Exception;
use Illuminate\Contracts\Debug\ShouldntReport;
 
class PodcastProcessingException extends Exception implements ShouldntReport
{
//
}

在内部,Laravel 已经为您忽略了一些类型的错误,例如因 404 HTTP 错误或由无效 CSRF 令牌生成的 419 HTTP 响应而导致的异常。如果您希望指示 Laravel 停止忽略给定类型的异常,可以在应用程序的 bootstrap/app.php 文件中使用 stopIgnoring 异常方法。

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

渲染异常

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

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

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

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

use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
->withExceptions(function (Exceptions $exceptions) {
$exceptions->render(function (NotFoundHttpException $e, Request $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => 'Record not found.'
], 404);
}
});
})

将异常渲染为 JSON

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

use Illuminate\Http\Request;
use Throwable;
 
->withExceptions(function (Exceptions $exceptions) {
$exceptions->shouldRenderJsonWhen(function (Request $request, Throwable $e) {
if ($request->is('admin/*')) {
return true;
}
 
return $request->expectsJson();
});
})

自定义异常响应

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

use Symfony\Component\HttpFoundation\Response;
 
->withExceptions(function (Exceptions $exceptions) {
$exceptions->respond(function (Response $response) {
if ($response->getStatusCode() === 419) {
return back()->with([
'message' => 'The page expired, please try again.',
]);
}
 
return $response;
});
})

可报告和可渲染异常

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

<?php
 
namespace App\Exceptions;
 
use Exception;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
 
class InvalidOrderException extends Exception
{
/**
* Report the exception.
*/
public function report(): void
{
// ...
}
 
/**
* Render the exception into an HTTP response.
*/
public function render(Request $request): Response
{
return response(/* ... */);
}
}

如果您的异常类继承了已可渲染的异常类(例如内置的 Laravel 或 Symfony 异常类),您可以在异常类的 render 方法中返回 false 来渲染异常的默认 HTTP 响应。

/**
* Render the exception into an HTTP response.
*/
public function render(Request $request): Response|bool
{
if (/** Determine if the exception needs custom rendering */) {
 
return response(/* ... */);
}
 
return false;
}

如果您的异常类包含仅在特定条件下才需要的自定义报告逻辑,则可能需要指示 Laravel 有时使用默认的异常处理配置来报告异常。为了实现这一点,您可以在异常类的 report 方法中返回 false

/**
* Report the exception.
*/
public function report(): bool
{
if (/** Determine if the exception needs custom reporting */) {
 
// ...
 
return true;
}
 
return false;
}
lightbulb

您可以在 report 方法中类型提示任何所需的依赖项,Laravel 的 服务容器 会自动将这些依赖项注入到方法中。

限制报告异常

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

要对异常进行随机抽样,您可以在应用程序的 bootstrap/app.php 文件中使用 throttle 异常方法。throttle 方法接收一个闭包,该闭包应返回一个 Lottery 实例。

use Illuminate\Support\Lottery;
use Throwable;
 
->withExceptions(function (Exceptions $exceptions) {
$exceptions->throttle(function (Throwable $e) {
return Lottery::odds(1, 1000);
});
})

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

use App\Exceptions\ApiMonitoringException;
use Illuminate\Support\Lottery;
use Throwable;
 
->withExceptions(function (Exceptions $exceptions) {
$exceptions->throttle(function (Throwable $e) {
if ($e instanceof ApiMonitoringException) {
return Lottery::odds(1, 1000);
}
});
})

您还可以通过返回 Limit 实例而不是 Lottery 实例来限制记录到日志或发送到外部错误跟踪服务的异常。当您的应用程序使用的第三方服务出现故障时,例如,当出现异常的突然爆发时,这将很有用。

use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Cache\RateLimiting\Limit;
use Throwable;
 
->withExceptions(function (Exceptions $exceptions) {
$exceptions->throttle(function (Throwable $e) {
if ($e instanceof BroadcastException) {
return Limit::perMinute(300);
}
});
})

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

use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Cache\RateLimiting\Limit;
use Throwable;
 
->withExceptions(function (Exceptions $exceptions) {
$exceptions->throttle(function (Throwable $e) {
if ($e instanceof BroadcastException) {
return Limit::perMinute(300)->by($e->getMessage());
}
});
})

当然,您可以为不同的异常返回 LotteryLimit 实例的混合。

use App\Exceptions\ApiMonitoringException;
use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Lottery;
use Throwable;
 
->withExceptions(function (Exceptions $exceptions) {
$exceptions->throttle(function (Throwable $e) {
return match (true) {
$e instanceof BroadcastException => Limit::perMinute(300),
$e instanceof ApiMonitoringException => Lottery::odds(1, 1000),
default => Limit::none(),
};
});
})

HTTP 异常

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

abort(404);

自定义 HTTP 错误页面

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

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

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

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

备用 HTTP 错误页面

您还可以为一系列 HTTP 状态代码定义一个“备用”错误页面。如果不存在与出现的特定 HTTP 状态代码对应的页面,则将渲染此页面。要实现此目的,请在应用程序的 resources/views/errors 目录中定义一个 4xx.blade.php 模板和一个 5xx.blade.php 模板。

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