LaravelDocs(中文)

日誌 (Logging)

介紹 (Introduction)

為了幫助你更了解應用程式中發生的事情,Laravel 提供了強大的日誌服務,允許你將訊息記錄到檔案、系統錯誤日誌,甚至發送到 Slack 來通知你的整個團隊。

Laravel 日誌基於「通道」。每個通道代表一種特定的寫入日誌資訊的方式。例如,single 通道將日誌檔案寫入單一日誌檔案,而 slack 通道將日誌訊息發送到 Slack。日誌訊息可以根據其嚴重性寫入多個通道。

在底層,Laravel 使用 Monolog 函式庫,它提供了對各種強大日誌處理器的支援。Laravel 讓設定這些處理器變得輕而易舉,允許你混合搭配它們來自訂應用程式的日誌處理。

設定 (Configuration)

控制應用程式日誌行為的所有設定選項都位於 config/logging.php 設定檔中。這個檔案允許你設定應用程式的日誌通道,因此請務必檢視每個可用通道及其選項。我們將在下面檢視一些常見選項。

預設情況下,Laravel 在記錄訊息時會使用 stack 通道。stack 通道用於將多個日誌通道聚合到單一通道中。關於建立堆疊的更多資訊,請查看下面的文件

可用的通道驅動 (Available Channel Drivers)

每個日誌通道都由「驅動」驅動。驅動決定了日誌訊息實際如何記錄以及記錄在哪裡。以下日誌通道驅動在每個 Laravel 應用程式中都可用。這些驅動的大多數條目已經存在於你的應用程式的 config/logging.php 設定檔中,因此請務必檢視此檔案以熟悉其內容:

名稱描述
custom呼叫指定工廠來建立通道的驅動。
daily基於 RotatingFileHandler 的 Monolog 驅動,每日輪換。
errorlog基於 ErrorLogHandler 的 Monolog 驅動。
monolog可使用任何支援的 Monolog 處理器的 Monolog 工廠驅動。
papertrail基於 SyslogUdpHandler 的 Monolog 驅動。
single基於單一檔案或路徑的日誌通道(StreamHandler)。
slack基於 SlackWebhookHandler 的 Monolog 驅動。
stack用於建立「多通道」通道的包裝器。
syslog基於 SyslogHandler 的 Monolog 驅動。

[!NOTE] 查看進階通道自訂的文件以了解更多關於 monologcustom 驅動的資訊。

設定通道名稱 (Configuring The Channel Name)

預設情況下,Monolog 會以與當前環境相符的「通道名稱」進行實例化,例如 productionlocal。要更改此值,你可以在通道的設定中新增一個 name 選項:

'stack' => [
    'driver' => 'stack',
    'name' => 'channel-name',
    'channels' => ['single', 'slack'],
],

通道先決條件 (Channel Prerequisites)

設定 Single 和 Daily 通道 (Configuring The Single And Daily Channels)

singledaily 通道有三個可選的設定選項:bubblepermissionlocking

名稱描述預設值
bubble指示訊息在被處理後是否應該向上冒泡到其他通道。true
locking在寫入之前嘗試鎖定日誌檔案。false
permission日誌檔案的權限。0644

此外,daily 通道的保留策略可以透過 LOG_DAILY_DAYS 環境變數或設定 days 設定選項來設定。

名稱描述預設值
days每日日誌檔案應該保留的天數。14

設定 Papertrail 通道 (Configuring The Papertrail Channel)

papertrail 通道需要 hostport 設定選項。這些可以透過 PAPERTRAIL_URLPAPERTRAIL_PORT 環境變數定義。你可以從 Papertrail 取得這些值。

設定 Slack 通道 (Configuring The Slack Channel)

slack 通道需要一個 url 設定選項。這個值可以透過 LOG_SLACK_WEBHOOK_URL 環境變數定義。此 URL 應該與你為 Slack 團隊設定的 incoming webhook 的 URL 相符。

預設情況下,Slack 只會接收 critical 層級及以上的日誌;然而,你可以使用 LOG_LEVEL 環境變數或修改 Slack 日誌通道設定陣列中的 level 設定選項來調整此設定。

記錄棄用警告 (Logging Deprecation Warnings)

