Laravel/Lumen 自定義錯誤日誌格式過濾堆疊資訊

mystudytime發表於2020-03-03

作者:studytime(有問題可以到作者部落格下留言)
原文連結:https://www.studytime.xin

概述

Laravel/Lumen的日誌預設是基於Monolog進行的一層封裝,簡單使用的話文件還是很清晰的。

但是當我希望過濾錯誤日誌裡面的大量堆疊時候,以及一些其他自定義配置時,發現5.6版本之後對日誌系統做了升級。原有的configureMonologUsing自定義Monolog設定已經廢棄。參照新的文件和原始碼,重新做了封裝後,基本解決異常日誌堆疊問題。

構建自定義日誌通道

所有的應用程式日誌系統配置都位於 config/logging.php 配置檔案中。這個檔案允許你配置你的應用程式日誌通道,所以務必檢視每個可用的通道及它們的選項。

lumen 框架下該配置檔案可以在vendor包中找到對應的配置模板檔案,複製到config下,同時需要在bootstrap/app.php中註冊。

$app->configure($config);

針對具體的配置資訊簡單的在下文demo說明下:

<?php

// 配置檔案路徑:/config/logging.php

return [

    // 預設用哪個
    'default' => env('LOG_CHANNEL', 'stack'),

    'channels' => [
        //自定義頻道
        'myapplog' => [
            // 日誌驅動模式:
            'driver' => 'daily',                           
            // 日誌存放路徑
            'path' => storage_path('logs/myapplog.log'),
            // 日誌等級:
            'level' => 'info',
            // 日誌分片週期,多少天一個檔案
            'days' => 1,
        ],
    ],
];

日誌使用

<?php
use Log;

class LogTestController extends Controller
{
  $message = 'Some message';
  $log = ['site_name'=>'studytime.xin','site_id'=>'1']; 
  Log::channel('myapplog')->info($message, $log);  //Log後的陣列會自動轉成Json存到日誌記錄中
}

日誌寫入結果

[2020-03-01 10:22:28] local.INFO: Some message {'site_name':'studytime.xin','site_id':'1'}

高度自定義 Monolog 通道

有時需要完全控制已存在通道的 Monolog: 比如,你可能想要為給定通道的日誌處理配置自定義的 Monolog FormatterInterface 實現:

先在通道配置中定義一個 tap 陣列。 tap 陣列包含一個在通道建立後有機會用於自定義 Monolog 例項的類列表:

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

一旦在通道中有了 tap 選項配置,就要準備用於自定義 Monolog 例項的類。這種類這需要一個方法: __invoke,它接受一個 Illuminate\Log\Logger 例項作為其引數。 Illuminate\Log\Logger 例項將所有方法呼叫代理到基礎的 Monolog 例項:

<?php

namespace App\Logging;

class CustomizeFormatter
{
    /**
     * 自定義給定的日誌例項。
     *
     * @param  \Illuminate\Log\Logger  $logger
     * @return void
     */
    public function __invoke($logger)
    {
        foreach ($logger->getHandlers() as $handler) {
            $handler->setFormatter(...);
        }
    }
}

Tip:所有的 “tap” 類都是由 服務容器 解析的,因此任何依賴它們的構造器都會自動被注入。這段算是官方文件的說明了。

如何實現自定義錯誤日誌格式、過濾大量異常日誌堆疊

在 config/logging.php 建立對應的處理渠道

'channels' => [
    'custom' => [
        'driver' => 'daily',
        'path' => storage_path('logs/lumen.log'),
        'tap' => [App\Logging\MyLogFormatter::class],
        'days' => 1,
        'level' => 'info', 
    ],
],

在 app/Logging 目錄下建立 MyLogFormatter 自定義 Monolog 例項的類

<?php

namespace App\Logging;

class MyLogFormatter
{
    public function __invoke($logger)
    {
        foreach ($logger->getHandlers() as $handler) {
            $handler->setFormatter(new LineFormatter());
        }
    }
}

在 app/Logging 目錄下建立 用於重寫 Monolog 的 LineFormatter 格式化處理器

LineFormatter 是 Monolog 預設的格式化處理器

<?php


namespace App\Logging;

use Illuminate\Support\Str;
use Monolog\Formatter\LineFormatter as BaseLineFormatter;
use Monolog\Formatter\NormalizerFormatter;

class LineFormatter extends BaseLineFormatter
{
    const NEW_SIMPLE_FORMAT = "[%datetime%] [%uuid%] %channel%.%level_name%: %message% %context% %extra%\n";

    public function format(array $record)
    {
        $output = self::NEW_SIMPLE_FORMAT;
        $vars   = (new NormalizerFormatter())->format($record);
        $vars['uuid'] = 'uuid:' . Str::uuid();
        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);
            }
        }
        // remove leftover %extra.xxx% and %context.xxx% if any
        if (false !== strpos($output, '%')) {
            $output = preg_replace('/%(?:extra|context)\..+?%/', '', $output);
        }
        return $output;
    }
}

異常日誌處理效果

[2020-03-03 21:42:36] [uuid:374b0045-7aef-4d4b-9ba0-fce13d8dd776] local.ERROR:  {"class":"Illuminate\\Queue\\MaxAttemptsExceededException","message":"App\\Jobs\\DownloadReportDataJob has been attempted too many times or run too long. The job may have previously timed out.","code":0,"file":"/data/wwwroot/advertisingscript-line/vendor/illuminate/queue/Worker.php:601"} []

此時可以看到異常的日誌已經沒有大量的堆疊問題。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章