虾皮的员工都说好,PHP 错误日志监控钉钉通知

虾皮的员工都说好,PHP 错误日志监控钉钉通知

Tp & Laravel 错误日志 钉钉机器人通知

这边以Tp为例子,Laravel 差不多也是一样的,其实学会了这个框架复写可以做很多有趣的事情

前言

在我以往的开发的过程中,会写一些错误的日志,但是一般运营那边没有反馈,我们也不太会在意日志,所以一般小概率的报错也不会有人察觉,这样不好,不好。

在当下互联网寒冬,容不得我们半点马虎,所以还是要认证对待自己的工作,报错的时候第一时间知道,偷偷的改掉。

Tp & Laravel 复写 错误日志服务 实现 钉钉机器人通知[保姆级教程]

思路

借助免费的钉钉消息通知,可以很方便实现我们客户端的通知,但是也不能让程序一直 发送这个错误信息吧,所以还是要做限制的,一天同一个错误不超过我们自己指定的数量。

开发

  • 继承方式 修改 Log 源码加入钉钉通知逻辑
  • 站在在巨人的肩膀上触发具体的钉钉通知

查看 config/log.php文件代码,看到框架已经兼容了多渠道配置的方式,比如这边可以用file的方式,当然也可以扩展es或者kafka的驱动方式

├─log
│  │  Channel.php
│  │  ChannelSet.php
│  │
│  └─driver
│          File.php
│          Socket.php

其实如何写扩展,官方文档也没有写,这个就需要结合大家的经验去做了。这就需要查阅源码才可以实现了。

当我配置一个渠道为fileNotice 执行一下程序报:Driver [FileNotice] not supported 我们就可以根据 这个not supported 关键词找到具体报错的位置了。

    /**
     * 获取驱动类
     * @param string $type
     * @return string
     */
    protected function resolveClass(string $type): string
    {
        if ($this->namespace || false !== strpos($type, '\\')) {
            $class = false !== strpos($type, '\\') ? $type : $this->namespace . Str::studly($type);
            if (class_exists($class)) {
                return $class;
            }
        }
        throw new InvalidArgumentException("Driver [$type] not supported.");
    }

以上就是具体报错的位置,从这边的判断,如果包含反斜杠,也就是命名空间+类 就直接引用了,最终我的配置文件这样写

<?php
// +----------------------------------------------------------------------
// | 日志设置
// +----------------------------------------------------------------------
return [
    // 默认日志记录通道
    'default'      => env('log.channel', 'fileNotice'),
    // 日志记录级别
    'level'        => [],
    // 日志类型记录的通道 ['error'=>'email',...]
    'type_channel' => [],
    // 关闭全局日志写入
    'close'        => false,
    // 全局日志处理 支持闭包
    'processor'    => null,
    // 日志通道列表
    'channels'     => [
        'file' => [
            // 日志记录方式
            'type'           => 'File',
            // 日志保存目录
            'path'           => '',
            // 单文件日志写入
            'single'         => false,
            // 独立日志级别
            'apart_level'    => [],
            // 最大日志文件数量
            'max_files'      => 0,
            // 使用JSON格式记录
            'json'           => false,
            // 日志处理
            'processor'      => null,
            // 关闭通道日志写入
            'close'          => false,
            // 日志输出格式化
            'format'         => '[%s][%s] %s',
            // 是否实时写入
            'realtime_write' => false,
        ],
        'fileNotice' => [
            // 日志记录方式
            'type'           => 'app\log\FileNotice',
            // 日志保存目录
            'path'           => '',
            // 单文件日志写入
            'single'         => false,
            // 独立日志级别
            'apart_level'    => [],
            // 最大日志文件数量
            'max_files'      => 0,
            // 使用JSON格式记录
            'json'           => false,
            // 日志处理
            'processor'      => null,
            // 关闭通道日志写入
            'close'          => false,
            // 日志输出格式化
            'format'         => '[%s][%s] %s',
            // 是否实时写入
            'realtime_write' => false,
            //钉钉token
            'token' => 'xxx',
            //钉钉secret
            'secret' => 'xxx',
        ],
        // 其它日志通道配置
    ],
];

这边添加了钉钉token的配置项和secret 的配置项,这边可以灵活的使用 env 不同环境之间的切换

接下去创建具体的日志文件 app/log/FileNotice,我们继承文件 File 类型就好了

<?php
namespace app\log;
use think\log\driver\File;
class FileNotice extends File
{
}

现在可以复写具体的类和方法了,差一个钉钉发送的功能,这个之前论坛老哥已经推荐过消息发送的包,直接引用即可。

composer require guanguans/notify -vvv

save 是复制过来的方法 具体实现代码:

<?php
namespace app\log;
use Guanguans\Notify\Factory;
use think\log\driver\File;
class FileNotice extends File
{
    /**
     * 日志写入接口
     * @access public
     * @param array $log 日志信息
     * @return bool
     */
    public function save(array $log): bool
    {
        $destination = $this->getMasterLogFile();
        $path = dirname($destination);
        !is_dir($path) && mkdir($path, 0755, true);
        $info = [];
        // 日志信息封装
        $time = \DateTime::createFromFormat('0.u00 U', microtime())->setTimezone(new \DateTimeZone(date_default_timezone_get()))->format($this->config['time_format']);
        foreach ($log as $type => $val) {
            $message = [];
            foreach ($val as $msg) {
                if (!is_string($msg)) {
                    $msg = var_export($msg, true);
                }
                $message[] = $this->config['json'] ?
                    json_encode(['time' => $time, 'type' => $type, 'msg' => $msg], $this->config['json_options']) :
                    sprintf($this->config['format'], $time, $type, $msg);
            }
            if (true === $this->config['apart_level'] || in_array($type, $this->config['apart_level'])) {
                // 独立记录的日志级别
                $filename = $this->getApartLevelFile($path, $type);
                $this->write($message, $filename);
                continue;
            }
            $send_num = cache($msg, 0);
            //这边写入钉钉发送的逻辑
            if ($type == 'error' && $send_num < 1) {
                Factory::dingTalk()
                    ->setToken($this->config['token'])
                    ->setSecret($this->config['secret'])
                    ->setMessage((new \Guanguans\Notify\Messages\DingTalk\LinkMessage([
                        'title' => '错误详情',
                        'text' => $msg,
                        'messageUrl' => $this->config['messageUrl'],
                        'picUrl' => 'https://avatars.githubusercontent.com/u/22309277?v=4',
                    ])))
                    ->send();
                cache($msg, $send_num++, 24 * 3600);
            }
            $info[$type] = $message;
        }
        if ($info) {
            return $this->write($info, $destination);
        }
        return true;
    }
}

触发

 Log::error('测试报错');

效果:

Tp & Laravel 错误日志 钉钉机器人通知

寒冬来了,有义务 分享给瑟瑟发抖的各位

Tp & Laravel 复写 错误日志服务 实现 钉钉机器人通知[保姆级教程]

原文链接

                       

点击阅读全文

上一篇 2023年 6月 11日 am10:14
下一篇 2023年 6月 11日 am10:15