在 Laravel 中集成钉钉日志通知:借鉴 Slack 的实现
前几天论坛上提到异常监控,除了直接处理异常本身,由于 Laravel 异常也会输出到日志,还可通过日志来提醒异常。这样无需修改核心代码,还方便从配置控制。
Laravel 日志底层使用 Monolog,它默认不支持钉钉。Laravel 和 Monolog 文档非常齐全,我们可以自己开发。
由于钉钉和 Slack 类似,而 Laravel 自带 Slack 支持,我们可以参考 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
这是钉钉的效果:
- Slack 的效果总体美观
- 钉钉 Markdown 代码块不保留缩进
其实可以用 sentry ,在线saas有免费版本,也可以自建
哇,这个思路很棒,没想过自定义Monolog去实现,又打开一个新世界 :+1: