日誌

簡介

為了協助你瞭解程式中發生的大小事,Laravel 提供了強健的日誌(Log)服務,能讓你將訊息紀錄到檔案、系統錯誤日誌(Error Log)、甚至是紀錄到 Slack 中來通知整個團隊。

Laravel 的 Log 紀錄是基於「通道(Channel)」的。每個通道都代表了一種寫入日誌資訊的特定方法。舉例來說,single 通道將日誌寫進單一日誌檔中,而 slack 通道則將日誌訊息傳送到 Slack。也可以依據日誌的嚴重性(Severity)來將日誌訊息寫到多個通道。

在 Laravel 內部,我們使用了 Monolog 函式庫。Monolog 提供了多種強大的日誌處理程式(Handler)。Laravel 還讓我們能輕鬆地設定這些 Monolog 的日誌處理程式,可以混合使用不同處理程式來為我們的程式處理日誌。

設定

用於設定程式日誌行為的設定選項都放在 config/logging.php 設定檔中。我們可以使用這個檔案來設定專案的日誌通道,因此建議你瞭解一下各個可用的通道與其對應的選項。稍後我們會來討論一些常見的選項。

預設情況下,Laravel 會使用 stack 通道來紀錄日誌訊息。stack 通道可用來將多個日誌通道彙總到單一通道內。有關建立 Stack 的詳細資訊,請參考下方的說明文件

設定通道名稱

預設情況下,在初始化 Monologo 時,會使用目前環境的名稱來作為「通道名稱」,如 productionlocal。若要更改通道名稱,請在通道設定中加上 name 選項:

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

可用的通道 Driver

每個日誌通道都以一個「Driver」驅動。Driver 會判斷要如何紀錄日誌訊息、以及要將日誌訊息紀錄到哪裡。在所有的 Laravel 應用程式中都可使用下列日誌通道 Driver。在你專案中的 config/logging.php 設定檔內已經預先填好下表中大部分的 Driver,因此我們建議你可瞭解一下這個檔案以熟悉其內容:

名稱說明
custom會呼叫特定 Factory 建立通道的 Driver
daily會每日重置(Rotate)之一個基於 RotatingFileHandler 的 Monolog Driver
errorlog基於 ErrorLogHandler 的 Monolog Driver
monolog可使用任意支援的 Monolog Handler 之 Monolog Factory Driver
null會取消所有 Log 訊息的 Driver
papertrail基於 SyslogUdpHandler 的 Monolog Driver
single基於單一檔案或路徑的 Logger 通道 (StreamHandler)
slack基於 SlackWebhookHandler 的 Monolog Driver
stack會建立「多通道」通道的包裝
syslog基於 SyslogHandler 的 Monolog Driver
lightbulb

請閱讀進階的通道客製化以瞭解更多有關 monologcustom Driver 的資訊。

通道的前置需求

設定 Single 與 Daily 通道

single (單一) 與 daily (每日) 通道有三個可選的設定選項:bubblepermissionlocking

名稱說明預設
bubble代表該訊息被處理後是否應向上傳遞(Bubble)給其他通道true
locking在寫入 Log 檔前嘗試鎖定該檔案false
permissionLog 檔的權限0644

設定 Papertrail 通道

papertrail 通道有 hostport 兩個必填的設定選項。可以從 Papertrail 上取得這些值。

設定 Slack 通道

slack 通道有一個 url 必填設定選項。該 URL 應為在 Slack 團隊上設定之傳入的 Webhook URL。

預設情況下,Slack 只會接收等級為 critical 或以上的日誌。不過,也可以在 config/logging.php 設定檔中更改 Slack 日誌通道的 level 選項來調整要接收的等級。

紀錄 Deprecation Warning

PHP、Laravel、或是其他函式庫等,通常會通知使用者其部分功能已棄用(Deprecated),且將在未來的版本中移除這些功能。若想收到這些棄用警告,可在 config/logging.php 設定檔中設定要用於記錄 deprecations 日誌的通道:

1'deprecations' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
2 
3'channels' => [
4 ...
5]
1'deprecations' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
2 
3'channels' => [
4 ...
5]

或者,也可以定義一個名為 deprecations 的日誌通道。若有該名稱的通道,Laravel 會使用該通道來紀錄 Deprecation 日誌:

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

建立日誌 Stack

剛才也提到過,stack Driver 能讓我們將多個通道組合為單一日誌通道來更方便地使用。為了說明如何使用日誌的 Stack(堆疊),我們先來看看下面這個可能出現在正式專案中的範例設定檔:

1'channels' => [
2 'stack' => [
3 'driver' => 'stack',
4 'channels' => ['syslog', 'slack'],
5 ],
6 
7 'syslog' => [
8 'driver' => 'syslog',
9 'level' => 'debug',
10 ],
11 
12 'slack' => [
13 'driver' => 'slack',
14 'url' => env('LOG_SLACK_WEBHOOK_URL'),
15 'username' => 'Laravel Log',
16 'emoji' => ':boom:',
17 'level' => 'critical',
18 ],
19],
1'channels' => [
2 'stack' => [
3 'driver' => 'stack',
4 'channels' => ['syslog', 'slack'],
5 ],
6 
7 'syslog' => [
8 'driver' => 'syslog',
9 'level' => 'debug',
10 ],
11 
12 'slack' => [
13 'driver' => 'slack',
14 'url' => env('LOG_SLACK_WEBHOOK_URL'),
15 'username' => 'Laravel Log',
16 'emoji' => ':boom:',
17 'level' => 'critical',
18 ],
19],

讓我們來逐步分析這個設定檔。首先,可以注意到 stack 通道使用 channels 選項來彙總了另外兩個通道:syslogslack。所以,在紀錄日誌訊息時,這兩個頻道都可能會去紀錄該訊息。不過,我們稍後會看到,實際上這兩個通道會依照訊息的嚴重程度 (「等級(Level)」) 來判斷是否要紀錄訊息。

日誌的等級

來看看上述範例中 syslogslack 通道設定中的 level 設定。這個選項用來判斷該通道所要紀錄的最小訊息「等級(Level)」。Monolog —— 負責提供 Laravel Log 服務的函式庫 —— 提供了所有在 RFC 5424 規格中定義的所有日誌等級:emergency, alert, critical, error, warning, notice, info, 與 debug

所以,假設我們使用 debug 方法來紀錄訊息:

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

在我們的設定檔中,syslog 通道會將該訊息寫到系統日誌(System Log)中。不過,因為這個訊息不是 critical 或以上的等級,因此這個訊息不會被傳送到 Slack。不過,若我們紀錄 emergency 等級的訊息,則該訊息就會被送到系統日誌與 Slack 兩個地方,因為 emergency 等級大於我們為這兩個通道設定的最小等級門檻:

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

寫入日誌訊息

可以使用 Log [Facade] 來將訊息寫入到日誌中。剛才也提到過,日誌程式提供了八個等級,這八個等級定義在 RFC 5424 規格中:emergency, alert, critical, error, warning, notice, infodebug

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

有上下文的資訊

可以傳入一組包含上下文資料(Contextual Data)的陣列給日誌方法。這些上下文資料會被格式化並與日誌訊息一起顯示:

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

有時候,我們可能會想讓某些上下文資訊被包含在接下來所有紀錄的日誌中。舉例來說,我們紀錄能關聯上連入 Request 的 Request ID。為此,可呼叫 Log Facade 的 withContext 方法:

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

寫入指定的通道

有時候,我們可能會想將訊息寫到預設通道以外的其他通道。可以使用 Log Facade 上的 channel 方法來取得並紀錄到設定檔中定義的任何通道:

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

