跳轉至內容

日誌

簡介

為了幫助您深入瞭解應用程式內部發生的情況,Laravel 提供了強大的日誌服務,允許您將訊息記錄到檔案、系統錯誤日誌,甚至是 Slack,以便通知您的整個團隊。

Laravel 日誌基於“通道”。每個通道代表一種特定的記錄日誌資訊的方式。例如,single 通道將日誌寫入單個日誌檔案,而 slack 通道則將日誌訊息傳送到 Slack。日誌訊息可以根據其嚴重程度被寫入多個通道。

在底層,Laravel 使用了 Monolog 庫,它支援各種強大的日誌處理程式。Laravel 讓配置這些處理程式變得非常簡單,允許您混合搭配它們來定製應用程式的日誌處理方式。

配置

所有控制應用程式日誌行為的配置選項都存放在 config/logging.php 配置檔案中。此檔案允許您配置應用程式的日誌通道,因此請務必檢視每個可用通道及其選項。我們將在下面回顧一些常用選項。

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

可用通道驅動

每個日誌通道都由一個“驅動”提供支援。驅動程式決定了日誌訊息實際記錄的方式和位置。每個 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 驅動程式。

檢視關於高階通道自定義的文件,以瞭解更多關於 monologcustom 驅動程式的資訊。

配置通道名稱

預設情況下,Monolog 使用與當前環境匹配的“通道名稱”進行例項化,例如 productionlocal。要更改此值,可以在通道的配置中新增 name 選項。

1'stack' => [
2 'driver' => 'stack',
3 'name' => 'channel-name',
4 'channels' => ['single', 'slack'],
5],

通道前提條件

配置 Single 和 Daily 通道

singledaily 通道有三個可選配置選項:bubblepermissionlocking

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

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

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

配置 Papertrail 通道

papertrail 通道需要 hostport 配置選項。這些可以透過 PAPERTRAIL_URLPAPERTRAIL_PORT 環境變數定義。您可以從 Papertrail 獲取這些值。

配置 Slack 通道

slack 通道需要 url 配置選項。此值可以透過 LOG_SLACK_WEBHOOK_URL 環境變數定義。此 URL 應與您為 Slack 團隊配置的傳入 Webhook URL 相匹配。

預設情況下,Slack 僅接收 critical 級別及以上的日誌;但是,您可以使用 LOG_LEVEL 環境變數或修改 Slack 日誌通道配置陣列中的 level 配置選項來進行調整。

棄用警告日誌記錄

PHP、Laravel 以及其他庫通常會通知使用者其某些功能已被棄用,並將在未來版本中移除。如果您希望記錄這些棄用警告,可以使用 LOG_DEPRECATIONS_CHANNEL 環境變數指定您偏好的 deprecations 日誌通道,或者在應用程式的 config/logging.php 配置檔案中進行設定。

1'deprecations' => [
2 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
3 'trace' => env('LOG_DEPRECATIONS_TRACE', false),
4],
5 
6'channels' => [
7 // ...
8]

或者,您可以定義一個名為 deprecations 的日誌通道。如果存在以此命名的日誌通道,它將始終被用於記錄棄用資訊。

1'channels' => [
2 'deprecations' => [
3 'driver' => 'single',
4 'path' => storage_path('logs/php-deprecation-warnings.log'),
5 ],
6],

構建日誌堆疊

如前所述,stack 驅動程式允許您為了方便起見將多個通道合併為一個日誌通道。為了說明如何使用日誌堆疊,讓我們看一個您在生產環境應用程式中可能會看到的配置示例。

1'channels' => [
2 'stack' => [
3 'driver' => 'stack',
4 'channels' => ['syslog', 'slack'],
5 'ignore_exceptions' => false,
6 ],
7 
8 'syslog' => [
9 'driver' => 'syslog',
10 'level' => env('LOG_LEVEL', 'debug'),
11 'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
12 'replace_placeholders' => true,
13 ],
14 
15 'slack' => [
16 'driver' => 'slack',
17 'url' => env('LOG_SLACK_WEBHOOK_URL'),
18 'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
19 'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
20 'level' => env('LOG_LEVEL', 'critical'),
21 'replace_placeholders' => true,
22 ],
23],

