在 Laravel 中集成钉钉日志通知:借鉴 Slack 的实现

前几天论坛上提到异常监控,除了直接处理异常本身,由于 Laravel 异常也会输出到日志,还可通过日志来提醒异常。这样无需修改核心代码,还方便从配置控制。

Laravel 日志底层使用 Monolog,它默认不支持钉钉。Laravel 和 Monolog 文档非常齐全,我们可以自己开发。

由于钉钉和 Slack 类似,而 Laravel 自带 Slack 支持,我们可以参考 Slack 的实现效果来完成钉钉的集成。

集成钉钉到 Laravel 日志,参考 Slack

实现钉钉 Logger

Monolog 支持各式各样的 Logger 来处理日志,我们定义一个新的 Logger,通过钉钉机器人提供的 Webhook 发送日志。

  • 参考 Slack 的效果,使用 Markdown 格式渲染日志
// app/Logging/DingtalkLogger.php
class DingtalkLogger
{
    public function __invoke(array $config): Logger
    {
        return new Logger('dingtalk', [
            new DingtalkHandler($config['url'], $config['level']),
        ]);
    }
}
// app/Logging/DingtalkHandler.php
class DingtalkHandler extends AbstractProcessingHandler
{
    private string $webhookUrl;

    public function __construct(
        string $webhookUrl,
        string $level = 'critical',
        bool $bubble = true,
    ) {
        parent::__construct($level, $bubble);

        $this->webhookUrl = $webhookUrl;
    }

    protected function write(LogRecord $record): void
    {
        Http::post($this->webhookUrl, $this->message($record));
    }

    private function message(LogRecord $record): array
    {
        $sections = [
            'message' => $record->message,
            'level' => Str::upper($record->level->name),
        ];

        $normalizerFormatter = new NormalizerFormatter();
        foreach (array_merge($record->context, $record->extra) as $key => $value) {
            $sections[$key] = $normalizerFormatter->normalizeValue($value);
        }

        $text = collect($sections)->map(function ($value, $key) {
            $title = Str::limit(ucfirst($key));

            $valueStr = Str::limit($this->stringify($value), 1000);
            $content = is_array($value) ? sprintf("```\n%s\n```", $valueStr) : $valueStr;

            return sprintf("# %s\n%s", $title, $content);
        })->implode("\n");

        return [
            'msgtype' => 'markdown',
            'markdown' => [
                'title' => 'Laravel Log',
                'text' => $text,
            ],
        ];
    }

    private function stringify($value): string
    {
        return is_string($value) ?
            $value :
            Utils::jsonEncode($value, Utils::DEFAULT_JSON_FLAGS | JSON_PRETTY_PRINT);
    }
}

集成到 Laravel 日志

新增一个 Laravel 日志 Channel:

// config/logging.php
'channels' => [
    'dingtalk' => [
        'driver' => 'custom',
        'url' => env('LOG_DINGTALK_WEBHOOK_URL'),
        'via' => App\Logging\DingtalkLogger::class,
        'level' => env('LOG_LEVEL', 'critical'),
    ],
],

不要忘记增加配置:

Laravel 10 需要注意下 LOG_STACK,默认是固定值

// .env
LOG_CHANNEL=stack
LOG_STACK=single,dingtalk
LOG_DINGTALK_WEBHOOK_URL=https://oapi.dingtalk.com/robot/send?access_token=<your-token>

测试下效果

运行一个未知命令触发异常:

$ php artisan foobar

这是钉钉的效果:

在 Laravel 中集成钉钉日志通知:借鉴 Slack 的实现

  • Slack 的效果总体美观
  • 钉钉 Markdown 代码块不保留缩进
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 4

其实可以用 sentry ,在线saas有免费版本,也可以自建

4周前 评论
xuchunyang (楼主) 4周前
xuchunyang (楼主) 4周前

哇,这个思路很棒,没想过自定义Monolog去实现,又打开一个新世界 :+1:

4周前 评论

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