錯誤與日誌

介紹

當你開始一個新的 Laravel 專案時,都已經為你設定好錯誤與異常處理。App\Exceptions\Handler 類別是記錄應用程式所觸發的所有異常並將其呈現給使用者的地方。在這個文件中,我們將會深入這個類別。

關於日誌,Laravel 使用 Monolog 函式庫,它提供了支援各種強大日誌的處理程序。Laravel 為你設定了其中幾個處理程序,可以讓你在單一或多個日誌檔案,或將錯誤訊息寫入系統日誌之間做選擇。

設定

錯誤細節

config/app.php 設定檔中的 debug 選項會確認實際有多少錯誤資訊顯示給使用者。預設這個選項會遵守存放在 .env 檔案的 APP_DEBUG 環境變數的值。

在本機開發的時候,你應該將 APP_DEBUG 環境變數設定為 true。在你的上線環境中,這個值應該永遠為 false。如果在正式上線主機上設定為 true,你可能會將設定檔的敏感資訊暴露給應用程式末端的使用者。

日誌儲存

Laravel 提供立即可用的日誌模式,支援寫入日誌資訊到 single 檔案、daily 檔案、syslogerrorlog。要設定 Laravel 使用的儲存系統,你應該在 config/app.php 設定檔修改 log 選項。例如,如果你希望使用 daily 記錄檔而不是單個檔案,你應該將 app 設定檔的 log 值設定為 daily

'log' => 'daily'

最大天數日誌檔案

使用 daily 日誌模式,Laravel 預設只會保留五天的日誌檔案。如果你想要調整保留檔案的天數,你可以新增 log_max_files 設定值到 app 設定檔:

'log_max_files' => 30

日誌的嚴重性級別

使用 Monolog 時,日誌訊息可以有不同級別的嚴重性。預設的 Laravel 會儲存所有寫入的日誌級別。然而,在你正式環境中,你可能希望通過增加 log_level 選項到你的 app.php 設定檔來將設定應該記錄的最低嚴重性。

這個選項一旦被設定,Laravel 會記錄所有級別大於或等於指定的嚴重性。例如,預設的 error 日誌級別 會記錄 錯誤嚴重警告緊急 訊息:

'log_level' => env('APP_LOG_LEVEL', 'error'),

{tip} Monolog 可以識別以下嚴重程度,從最低到最嚴重: debug, info, notice, warning, error, critical, alert, emergency.

自訂 Monolog 設定

如果你想要完全掌控 Monolog 為應用程式的設定,可以使用 configureMonologUsing 方法。在檔案回傳 $app 變數之前,你應該在你的 bootstrap/app.php 檔案終回呼這個方法:

$app->configureMonologUsing(function ($monolog) {
    $monolog->pushHandler(...);
});

return $app;

自訂頻道名稱

預設的 Monolog 實例化名稱會與當前環境匹配,像是 productionlocal。要變更這個值,請新增 log_channel 選項到 app.php 設定檔中:

'log_channel' => env('APP_LOG_CHANNEL', 'my-app-name'),

異常處理

Report 方法

App\Exceptions\Handler 類別會幫你處理所有例外。這個類別會有兩個方法:reportrender。我們將詳細的討論這些方法。report 方法用於記錄例外或發送它們到外部服務,像是 BugsnagSentry。預設的 report 方法只是將例外傳給記錄例外的基礎類別。不過,你可以自由的記錄例外。

例如,如果你需要以不同的方式回報不同類型的例外,你可以使用 instanceof 類型運算子:

/**
 * 回報或記錄一個例外。
 *
 * 這是向 Sentry,Bugsnag 等發送例外的好地方。
 *
 * @param  \Exception  $exception
 * @return void
 */
public function report(Exception $exception)
{
    if ($exception instanceof CustomException) {
        //
    }

    return parent::report($exception);
}

report 輔助函式

有時你一方面要回報例外,另一方面要處理當前的請求。report 輔助函式可以讓你使用異常處理器的 report 方法快速的回報例外,而不會顯示錯誤頁面:

public function isValid($value)
{
    try {
        // Validate the value...
    } catch (Exception $e) {
        report($e);

        return false;
    }
}

依類型忽略例外