讓我們分析一下這個配置。首先,注意我們的 stack 通道透過其 channels 選項聚合了另外兩個通道:syslogslack。因此,在記錄訊息時,這兩個通道都有機會記錄該訊息。然而,正如我們在下面將看到的,這些通道是否實際記錄訊息取決於訊息的嚴重程度 /“級別”。

日誌級別

請注意上述 syslogslack 通道配置中存在的 level 配置選項。此選項決定了訊息被通道記錄所需的最低“級別”。為 Laravel 日誌服務提供支援的 Monolog,提供了 RFC 5424 規範中定義的所有日誌級別。按嚴重程度降序排列,這些日誌級別為:emergencyalertcriticalerrorwarningnoticeinfodebug

那麼,假設我們使用 debug 方法記錄了一條訊息。

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

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

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

寫入日誌訊息

您可以使用 Log 門面 (Facade) 將資訊寫入日誌。如前所述,記錄器提供了 RFC 5424 規範中定義的八個日誌級別:emergencyalertcriticalerrorwarningnoticeinfodebug

1use Illuminate\Support\Facades\Log;
2 
3Log::emergency($message);
4Log::alert($message);
5Log::critical($message);
6Log::error($message);
7Log::warning($message);
8Log::notice($message);
9Log::info($message);
10Log::debug($message);

您可以呼叫這些方法中的任何一個來為相應的級別記錄訊息。預設情況下,訊息將被寫入由您的 logging 配置檔案配置的預設日誌通道。

1<?php
2 
3namespace App\Http\Controllers;
4 
5use App\Models\User;
6use Illuminate\Support\Facades\Log;
7use Illuminate\View\View;
8 
9class UserController extends Controller
10{
11 /**
12 * Show the profile for the given user.
13 */
14 public function show(string $id): View
15 {
16 Log::info('Showing the user profile for user: {id}', ['id' => $id]);
17 
18 return view('user.profile', [
19 'user' => User::findOrFail($id)
20 ]);
21 }
22}

上下文資訊

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

1use Illuminate\Support\Facades\Log;
2 
3Log::info('User {id} failed to login.', ['id' => $user->id]);

有時,您可能希望指定一些應包含在特定通道中所有後續日誌條目中的上下文資訊。例如,您可能希望記錄與應用程式的每個傳入請求相關聯的請求 ID。要實現這一點,您可以呼叫 Log 門面的 withContext 方法。

1<?php
2 
3namespace App\Http\Middleware;
4 
5use Closure;
6use Illuminate\Http\Request;
7use Illuminate\Support\Facades\Log;
8use Illuminate\Support\Str;
9use Symfony\Component\HttpFoundation\Response;
10 
11class AssignRequestId
12{
13 /**
14 * Handle an incoming request.
15 *
16 * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
17 */
18 public function handle(Request $request, Closure $next): Response
19 {
20 $requestId = (string) Str::uuid();
21 
22 Log::withContext([
23 'request-id' => $requestId
24 ]);
25 
26 $response = $next($request);
27 
28 $response->headers->set('Request-Id', $requestId);
29 
30 return $response;
31 }
32}

如果您想在*所有*日誌通道中共享上下文資訊,可以呼叫 Log::shareContext() 方法。此方法會將上下文資訊提供給所有已建立的通道以及隨後建立的任何通道。

1<?php
2 
3namespace App\Http\Middleware;
4 
5use Closure;
6use Illuminate\Http\Request;
7use Illuminate\Support\Facades\Log;
8use Illuminate\Support\Str;
9use Symfony\Component\HttpFoundation\Response;
10 
11class AssignRequestId
12{
13 /**
14 * Handle an incoming request.
15 *
16 * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
17 */
18 public function handle(Request $request, Closure $next): Response
19 {
20 $requestId = (string) Str::uuid();
21 
22 Log::shareContext([
23 'request-id' => $requestId
24 ]);
25 
26 // ...
27 }
28}