若想視需要建立由多個通道組合成的日誌 Stack,可使用 stack 方法:

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

視需要建立的通道

也可以在不寫入設定檔的情況下在執行階段(Runtime)視需要建立通道。要建立視需要時建立的通道,請傳入一個設定用陣列給 Log Facade 的 build 方法:

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

也可以在視需要建立的日誌 Stack 中包含一個視需要建立的通道。只要在傳給 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!');
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 實作。

要開始自訂 Monolog,請在通道設定中定義一個 tap 陣列。tap 陣列中應包含一組要用來在 Monolog 實體建立完畢後自訂 (或「監聽(Tap)」進) Monologo 實體的類別。對於要將這些類別放在哪裡,Laravel 並沒有相關規範。因此,我們可以隨意在專案內建立目錄來放置這些類別:

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

在通道上設定好 tap 選項後,就可以開始定義用來自訂 Monolog 實體的類別了。這個類別只需要有一個方法即可:__invoke。該方法會收到 Illuminate\Log\Logger 實體,該實體會將所有的方法呼叫代理到底層的 Monolog 實體:

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

所有的「Tap」類別都會由 Service Container 解析,所以在 Constructor(建構函式) 中要求的相依性都會自動被插入。

建立 Monolog Handler 通道

Monolog 中有多個可用的 Handler,Laravel 並未為每個 Handler 都提供一個內建的通道。在某些情況下,我們可能會想給一些沒有對應 Laravel 日誌 Driver 的 Monolog Handler 建立實體作為自訂通道。只要使用 monolog Driver 就可以輕鬆地建立這類通道。

使用 monolog Driver 時,handler 設定選項可用來指定要初始化哪個 Handler。然後,也可以選擇性地使用 with 設定選項來指定該 Handler 的 Constructor(建構函式) 所需要的參數:

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

Monolog 格式

使用 monolog Driver 時,會使用 Monolog 的 LineFormatter 來作為預設的格式化工具(Formatter)。不過,我們也可以使用 formatterformatter_with 設定選項來自訂要傳給該 Handler 的格式化工具:

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],
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 Handler 本身就有提供格式化工具,則可以將 formatter 設定選項設為 default

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

使用 Factory 來建立自訂通道

若想定義整個自訂通道來完整控制 Monolog 的初始化與設定,則可在 config/logging.php 設定檔中使用 custom Driver。設定中應包含一個 via 選項來包含建立 Monolog 實體時要叫用的 Factory(工廠) 類別名稱:

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

設定好 custom Driver 通道後,就可以開始定義用來建立 Monolog 實體的類別了。這個類別只需要有一個 __invoke 方法就好了,該方法應回傳 Monolog Logger(日誌程式)的實體。__invoke 方法會收到一個引數,即為該通道的設定陣列:

1<?php
2 
3namespace App\Logging;
4 
5use Monolog\Logger;
6 
7class CreateCustomLogger
8{
9 /**
10 * Create a custom Monolog instance.
11 *
12 * @param array $config
13 * @return \Monolog\Logger
14 */
15 public function __invoke(array $config)
16 {
17 return new Logger(...);
18 }
19}
1<?php
2 
3namespace App\Logging;
4 
5use Monolog\Logger;
6 
7class CreateCustomLogger
8{
9 /**
10 * Create a custom Monolog instance.
11 *
12 * @param array $config
13 * @return \Monolog\Logger
14 */
15 public function __invoke(array $config)
16 {
17 return new Logger(...);
18 }
19}
翻譯進度
100% 已翻譯
更新時間:
2023年2月11日 上午10:27:00 [世界標準時間]
翻譯人員:
  • cornch
幫我們翻譯此頁

留言

尚無留言

“Laravel” is a Trademark of Taylor Otwell.
The source documentation is released under MIT license. See laravel/docs on GitHub for details.
The translated documentations are released under MIT license. See cornch/laravel-docs-l10n on GitHub for details.