PHP、Laravel 和其他函式庫經常會通知使用者它們的某些功能已被棄用,並將在未來版本中移除。如果你想要記錄這些棄用警告,你可以使用 LOG_DEPRECATIONS_CHANNEL 環境變數指定你偏好的 deprecations 日誌通道,或在應用程式的 config/logging.php 設定檔中指定:

'deprecations' => [
    'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
    'trace' => env('LOG_DEPRECATIONS_TRACE', false),
],

'channels' => [
    // ...
]

或者,你可以定義一個名為 deprecations 的日誌通道。如果存在具有此名稱的日誌通道,它將始終用於記錄棄用:

'channels' => [
    'deprecations' => [
        'driver' => 'single',
        'path' => storage_path('logs/php-deprecation-warnings.log'),
    ],
],

建立日誌堆疊 (Building Log Stacks)

如前所述,stack 驅動允許你將多個通道合併為單一日誌通道以方便使用。為了說明如何使用日誌堆疊,讓我們看一個你可能在正式應用程式中看到的範例設定:

'channels' => [
    'stack' => [
        'driver' => 'stack',
        'channels' => ['syslog', 'slack'], // [tl! add]
        'ignore_exceptions' => false,
    ],

    'syslog' => [
        'driver' => 'syslog',
        'level' => env('LOG_LEVEL', 'debug'),
        'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
        'replace_placeholders' => true,
    ],

    'slack' => [
        'driver' => 'slack',
        'url' => env('LOG_SLACK_WEBHOOK_URL'),
        'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
        'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
        'level' => env('LOG_LEVEL', 'critical'),
        'replace_placeholders' => true,
    ],
],

讓我們分析這個設定。首先,注意我們的 stack 通道透過其 channels 選項聚合了另外兩個通道:syslogslack。因此,當記錄訊息時,這兩個通道都有機會記錄訊息。然而,如我們將在下面看到的,這些通道是否實際記錄訊息可能由訊息的嚴重性 / 「層級」決定。

日誌層級 (Log Levels)

注意上面範例中 syslogslack 通道設定中存在的 level 設定選項。此選項決定了訊息必須達到的最低「層級」才能被通道記錄。Monolog 驅動 Laravel 的日誌服務,提供了 RFC 5424 規範中定義的所有日誌層級。按嚴重性降序排列,這些日誌層級為:emergencyalertcriticalerrorwarningnoticeinfodebug

所以,想像我們使用 debug 方法記錄一條訊息:

Log::debug('An informational message.');

根據我們的設定,syslog 通道會將訊息寫入系統日誌;然而,由於錯誤訊息不是 critical 或更高層級,它不會被發送到 Slack。然而,如果我們記錄一條 emergency 訊息,它會被發送到系統日誌和 Slack,因為 emergency 層級高於兩個通道的最低層級閾值:

Log::emergency('The system is down!');

撰寫日誌訊息 (Writing Log Messages)

你可以使用 Log facade 將資訊寫入日誌。如前所述,日誌器提供了 RFC 5424 規範中定義的八個日誌層級:emergencyalertcriticalerrorwarningnoticeinfodebug

use Illuminate\Support\Facades\Log;

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

你可以呼叫這些方法中的任何一個來記錄對應層級的訊息。預設情況下,訊息將被寫入由你的 logging 設定檔設定的預設日誌通道:

<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;

class UserController extends Controller
{
    /**
     * Show the profile for the given user.
     */
    public function show(string $id): View
    {
        Log::info('Showing the user profile for user: {id}', ['id' => $id]);

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

上下文資訊 (Contextual Information)

可以將上下文資料陣列傳遞給日誌方法。這些上下文資料將與日誌訊息一起格式化和顯示:

use Illuminate\Support\Facades\Log;

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

有時,你可能希望指定一些上下文資訊,這些資訊應該包含在特定通道中的所有後續日誌條目中。例如,你可能希望記錄與應用程式每個傳入請求相關聯的請求 ID。為達成此目的,你可以呼叫 Log facade 的 withContext 方法:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;

class AssignRequestId
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        $requestId = (string) Str::uuid();

        Log::withContext([
            'request-id' => $requestId
        ]);

        $response = $next($request);

        $response->headers->set('Request-Id', $requestId);

        return $response;
    }
}

如果你想要在所有日誌通道之間共享上下文資訊,你可以調用 Log::shareContext() 方法。此方法會將上下文資訊提供給所有已建立的通道以及後續建立的任何通道:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;