如果您需要在處理排隊作業時共享日誌上下文,可以使用 作業中介軟體

寫入特定通道

有時您可能希望將訊息記錄到應用程式預設通道以外的其他通道。您可以使用 Log 門面上的 channel 方法來檢索並在配置檔案中定義的任何通道中進行記錄。

1use Illuminate\Support\Facades\Log;
2 
3Log::channel('slack')->info('Something happened!');

如果您想建立一個由多個通道組成的按需日誌堆疊,可以使用 stack 方法。

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

按需通道

也可以透過在執行時提供配置來建立按需通道,而無需將該配置存放在應用程式的 logging 配置檔案中。要實現這一點,您可以將配置陣列傳遞給 Log 門面的 build 方法。

1use Illuminate\Support\Facades\Log;
2 
3Log::build([
4 'driver' => 'single',
5 'path' => storage_path('logs/custom.log'),
6])->info('Something happened!');

您可能還希望將按需通道包含在按需日誌堆疊中。這可以透過將您的按需通道例項包含在傳遞給 stack 方法的陣列中來實現。

1use Illuminate\Support\Facades\Log;
2 
3$channel = Log::build([
4 'driver' => 'single',
5 'path' => storage_path('logs/custom.log'),
6]);
7 
8Log::stack(['slack', $channel])->info('Something happened!');

Monolog 通道自定義

為通道自定義 Monolog

有時您可能需要完全控制 Monolog 如何為現有通道進行配置。例如,您可能想為 Laravel 內建的 single 通道配置自定義的 Monolog FormatterInterface 實現。

首先,在通道的配置中定義一個 tap 陣列。tap 陣列應包含一個類列表,這些類有機會在 Monolog 例項建立後對其進行自定義(或“接入”)。這些類沒有特定的存放位置,因此您可以自由地在應用程式中建立一個目錄來存放它們。

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

配置完通道的 tap 選項後,就可以定義將自定義 Monolog 例項的類了。此類只需要一個方法:__invoke,它接收一個 Illuminate\Log\Logger 例項。Illuminate\Log\Logger 例項會將所有方法呼叫代理給底層的 Monolog 例項。

1<?php
2 
3namespace App\Logging;
4 
5use Illuminate\Log\Logger;
6use Monolog\Formatter\LineFormatter;
7 
8class CustomizeFormatter
9{
10 /**
11 * Customize the given logger instance.
12 */
13 public function __invoke(Logger $logger): void
14 {
15 foreach ($logger->getHandlers() as $handler) {
16 $handler->setFormatter(new LineFormatter(
17 '[%datetime%] %channel%.%level_name%: %message% %context% %extra%'
18 ));
19 }
20 }
21}

所有的“tap”類都由 服務容器 解析,因此它們所需的任何建構函式依賴項都將被自動注入。

建立 Monolog 處理程式通道

Monolog 擁有多種可用的處理程式,而 Laravel 並沒有為每一個處理程式包含內建通道。在某些情況下,您可能希望建立一個僅作為特定 Monolog 處理程式例項的自定義通道,而該處理程式沒有對應的 Laravel 日誌驅動程式。這些通道可以使用 monolog 驅動程式輕鬆建立。

使用 monolog 驅動程式時,handler 配置選項用於指定將要例項化的處理程式。可選擇地,可以使用 handler_with 配置選項指定處理程式所需的任何建構函式引數。

1'logentries' => [
2 'driver' => 'monolog',
3 'handler' => Monolog\Handler\SyslogUdpHandler::class,
4 'handler_with' => [
5 'host' => 'my.logentries.internal.datahubhost.company.com',
6 'port' => '10000',
7 ],
8],

Monolog 格式化程式

