33%
翻译进度
9
分块数量
3
参与人数

错误处理

这是一篇协同翻译的文章,你可以点击『我来翻译』按钮来参与翻译。


错误处理#

简介#

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

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

配置#

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

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

处理异常#

异常报告#

在 Laravel 中,异常报告用于记录异常或将其发送到诸如 Sentry or Flare 等外部服务。默认情况下,异常将根据您的 日志记录 配置进行记录。不过,您依然可以按照自己的意愿自由记录异常。

fengwuxin 翻译于 1 周前

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

use App\Exceptions\InvalidOrderException;

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

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

use App\Exceptions\InvalidOrderException;

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

    $exceptions->report(function (InvalidOrderException $e) {
        return false;
    });
})

[!NOTE]
要为给定的异常自定义异常报告,你还可以使用可报告异常。 reportable exceptions.

全局日志上下文#

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

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

fengwuxin 翻译于 1 周前

异常日志上下文#

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

<?php

namespace App\Exceptions;

use Exception;

class InvalidOrderException extends Exception
{
    // ...

    /**
     * 获取异常上下文信息
     *
     * @return array<string, mixed>
     */
    public function context(): array
    {
        return ['order_id' => $this->orderId];
    }
}

report 辅助函数#

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

public function isValid(string $value): bool
{
    try {
        // 验证这个值...
    } 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); // 报告了

try {
    throw $original;
} catch (Throwable $caught) {
    report($caught); //  忽略了
}

report($original); //  忽略了
report($caught); //  忽略了
dkp 翻译于 6 天前

Exception Log Levels#

When messages are written to your application's logs, the messages are written at a specified log level, which indicates the severity or importance of the message being logged.

As noted above, even when you register a custom exception reporting callback using the report method, Laravel will still log the exception using the default logging configuration for the application; however, since the log level can sometimes influence the channels on which a message is logged, you may wish to configure the log level that certain exceptions are logged at.

To accomplish this, you may use the level exception method in your application's bootstrap/app.php file. This method receives the exception type as its first argument and the log level as its second argument:

use PDOException;
use Psr\Log\LogLevel;

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

Ignoring Exceptions by Type#

When building your application, there will be some types of exceptions you never want to report. To ignore these exceptions, you may use the dontReport exception method in your application's bootstrap/app.php file. Any class provided to this method will never be reported; however, they may still have custom rendering logic:

use App\Exceptions\InvalidOrderException;

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

Alternatively, you may simply "mark" an exception class with the Illuminate\Contracts\Debug\ShouldntReport interface. When an exception is marked with this interface, it will never be reported by Laravel's exception handler:

<?php

namespace App\Exceptions;

use Exception;
use Illuminate\Contracts\Debug\ShouldntReport;

class PodcastProcessingException extends Exception implements ShouldntReport
{
    //
}

Internally, Laravel already ignores some types of errors for you, such as exceptions resulting from 404 HTTP errors or 419 HTTP responses generated by invalid CSRF tokens. If you would like to instruct Laravel to stop ignoring a given type of exception, you may use the stopIgnoring exception method in your application's bootstrap/app.php file:

use Symfony\Component\HttpKernel\Exception\HttpException;

->withExceptions(function (Exceptions $exceptions) {
    $exceptions->stopIgnoring(HttpException::class);
})

Rendering Exceptions#

By default, the Laravel exception handler will convert exceptions into an HTTP response for you. However, you are free to register a custom rendering closure for exceptions of a given type. You may accomplish this by using the render exception method in your application's bootstrap/app.php file.

The closure passed to the render method should return an instance of Illuminate\Http\Response, which may be generated via the response helper. Laravel will determine what type of exception the closure renders by examining the type-hint of the closure:

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);
    });
})

You may also use the render method to override the rendering behavior for built-in Laravel or Symfony exceptions such as NotFoundHttpException. If the closure given to the render method does not return a value, Laravel's default exception rendering will be utilized:

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);
        }
    });
})

