## Laravel 日志
### 簡介
> laravel 日志系統使用了 Monolog 作用日志系統
- 參考文檔
- 開源地址 https://learnku.com/articles/8108/the-log-system-of-laravel-monolog
- Laravel/Lumen 自定義錯誤日志格式過濾堆棧信息 https://learnku.com/articles/41305
- Laravel 的日志系統 - monolog https://learnku.com/articles/8108/the-log-system-of-laravel-monolog
- 文檔8.5: https://learnku.com/docs/laravel/8.5/logging/10380
### 基本使用方法
- channel 通道|管道|渠道
> channel 是用于將 laravel 日志輸出到不同的通道中的, 例如 single 單個日志文件通道默認所有的日志信息將記錄到 single 中
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
],
> 再比如 daily 通道是按照天數進行記錄, 如果安裝 daily 通道則會每天生成的日志 laravel-xx-xx-xx.log
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => 14,
],
> Slack 通道 是一款即時通信軟件,類似于 QQ,它提供開放的 API,可以調用它向自己團隊中指定的個人或者頻道(Channel)發送消息,因此用它來進行異常通知是再合適不過的
> 如果需要在 Laravel 中使用則必須安裝擴展包
https://github.com/maknz/slack-laravel
- stack 堆棧
> Monlog 中所有的日志管道都通過 stack 來壓入后后遍歷整個通道的堆棧。
### 配置分別對日志寫入兩個文件,一個為數據的形式一個為 JSON 格式
- 使用 tap 自定義重新寫入日志文件中
'alilogo' => [
'driver' => 'daily', // 記錄到每天日志方便日志進行采集
'path' => storage_path('logs/alilogo.log'), // 前綴名稱
'level' => 'debug', // 錯誤級別
'days' => 1, // 保存的日志的天數
'tap' => [\App\Logging\LogstashJsonFormatter::class], // 自定義模式
],
- 接下來編寫 LogstashJsonFormatter 文件
> 原理就是使用 __invoke() 函數每次實例化這個類的時候調用 __invoke()
> processors 的概念: processors 標識一個單獨的處理器, 可以對每個處理器進行設置數據
> 原理:push 使用 php 的 array_unshift(), 第二個參數是傳入的閉包函數,入棧后依次遍歷處理
use Monolog\Formatter\LogstashFormatter as CustomerLogstashFormatter;
public function __invoke($logger) // 這里傳入的是一個 Monolog\Logger 對象
{
foreach ($logger->getHandlers() as $handler) { // 遍歷 handlers, handlers 是一個數組包含了寫入日志的基本信息
$handler->setFormatter(new CustomerLogstashFormatter(env("APP_NAME"))); // 這里的 CustomerLogstashFormatter是別名
$handler->pushProcessor(new WebProcessor());
$handler->pushProcessor(new MemoryUsageProcessor());
$handler->pushProcessor(new ProcessIdProcessor());
$handler->pushProcessor( // 自定義管理參數通過 record
function ($record) {
$record['extra']['env'] = env('APP_ENV'); // 自定義擴展數據
return $record;
}
);
}
}
> 說明: LogstashFormatter 中的 format() 最后輸出的是一個 json 的數據
> 最后我們輸入一串 json 到日志文件中
{"@timestamp":"2022-02-21T15:16:03.741202+08:00","@version":1,"host":"workbench","message":"debug","type":"Laravel","channel":"local","level":"DEBUG","monolog_level":100,"extra":{"env":"local","process_id":4087,"memory_usage":"22 MB"},"context":{"data":[{"id":184,"group_id":130,"name":"默認店鋪","mobile":"13594755555","contacts":"廣東","description":null,"address":null,"email":null,"member_group_id":null,"created_at":"2022-01-12 17:38:10","updated_at":"2022-01-12 17:38:10","deleted_at":null,"instance_id":1},{"id":190,"group_id":130,"name":"門店04","mobile":"13594666622","contacts":"復制人","description":null,"address":"地址","email":null,"member_group_id":null,"created_at":"2022-01-18 14:46:14","updated_at":"2022-01-18 14:46:14","deleted_at":null,"instance_id":1},{"id":191,"group_id":130,"name":"門店名稱","mobile":"13594754466","contacts":"啊啊啊","description":null,"address":"地址","email":null,"member_group_id":null,"created_at":"2022-01-18 14:48:36","updated_at":"2022-01-18 14:48:36","deleted_at":null,"instance_id":1},{"id":194,"group_id":130,"name":"荷花","mobile":"17788882345","contacts":"荷花","description":null,"address":"123","email":null,"member_group_id":null,"created_at":"2022-01-19 15:25:13","updated_at":"2022-01-19 15:25:13","deleted_at":null,"instance_id":1}]}}
### 優化輸出參數
- 時間: 默認的 timestamp 不能直觀的查看
- url: 記錄 url 路徑方便排查文件
- ip: 記錄是那個客戶端發起的請求
## 如何在代碼中按需打印
> 針對不同的情況我們進行打印,首先規定好一些常用的說明, stack 打印可將 alilogo 和 single 共壓入棧中打分別打印到日志中
\Log::stack(['alilogo', 'single'])->debug('debug', $log3);
> 通過通道的方式進行按需打印
\Log::channel('alilogo')->debug('debug', $log2); // 打印到 alilogo 通道
### 解決如何不同環境下按需記錄日志
> 目前想通過直接或者 env , 但是應該有更簡單方法
### 配置 logging.php 使得不用更改代碼就達到記錄的作用
'stack' => [
'driver' => 'stack',
'channels' => ['single', LoggerChannel::ALIYUN_JSON], // 只打印這個兩種
'ignore_exceptions' => false,
],
'aliyun' => [
'driver' => 'daily',
'path' => storage_path('logs/aliyun.log'),
'level' => 'debug', // 只在 debug 級別下生效
'days' => 14,
'tap' => [\App\Logging\LogstashJsonFormatter::class],
],
> 現在只需要在代碼中直接輸出就可以達到效果
\Log::debug('debug', $log3)
### 更進一步優化輸出日志文件
> 通過將不同級別的日志分別放在通過的文件中方便根據優先級進行修復代碼, monolog 中的優先級分別有
- DEBUG (100): 詳細調試信息,經常使用
- INFO (200): 一些常規的信息例如用戶登錄、注冊、SQL 日志
- NOTICE (250): 正常但是又重要事件信息
- WARNING (300): 例如一些過期的函數的警告不影響程序正常執行完畢, 可能需要換個 api
- ERROR (400): 運行時錯誤,不需要立即采取行動,但通常應記錄和監控
- CRITICAL (500): 程序不可用意外情況,例如突然性的中斷
- ALERT (550): 一些提示
- EMERGENCY (600): 例如類沒有引入類沒有實例化等等
### 輸出 error 錯誤的日志到 JSON
> 安裝上面的方法建立一個 LogErrorFormatter
public function __invoke($logger)
{
foreach ($logger->getHandlers() as $handler) {
$handler->setFormatter(new LineFormatter());
}
}
> 繼承 LineFormatter 重寫 format
const NEW_SIMPLE_FORMAT = "[%datetime%] [%uuid%] %channel%.%level_name%: %message% %context% %extra%\n";
public function format(array $record):String
{
$output = self::NEW_SIMPLE_FORMAT; // 定義輸出格式
$vars = (new NormalizerFormatter())->format($record); // 獲取需要格式化的數據
$vars['uuid'] = 'uuid:' . Str::uuid(); // 生成一個唯一id
foreach ($vars['extra'] as $var => $val) { // 替換自定義變量格式
if (false !== strpos($output, '%extra.' . $var . '%')) {
$output = str_replace('%extra.' . $var . '%', $this->stringify($val), $output);
unset($vars['extra'][$var]);
}
}
// 卸載異常、錯誤的堆棧信息
if (isset($vars['context']['exception']) && !empty($vars['context']['exception'])) {
$vars['message'] = '';
$vars['context'] = $vars['context']['exception'];
if (isset($vars['context']['trace'])) {
unset($vars['context']['trace']);
}
if (isset($vars['context']['previous'])) {
unset($vars['context']['previous']);
}
}
// 替換異常堆棧信息
if (false !== strpos($output, '%')) {
$output = preg_replace('/%(?:extra|context)\..+?%/', '', $output);
}
foreach ($vars as $var => $val) {
if (false !== strpos($output, '%' . $var . '%')) {
$output = str_replace('%' . $var . '%', $this->stringify($val), $output);
}
}
if (false !== strpos($output, '%')) {
$output = preg_replace('/%(?:extra|context)\..+?%/', '', $output);
}
return $output;
}
### 調試代碼到輸出為 JSON
- 把默認輸出到文件中,
### 如何在線上環境屏蔽 BusinessException 這種錯誤不讓輸出到日志中