class AssignRequestId
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        $requestId = (string) Str::uuid();

        Log::shareContext([
            'request-id' => $requestId
        ]);

        // ...
    }
}

[!NOTE] 如果你需要在處理佇列任務時共享日誌上下文,你可以使用任務中介層

寫入特定通道 (Writing To Specific Channels)

有時你可能希望將訊息記錄到應用程式預設通道以外的通道。你可以使用 Log facade 上的 channel 方法來擷取並記錄到設定檔中定義的任何通道:

use Illuminate\Support\Facades\Log;

Log::channel('slack')->info('Something happened!');

如果你想要建立一個由多個通道組成的隨選日誌堆疊,你可以使用 stack 方法:

Log::stack(['single', 'slack'])->info('Something happened!');

隨選通道 (On Demand Channels)

也可以透過在執行時提供設定來建立隨選通道,而無需將該設定存在於應用程式的 logging 設定檔中。為達成此目的,你可以將設定陣列傳遞給 Log facade 的 build 方法:

use Illuminate\Support\Facades\Log;

Log::build([
  'driver' => 'single',
  'path' => storage_path('logs/custom.log'),
])->info('Something happened!');

你也可能希望在隨選日誌堆疊中包含隨選通道。這可以透過將隨選通道實例包含在傳遞給 stack 方法的陣列中來達成:

use Illuminate\Support\Facades\Log;

$channel = Log::build([
  'driver' => 'single',
  'path' => storage_path('logs/custom.log'),
]);

Log::stack(['slack', $channel])->info('Something happened!');

Monolog 通道自訂 (Monolog Channel Customization)

為通道自訂 Monolog (Customizing Monolog For Channels)

有時你可能需要完全控制現有通道的 Monolog 設定方式。例如,你可能想要為 Laravel 內建的 single 通道設定自訂的 Monolog FormatterInterface 實作。

要開始,請在通道的設定上定義一個 tap 陣列。tap 陣列應該包含一個類別列表,這些類別應該有機會在 Monolog 實例建立後自訂(或「tap」進入)它。這些類別沒有約定的放置位置,因此你可以在應用程式中建立一個目錄來包含這些類別:

'single' => [
    'driver' => 'single',
    'tap' => [App\Logging\CustomizeFormatter::class],
    'path' => storage_path('logs/laravel.log'),
    'level' => env('LOG_LEVEL', 'debug'),
    'replace_placeholders' => true,
],

一旦你在通道上設定了 tap 選項,你就可以定義將自訂 Monolog 實例的類別。這個類別只需要一個方法:__invoke,它接收一個 Illuminate\Log\Logger 實例。Illuminate\Log\Logger 實例會將所有方法呼叫代理到底層的 Monolog 實例:

<?php

namespace App\Logging;

use Illuminate\Log\Logger;
use Monolog\Formatter\LineFormatter;

class CustomizeFormatter
{
    /**
     * Customize the given logger instance.
     */
    public function __invoke(Logger $logger): void
    {
        foreach ($logger->getHandlers() as $handler) {
            $handler->setFormatter(new LineFormatter(
                '[%datetime%] %channel%.%level_name%: %message% %context% %extra%'
            ));
        }
    }
}

[!NOTE] 所有的「tap」類別都由 service container 解析,因此它們需要的任何建構函式依賴都會自動注入。

建立 Monolog Handler 通道 (Creating Monolog Handler Channels)

Monolog 有各種可用的處理器,而 Laravel 並沒有為每一個都包含內建通道。在某些情況下,你可能希望建立一個自訂通道,它只是一個沒有對應 Laravel 日誌驅動的特定 Monolog 處理器的實例。這些通道可以使用 monolog 驅動輕鬆建立。

使用 monolog 驅動時,handler 設定選項用於指定將被實例化的處理器。可選地,處理器需要的任何建構函式參數可以使用 handler_with 設定選項指定:

'logentries' => [
    'driver'  => 'monolog',
    'handler' => Monolog\Handler\SyslogUdpHandler::class,
    'handler_with' => [
        'host' => 'my.logentries.internal.datahubhost.company.com',
        'port' => '10000',
    ],
],

Monolog 格式化器 (Monolog Formatters)

