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+1, 24 * 3600);
}
$info[$type] = $message;
}
if ($info) {
return $this->write($info, $destination);
}
return true;
}
}
触发
Log::error('测试报错');
效果:
寒冬来了,有义务 分享给瑟瑟发抖的各位
本作品采用《CC 协议》,转载必须注明作者和本文链接
赞 :+1:
这样是同步的吧 如果同时要多个log(文件写入 各种通知等等) 会影响程序吧?
日志和告警应该区分开
日志应该偏向于准确性,并且保证写入速度
告警应该考虑规则,比如 钉钉一分钟只能超过多少条就不会给你记录了, 相同的错误应该只记录一条,根据不同的规则,区分告警等级 ,
建议使用sentry
用sentry + 1