使用 monolog 驅動程式時,Monolog 的 LineFormatter 將作為預設格式化程式使用。但是,您可以使用 formatterformatter_with 配置選項自定義傳遞給處理程式的格式化程式型別。

1'browser' => [
2 'driver' => 'monolog',
3 'handler' => Monolog\Handler\BrowserConsoleHandler::class,
4 'formatter' => Monolog\Formatter\HtmlFormatter::class,
5 'formatter_with' => [
6 'dateFormat' => 'Y-m-d',
7 ],
8],

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

1'newrelic' => [
2 'driver' => 'monolog',
3 'handler' => Monolog\Handler\NewRelicHandler::class,
4 'formatter' => 'default',
5],

Monolog 處理器

Monolog 也可以在記錄訊息之前對它們進行處理。您可以建立自己的處理器或使用 Monolog 提供的現有處理器

如果您想為 monolog 驅動程式自定義處理器,請在通道的配置中新增 processors 配置值。

1'memory' => [
2 'driver' => 'monolog',
3 'handler' => Monolog\Handler\StreamHandler::class,
4 'handler_with' => [
5 'stream' => 'php://stderr',
6 ],
7 'processors' => [
8 // Simple syntax...
9 Monolog\Processor\MemoryUsageProcessor::class,
10 
11 // With options...
12 [
13 'processor' => Monolog\Processor\PsrLogMessageProcessor::class,
14 'with' => ['removeUsedContextFields' => true],
15 ],
16 ],
17],

透過工廠建立自定義通道

如果您想定義一個完全自定義的通道,並在此通道中對 Monolog 的例項化和配置擁有完全控制權,則可以在 config/logging.php 配置檔案中指定 custom 驅動程式型別。您的配置應包含一個 via 選項,其中包含用於建立 Monolog 例項的工廠類的名稱。

1'channels' => [
2 'example-custom-channel' => [
3 'driver' => 'custom',
4 'via' => App\Logging\CreateCustomLogger::class,
5 ],
6],

配置完 custom 驅動程式通道後,您就可以定義建立 Monolog 例項的類了。此類只需要一個 __invoke 方法,該方法應返回 Monolog 記錄器例項。該方法將接收通道配置陣列作為其唯一引數。

1<?php
2 
3namespace App\Logging;
4 
5use Monolog\Logger;
6 
7class CreateCustomLogger
8{
9 /**
10 * Create a custom Monolog instance.
11 */
12 public function __invoke(array $config): Logger
13 {
14 return new Logger(/* ... */);
15 }
16}

使用 Pail 跟蹤日誌訊息

通常您可能需要即時跟蹤應用程式的日誌。例如,在除錯問題或監控應用程式日誌中特定型別的錯誤時。

Laravel Pail 是一個允許您直接從命令列輕鬆檢視 Laravel 應用程式日誌檔案的包。與標準的 tail 命令不同,Pail 設計用於處理任何日誌驅動程式,包括 Sentry 或 Flare。此外,Pail 還提供了一組有用的過濾器,幫助您快速找到所需內容。

安裝

Laravel Pail 需要 PCNTL PHP 擴充套件。

首先,使用 Composer 包管理器將 Pail 安裝到您的專案中:

1composer require --dev laravel/pail

用法

要開始跟蹤日誌,請執行 pail 命令:

1php artisan pail

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

1php artisan pail -v

為了獲得最大詳細程度並顯示異常堆疊跟蹤,請使用 -vv 選項:

1php artisan pail -vv

要停止跟蹤日誌,請隨時按下 Ctrl+C

過濾日誌

--filter

您可以使用 --filter 選項按型別、檔案、訊息和堆疊跟蹤內容過濾日誌。

1php artisan pail --filter="QueryException"

--message

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

1php artisan pail --message="User created"

--level

--level 選項可用於按其日誌級別過濾日誌。

1php artisan pail --level=error

--user

要僅顯示在特定使用者身份驗證期間寫入的日誌,您可以將使用者的 ID 提供給 --user 選項。

1php artisan pail --user=1