跳到主要內容

錯誤處理

📝 此頁面為 Laravel 官方文檔的繁體中文翻譯。查看原始英文版本

錯誤處理

簡介

當您啟動一個新的 Laravel 專案時,錯誤和異常處理已經為您配置好;但是,您可以隨時在應用程式的 bootstrap/app.php 中使用 withExceptions 方法來管理異常的報告和渲染方式。

傳遞給 withExceptions 閉包的 $exceptions 物件是 Illuminate\Foundation\Configuration\Exceptions 的實例,負責管理應用程式中的異常處理。我們將在整個文件中深入探討此物件。

設定

config/app.php 設定檔中的 debug 選項決定了實際向使用者顯示多少錯誤資訊。預設情況下,此選項設定為遵循 APP_DEBUG 環境變數的值,該值儲存在 .env 檔案中。

在本機開發期間,您應將 APP_DEBUG 環境變數設為 true

[!WARNING] 在生產環境中,APP_DEBUG 的值應始終為 false。如果在生產環境中將值設為 true,您可能會將敏感的設定值暴露給應用程式的最終使用者。

處理異常

報告異常

在 Laravel 中,異常報告用於記錄異常或將它們發送到外部服務,如 SentryFlare。預設情況下,異常將根據您的日誌記錄設定進行記錄。但是,您可以自由地以任何方式記錄異常。

如果您需要以不同方式報告不同類型的異常,可以在應用程式的 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;
    });
})

[!NOTE] 要自訂給定異常的異常報告,您也可以使用可報告異常

全域日誌上下文

如果可用,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
     */
    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): 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 仍將使用應用程式的預設日誌記錄設定來記錄異常;但是,由於日誌等級有時會影響訊息被記錄的管道,您可能希望配置某些異常被記錄的日誌等級。

要實現此目的,您可以在應用程式的 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 錯誤導致的異常、來源不匹配生成的 403 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);
        }
    });
})

將異常渲染為 JSON

渲染異常時,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;
    });
})

可報告和可渲染的異常

您可以在應用程式的異常上直接定義 reportrender 方法,而不是在應用程式的 bootstrap/app.php 檔案中定義自訂報告和渲染行為。當這些方法存在時,它們將被框架自動呼叫:

<?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 的服務容器自動注入到方法中。

限制報告的異常

如果您的應用程式報告了大量異常,您可能想限制實際記錄或發送到應用程式外部錯誤追蹤服務的異常數量。

要隨機抽樣異常,可以在應用程式的 bootstrap/app.php 檔案中使用 throttle 異常方法。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());
        }
    });
})

當然,您可以為不同的異常返回 LotteryLimit 實例的混合:

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 異常

某些異常描述了來自伺服器的 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 對這些狀態碼有內部專用頁面。要自訂為這些狀態碼渲染的頁面,您應該為每個狀態碼單獨定義一個自訂錯誤頁面。