错误处理
错误处理
Laravel 默认已经配置好了错误处理,如果希望自定义错误处理,可以在 bootstrap/app.php
中使用 withExceptions
方法,此方法接收一个 Illuminate\Foundation\Configuration\Exceptions
的实例。
处理异常
上报异常
使用 report
方法可以注册一个闭包,这个闭包将在特定类型(需要类型标注)的异常被抛出时调用:
<?php
namespace App\Exception;
class DemoException extends \Exception
{
}
->withExceptions(function (Exceptions $exceptions) {
$exceptions->report(function (DemoException $e) {
Log::error('demo', [
'message' => $e->getMessage(),
'code' => $e->getCode(),
]);
});
})
public function store(Request $request)
{
throw new DemoException('test', 400);
}
在使用 report
方法注册异常处理回调时,Laravel 仍然会使用默认日志配置记录异常,如果希望不使用默认日志,可以使用 stop
方法或者在 report
方法的闭包中返回 false
:
$exceptions->report(function (DemoException $e) {
Log::error('demo', [
'message' => $e->getMessage(),
'code' => $e->getCode(),
]);
})->stop();
如果希望上报异常但是继续执行逻辑,不给用户显示错误信息或页面,可以使用 report
帮助方法:
public function show(Request $request, string $id)
{
$validator = Validator::make($request->all(), [
'id' => [new ObjectId()],
]);
$validator->validate();
return $request->all();
}
异常日志上下文
Laravel 会自动将当前用户 ID 作为上下文记录到每个异常日志消息中,如果想自定义全局日志上下文,可以使用 context
方法:
->withExceptions(function (Exceptions $exceptions) {
$exceptions->context(function () {
return [
'now' => now()->toIso8601String(),
];
});
})
在异常类中定义一个 context
方法,此方法返回一个数组,数组中的键值对会被记录到异常日志中:
class DemoException extends \Exception
{
public function __construct(
private string $accountId,
)
{
parent::__construct('Demo Exception');
}
public function context(): array
{
return [
'accountId' => $this->accountId,
];
}
}
异常上报去重
使用 report
全局方法时,同一个异常可能会被上报多次,使用 dontReportDuplicates
方法可以忽略重复的异常:
->withExceptions(function (Exceptions $exceptions) {
$exceptions->dontReportDuplicates();
})
上报处:
public function store(Request $request)
{
try {
throw new DemoException(Str::uuid()->toString());
} catch (\Exception $e) {
report($e);
report($e); // ignored
report($e); // ignored
}
return false;
}
异常日志级别
使用 level
方法可以指定 Laravel 异常日志的级别:
->withExceptions(function (Exceptions $exceptions) {
$exceptions->level(DemoException::class, LogLevel::INFO);
})
按类型忽略
如果希望永远不要上报某个异常,可以使用 dontReport
方法:
->withExceptions(function (Exceptions $exceptions) {
$exceptions->dontReport(DemoException::class);
})
或者将自定义异常类实现 Illuminate\Contracts\Debug\ShouldntReport
接口:
<?php
namespace App\Exception;
use Illuminate\Contracts\Debug\ShouldntReport;
class DemoException extends \Exception implements ShouldntReport
{
//
}
默认情况下,Laravel 已经忽略了一部分异常,例如 404 错误,如果希望不要忽略特定的异常,可以使用 stopIgnoring
方法:
use Symfony\Component\HttpKernel\Exception\HttpException;
->withExceptions(function (Exceptions $exceptions) {
$exceptions->stopIgnoring(HttpException::class);
})
渲染异常
默认情况下,Laravel 会将异常转换为 HTTP 响应,如果希望为特定的异常注册自定义渲染逻辑,可以使用 render
方法,传递给这个方法的闭包应该返回一个 Illuminate\Http\Response
实例:
->withExceptions(function (Exceptions $exceptions) {
$exceptions->render(function(DemoException $e, Request $req) {
return response()->json([
'accountId' => $e->getAccountId(),
'params' => $req->all(),
]);
});
})
提示
如果传入 render
方法的闭包没有返回值,Laravel 将使用默认的异常渲染。
在渲染异常时,Laravel 将根据 Accept
请求头来确定应该将异常渲染为 HTML 还是 JSON,如果希望自定义这个逻辑,可以使用 shouldRenderJsonWhen
方法:
->withExceptions(function (Exceptions $exceptions) {
$exceptions->shouldRenderJsonWhen(function (Request $req, Throwable $e) {
return true;
});
})
可上报和可渲染的异常
上面的异常处理方式都是要在 app.php
中进行配置,如果自定义异常较多会比较复杂,因此 Laravel 允许在自定义类中直接提供 report
和 render
方法,这样在异常处理时,可以更方便的使用这些方法:
<?php
namespace App\Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class DemoException extends \Exception
{
public function __construct(
private string $accountId,
)
{
parent::__construct('Demo Exception');
}
public function report()
{
Log::warning('demo', [
'accountId' => $this->accountId,
]);
}
public function render(Request $request)
{
return \response()->json([
'params' => $request->all(),
], 400);
}
}
report
方法可以通过返回一个布尔值来控制当前异常是否需要上报。render
方法也可以通过返回 false
来使用默认的异常渲染。
提示
report
方法可以通过类型提示进行依赖注入。
HTTP 异常
如果只是想返回一个 HTTP 错误码并抛出异常,可以使用 abort
函数:
public function store(Request $request)
{
abort(403);
}