異常處理器的 $dontReport 屬性會包含一組例外類型的陣列不會被記錄。例如,404 錯誤導致的例外以及其它類型的錯誤將不會寫入你的日誌中。可以根據實際的需要來新增其他例外類型到這組陣列:

/**
 * 不該被回報的例外類型清單。
 *
 * @var array
 */
protected $dontReport = [
    \Illuminate\Auth\AuthenticationException::class,
    \Illuminate\Auth\Access\AuthorizationException::class,
    \Symfony\Component\HttpKernel\Exception\HttpException::class,
    \Illuminate\Database\Eloquent\ModelNotFoundException::class,
    \Illuminate\Validation\ValidationException::class,
];

Render 方法

render 方法負責將給定的例外轉換成應該回傳給瀏覽器的 HTTP 回應。預設的例外會被傳入產生回應的基礎類別。然而,你可以自由的檢查例外類型或回傳你自訂的回應:

/**
 * 渲染例外到 HTTP 回應。
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Exception  $exception
 * @return \Illuminate\Http\Response
 */
public function render($request, Exception $exception)
{
    if ($exception instanceof CustomException) {
        return response()->view('errors.custom', [], 500);
    }

    return parent::render($request, $exception);
}

可報告與可見的例外

想要取代異常處理器用來檢查類型例外的 reportrender 方法,你可以直接在你自訂的例外中定義 reportrender 方法。當這些方法存在時,它們會被框架自動呼叫:

<?php

namespace App\Exceptions;

use Exception;

class RenderException extends Exception
{
    /**
     * 回報例外。
     *
     * @return void
     */
    public function report()
    {
        //
    }

    /**
     * 渲染例外到 HTTP 回應。
     *
     * @param  \Illuminate\Http\Request
     * @return \Illuminate\Http\Response
     */
    public function render($request)
    {
        return response(...);
    }
}

HTTP 例外

有些例外會描述 HTTP 錯誤代碼來自伺服器。例如,這可能是「找不到頁面」的 404 錯誤代碼,「未授權的錯誤」的 401 錯誤代碼或者是開發者產生的 500 錯誤代碼。為了在你應用程式的任何地方產生這樣的回應,你可以使用 abort 輔助函式:

abort(404);

abort 輔助函式會馬上發出異常處理器即將渲染的例外。或者,你可以提供回應的文字內容:

abort(403, 'Unauthorized action.');

自訂 HTTP 錯誤頁面

Laravel 很容易為各種的 HTTP 狀態碼設計所要顯示的自訂錯誤頁面。例如,如果你希望自訂 HTTP 404 錯誤代碼頁面,而去建立 resources/views/errors/404.blade.php。這個檔案會為應用程式產生所有的 404 錯誤代碼而服務。在這個目錄中的視圖應該命名與 HTTP 狀態碼一致。透過 abort 函式發出的 HttpException 實例會作為 $exception 變數傳入視圖:

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

日誌

Laravel 在強大的 Monolog 函式庫上提供了一個簡單的抽象層。預設的 Laravel 已設定好在 storage/logs 目錄中建立日誌檔案。你可以使用 Log facade 撰寫資訊到日誌中:

<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Support\Facades\Log;
use App\Http\Controllers\Controller;

class UserController extends Controller
{
    /**
     * 顯示給定使用者的個人資料。
     *
     * @param  int  $id
     * @return Response
     */
    public function showProfile($id)
    {
        Log::info('Showing user profile for user: '.$id);

        return view('user.profile', ['user' => User::findOrFail($id)]);
    }
}

日誌提供了符合 RFC 5424 定義的八種日誌級別:emergencyalertcriticalerrorwarningnoticeinfodebug

Log::emergency($message);
Log::alert($message);
Log::critical($message);
Log::error($message);
Log::warning($message);
Log::notice($message);
Log::info($message);
Log::debug($message);

Context 資訊

Context 資料的陣列也可以被傳入 Log 方法。這個 Context 資料會被格式化並顯示日誌訊息:

Log::info('User failed to login.', ['id' => $user->id]);

存取底層的 Monolog 實例

Monolog 有多種額外的處理器可被用於記錄上。如果有需要,你可以存取被 Laravel 使用的底層 Monolog 實例:

$monolog = Log::getMonolog();