Rendering Exceptions as JSON#

When rendering an exception, Laravel will automatically determine if the exception should be rendered as an HTML or JSON response based on the Accept header of the request. If you would like to customize how Laravel determines whether to render HTML or JSON exception responses, you may utilize the shouldRenderJsonWhen method:

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();
    });
})

Customizing the Exception Response#

Rarely, you may need to customize the entire HTTP response rendered by Laravel's exception handler. To accomplish this, you may register a response customization closure using the respond method:

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;
    });
})

Reportable and Renderable Exceptions#

Instead of defining custom reporting and rendering behavior in your application's bootstrap/app.php file, you may define report and render methods directly on your application's exceptions. When these methods exist, they will automatically be called by the framework:

<?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(/* ... */);
    }
}

If your exception extends an exception that is already renderable, such as a built-in Laravel or Symfony exception, you may return false from the exception's render method to render the exception's default HTTP response:

/**
 * 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;
}

If your exception contains custom reporting logic that is only necessary when certain conditions are met, you may need to instruct Laravel to sometimes report the exception using the default exception handling configuration. To accomplish this, you may return false from the exception's report method:

/**
 * Report the exception.
 */
public function report(): bool
{
    if (/** Determine if the exception needs custom reporting */) {

        // ...

        return true;
    }

    return false;
}

[!NOTE]
You may type-hint any required dependencies of the report method and they will automatically be injected into the method by Laravel's service container.

Throttling Reported Exceptions#

If your application reports a very large number of exceptions, you may want to throttle how many exceptions are actually logged or sent to your application's external error tracking service.

To take a random sample rate of exceptions, you may use the throttle exception method in your application's bootstrap/app.php file. The throttle method receives a closure that should return a Lottery instance:

use Illuminate\Support\Lottery;
use Throwable;

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

It is also possible to conditionally sample based on the exception type. If you would like to only sample instances of a specific exception class, you may return a Lottery instance only for that class:

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);
        }
    });
})

You may also rate limit exceptions logged or sent to an external error tracking service by returning a Limit instance instead of a Lottery. This is useful if you want to protect against sudden bursts of exceptions flooding your logs, for example, when a third-party service used by your application is down:

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 default, limits will use the exception's class as the rate limit key. You can customize this by specifying your own key using the by method on the Limit:

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());
        }
    });
})

Of course, you may return a mixture of Lottery and Limit instances for different exceptions:

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 Exceptions#

Some exceptions describe HTTP error codes from the server. For example, this may be a "page not found" error (404), an "unauthorized error" (401), or even a developer generated 500 error. In order to generate such a response from anywhere in your application, you may use the abort helper:

abort(404);

Custom HTTP Error Pages#

Laravel makes it easy to display custom error pages for various HTTP status codes. For example, to customize the error page for 404 HTTP status codes, create a resources/views/errors/404.blade.php view template. This view will be rendered for all 404 errors generated by your application. The views within this directory should be named to match the HTTP status code they correspond to. The Symfony\Component\HttpKernel\Exception\HttpException instance raised by the abort function will be passed to the view as an $exception variable:

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

You may publish Laravel's default error page templates using the vendor:publish Artisan command. Once the templates have been published, you may customize them to your liking:

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

Fallback HTTP Error Pages#

You may also define a "fallback" error page for a given series of HTTP status codes. This page will be rendered if there is not a corresponding page for the specific HTTP status code that occurred. To accomplish this, define a 4xx.blade.php template and a 5xx.blade.php template in your application's resources/views/errors directory.

When defining fallback error pages, the fallback pages will not affect 404, 500, and 503 error responses since Laravel has internal, dedicated pages for these status codes. To customize the pages rendered for these status codes, you should define a custom error page for each of them individually.

本文章首发在 LearnKu.com 网站上。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
贡献者:3