使用 monolog 驅動時,Monolog LineFormatter 將作為預設格式化器。然而,你可以使用 formatterformatter_with 設定選項來自訂傳遞給處理器的格式化器類型:

'browser' => [
    'driver' => 'monolog',
    'handler' => Monolog\Handler\BrowserConsoleHandler::class,
    'formatter' => Monolog\Formatter\HtmlFormatter::class,
    'formatter_with' => [
        'dateFormat' => 'Y-m-d',
    ],
],

如果你使用的 Monolog 處理器能夠提供自己的格式化器,你可以將 formatter 設定選項的值設為 default

'newrelic' => [
    'driver' => 'monolog',
    'handler' => Monolog\Handler\NewRelicHandler::class,
    'formatter' => 'default',
],

Monolog 處理器 (Monolog Processors)

Monolog 也可以在記錄之前處理訊息。你可以建立自己的處理器或使用 Monolog 提供的現有處理器

如果你想要為 monolog 驅動自訂處理器,請在通道的設定中新增一個 processors 設定值:

'memory' => [
    'driver' => 'monolog',
    'handler' => Monolog\Handler\StreamHandler::class,
    'handler_with' => [
        'stream' => 'php://stderr',
    ],
    'processors' => [
        // Simple syntax...
        Monolog\Processor\MemoryUsageProcessor::class,

        // With options...
        [
            'processor' => Monolog\Processor\PsrLogMessageProcessor::class,
            'with' => ['removeUsedContextFields' => true],
        ],
    ],
],

透過工廠建立自訂通道 (Creating Custom Channels Via Factories)

如果你想要定義一個完全自訂的通道,在其中你可以完全控制 Monolog 的實例化和設定,你可以在 config/logging.php 設定檔中指定 custom 驅動類型。你的設定應該包含一個 via 選項,其中包含將被調用來建立 Monolog 實例的工廠類別名稱:

'channels' => [
    'example-custom-channel' => [
        'driver' => 'custom',
        'via' => App\Logging\CreateCustomLogger::class,
    ],
],

一旦你設定了 custom 驅動通道,你就可以定義將建立 Monolog 實例的類別。這個類別只需要一個 __invoke 方法,它應該回傳 Monolog 日誌器實例。該方法將接收通道設定陣列作為其唯一引數:

<?php

namespace App\Logging;

use Monolog\Logger;

class CreateCustomLogger
{
    /**
     * Create a custom Monolog instance.
     */
    public function __invoke(array $config): Logger
    {
        return new Logger(/* ... */);
    }
}

使用 Pail 尾隨日誌訊息 (Tailing Log Messages Using Pail)

通常你可能需要即時尾隨應用程式的日誌。例如,當除錯問題或監控應用程式日誌以查找特定類型的錯誤時。

Laravel Pail 是一個套件,允許你直接從命令列輕鬆深入查看 Laravel 應用程式的日誌檔案。與標準的 tail 命令不同,Pail 設計為可與任何日誌驅動一起使用,包括 Sentry 或 Flare。此外,Pail 提供了一組有用的過濾器來幫助你快速找到你要找的內容。

安裝 (Pail Installation)

[!WARNING] Laravel Pail 需要 PCNTL PHP 擴充套件。

要開始使用,請使用 Composer 套件管理器將 Pail 安裝到你的專案中:

composer require --dev laravel/pail

使用 (Pail Usage)

要開始尾隨日誌,請執行 pail 命令:

php artisan pail

要增加輸出的詳細程度並避免截斷(…),請使用 -v 選項:

php artisan pail -v

要獲得最大詳細程度並顯示例外堆疊追蹤,請使用 -vv 選項:

php artisan pail -vv

要停止尾隨日誌,請隨時按 Ctrl+C

過濾日誌 (Pail Filtering Logs)

--filter

你可以使用 --filter 選項按類型、檔案、訊息和堆疊追蹤內容過濾日誌:

php artisan pail --filter="QueryException"

--message

要僅按訊息過濾日誌,你可以使用 --message 選項:

php artisan pail --message="User created"

--level

--level 選項可用於按日誌層級過濾日誌:

php artisan pail --level=error

--user

要僅顯示在特定使用者經過身份驗證時寫入的日誌,你可以將使用者的 ID 提供給 --user 選項:

php artisan pail --user=1