Laravel/Lumen 自定义错误日志格式过滤堆栈信息

作者: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 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!