当你启动一个新的 Laravel 项目时,错误和异常处理已为你配置好;然而,在任何时候,你都可以使用 withExceptions 方法在你应用的 bootstrap/app.php 中来管理你的应用程序如何报告和渲染异常。
提供给 withExceptions 闭包的 $exceptions 对象是 Illuminate\Foundation\Configuration\Exceptions 的一个实例,负责管理应用程序中的异常处理。 在本文档中,我们将深入探讨此对象。
您的 config/app.php 配置文件中的 debug 选项决定了向用户实际显示多少错误信息。默认情况下,此选项设置为遵循 APP_DEBUG 环境变量的值,该变量存储在您的 .env 文件中。
在本地开发期间,你应该将APP_DEBUG环境变量设置为true。在你的生产环境中,这个值应该始终是false。如果该值在生产环境中被设置为true,你将面临向你的应用程序最终用户暴露敏感配置值的风险。
在 Laravel 中,异常报告用于记录异常或将它们发送到外部服务,例如 Sentry 或 Flare。默认情况下,异常将根据你的 日志 配置进行记录。但是,你可以自由地以你希望的任何方式记录异常。
如果你需要以不同方式报告不同类型的异常,你可以在你的应用的 bootstrap/app.php 中使用 report 异常方法来注册一个闭包,该闭包应在给定类型的异常需要报告时执行。Laravel 将通过检查闭包的类型提示来确定该闭包报告何种类型的异常:
use App\Exceptions\InvalidOrderException;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->report(function (InvalidOrderException $e) {
// ...
});
})当你使用 report 方法注册一个自定义异常报告回调时,Laravel 仍会使用应用程序的默认日志配置记录该异常。如果你希望阻止异常传播到默认日志栈,你可以在定义报告回调时使用 stop 方法,或者从回调中返回 false:
use App\Exceptions\InvalidOrderException;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->report(function (InvalidOrderException $e) {
// ...
})->stop();
$exceptions->report(function (InvalidOrderException $e) {
return false;
});
})[!注意]
为了自定义给定异常的异常报告,你也可以利用可报告异常.
如果可用,Laravel 会自动将当前用户的 ID 作为上下文数据添加到每个异常的日志消息中。你可以在应用的 bootstrap/app.php 文件中,使用 context 异常方法定义自己的全局上下文数据。这些信息将包含在你的应用写入的每个异常的日志消息中:
->withExceptions(function (Exceptions $exceptions): void {
$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];
}
}The report 助手有时您可能需要报告一个异常,但继续处理当前请求。report 辅助函数允许您快速报告一个异常,而无需向用户渲染错误页面:
public function isValid(string $value): bool
{
try {
// Validate the value...
} catch (Throwable $e) {
report($e);
return false;
}
}如果你在你的应用程序中全程使用 report 函数,你可能会偶尔多次报告相同的异常,从而在你的日志中创建重复的条目。
如果您想确保某个异常的单个实例只会被报告一次,您可以调用 dontReportDuplicates 异常方法,在您的应用的 bootstrap/app.php 文件中:
->withExceptions(function (Exceptions $exceptions): void {
$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 仍将使用应用程序的默认日志配置记录该异常;但是,由于日志级别有时会影响消息记录到的通道,你可能希望配置某些异常的日志级别。
To accomplish this, you you may use thelevelexception method in your application'sbootstrap/app.phpfile. This method receives the exception type as its first argument and the log level as its second argument: 为了实现此目的,你可以在应用的bootstrap/app.php文件中使用level` 异常方法。此方法接收异常类型作为第一个参数,日志级别作为第二个参数:
use PDOException;
use Psr\Log\LogLevel;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->level(PDOException::class, LogLevel::CRITICAL);
})在构建应用程序时,会遇到你绝不希望报告的某些异常类型。要忽略这些异常,你可以使用你应用程序的 bootstrap/app.php 文件中的 dontReport 异常方法。提供给此方法的任何类将永远不会被报告;但是,它们可能仍然具有自定义渲染逻辑:
use App\Exceptions\InvalidOrderException;
->withExceptions(function (Exceptions $exceptions): void {
$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
{
//
}如果需要对何时忽略特定类型的异常有更多控制,你可以给 dontReportWhen 方法提供一个闭包:
use App\Exceptions\InvalidOrderException;
use Throwable;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->dontReportWhen(function (Throwable $e) {
return $e instanceof PodcastProcessingException &&
$e->reason() === 'Subscription expired';
});
})Laravel 在内部已经为您忽略了某些类型的错误,例如由 404 HTTP 错误或无效 CSRF 令牌生成的 419 HTTP 响应所导致的异常。如果您希望指示 Laravel 停止忽略给定类型的异常,您可以在您的应用程序的 bootstrap/app.php 文件中使用 stopIgnoring 异常方法:
use Symfony\Component\HttpKernel\Exception\HttpException;
->withExceptions(function (Exceptions $exceptions): void {
$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): void {
$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): void {
$exceptions->render(function (NotFoundHttpException $e, Request $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => 'Record not found.'
], 404);
}
});
})当渲染异常时,Laravel 将根据请求的 Accept 头部自动判断异常是否应被渲染为 HTML 或 JSON 响应。如果您想自定义 Laravel 如何判断是渲染 HTML 还是 JSON 异常响应,您可以使用 shouldRenderJsonWhen 方法:
use Illuminate\Http\Request;
use Throwable;
->withExceptions(function (Exceptions $exceptions): void {
$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): void {
$exceptions->respond(function (Response $response) {
if ($response->getStatusCode() === 419) {
return back()->with([
'message' => 'The page expired, please try again.',
]);
}
return $response;
});
})与其在你的应用程序的bootstrap/app.php文件中定义自定义报告和渲染行为,你可以直接在你的应用程序的异常上定义report和render方法。当这些方法存在时,它们将自动被框架调用:
<?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 as an HTTP response.
*/
public function render(Request $request): Response
{
return response(/* ... */);
}
}如果你的异常继承了一个已经可渲染的异常,例如一个内置的 Laravel 或 Symfony 异常,你可以从异常的 render 方法中返回 false,以渲染异常的默认 HTTP 响应:
/**
* Render the exception as 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;
}[!NOTE]
您可以为report方法的任何所需依赖进行类型提示,它们将由 Laravel 的服务容器自动注入到该方法中。
如果您的应用程序报告了非常多的异常,您可能需要对实际记录或发送到您的应用程序的外部错误跟踪服务的异常数量进行节流。
要对异常进行随机采样,可以使用 throttle 异常方法,该方法位于应用程序的 bootstrap/app.php 文件中。该 throttle 方法接收一个闭包,该闭包应返回一个 Lottery 实例:
use Illuminate\Support\Lottery;
use Throwable;
->withExceptions(function (Exceptions $exceptions): void {
$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): void {
$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): void {
$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): void {
$exceptions->throttle(function (Throwable $e) {
if ($e instanceof BroadcastException) {
return Limit::perMinute(300)->by($e->getMessage());
}
});
})自然地,你可以针对不同的异常返回混合的Lottery和Limit实例:
use App\Exceptions\ApiMonitoringException;
use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Lottery;
use Throwable;
->withExceptions(function (Exceptions $exceptions): void {
$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 错误代码。例如, 这可能是一个"页面未找到"错误 (404), 一个"未经授权"错误 (401), 甚至是一个开发者生成的 500 错误。为了在你的应用程序中从任何地方生成这样的响应, 你可以使用 abort 助手:
abort(404);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 状态码时没有对应的页面,则将渲染此页面。为此,请在您应用程序的 resources/views/errors 目录中定义一个 4xx.blade.php 模板和一个 5xx.blade.php 模板。
定义回退错误页面时,这些回退页面不会影响 404、500 和 503 错误响应,因为 Laravel 对这些状态码有内部专用的页面。要自定义为这些状态码渲染的页面,您应该为每个状态码单独定义一个自定义错误页面。