Laravel优雅的添加企业微信/飞书机器人日志通知
- /app/Providers/AppServiceProvider.php文件中register添加
use use Illuminate\Support\Facades\Log;
use App\Handler\FeishuWebhookHandler;
use App\Handler\WorkWeixinWebhookHandler;
use Monolog\Logger as Monolog;
Log::extend('feishu',function ($app,$config){
return new Monolog($this->parseChannel($config), [
$this->prepareHandler(new FeishuWebhookHandler($config)),
]);
});
Log::extend('work_weixin',function ($app,$config){
return new Monolog($this->parseChannel($config), [
$this->prepareHandler(new WorkWeixinWebhookHandler($config)),
]);
});
- /config/logging.php文件中添加channels数组中添加
'feishu'=>[
'driver' => 'feishu',
'level' => env('LOG_LEVEL', 'debug'),
'host' => env('LOG_FEISHU_HOST','https://open.feishu.cn'),
'url' => env('LOG_FEISHU_URL',''),
'token' => env('LOG_FEISHU_TOKEN',''),
'lock_time'=>10 //相同错误内容锁定10秒
],
'work_weixin'=>[ //企业微信
'driver' => 'work_weixin',
'level' => env('LOG_LEVEL', 'debug'),
'host' => env('LOG_WORK_WEIXIN_HOST','https://qyapi.weixin.qq.com'),
'url' => env('LOG_WORK_WEIXIN_URL','/cgi-bin/webhook/send'),
'token' => env('LOG_WORK_WEIXIN_TOKEN',''),
'lock_time'=>10 //相同错误内容锁定10秒
],
- /app/Handler/FeishuWebhookHandler.php飞书机器人调用
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Handler;
use GuzzleHttp\Client as GuzzleHttpClient;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\RequestOptions;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Handler\Slack\SlackRecord;
use Swoole\Coroutine;
/**
* Sends notifications through Slack Webhooks
*
* @author Haralan Dobrev <hkdobrev@gmail.com>
* @see https://api.slack.com/incoming-webhooks
*/
class FeishuWebhookHandler extends AbstractProcessingHandler
{
/**
* Slack Webhook token
* @var string
*/
protected $webhookUrl;
/**
* Instance of the SlackRecord util class preparing data for Slack API.
* @var SlackRecord
*/
protected $token='';
protected $lock_time = 0;
public function __construct($config) {
if (!extension_loaded('curl')) {
throw new MissingExtensionException('The curl extension is needed to use the FeishuWebhookHandler');
}
$bubble = $config['bubble'] ?? true;
parent::__construct($config['level'], $bubble);
$this->webhookUrl = $config['host'].$config['url'];
$this->token = $config['token'];
$this->lock_time = Arr::get($config,'lock_time',0)?:0;
}
public function getWebhookUrl(): string
{
return $this->webhookUrl;
}
protected function makeSign($time = '')
{
$timestamp = $time ? $time : time();
$secret = $this->token;
$string = "{$timestamp}\n{$secret}";
return base64_encode(hash_hmac('sha256', "", $string, true));
}
/**
* {@inheritDoc}
*/
protected function write(array $record): void
{
if($this->lock_time){
$key = 'logs_feishu_lock:'.md5(Arr::get($record,'message',''));
if(Cache::get($key)){
return;
}
Cache::put($key,1,now()->addSecond($this->lock_time));
}
$timestamp = time();
$postData = [
"timestamp" => $timestamp,
"sign" => $this->makeSign($timestamp),
"msg_type" => "text",
"content" => [
"text" => $this->getFormatter()->format($record),
]
];
$sendCallBack = $this->sendCallBack();
if(Coroutine::getcid()>0){
Coroutine\go($sendCallBack,$postData);
}else{
register_shutdown_function($sendCallBack,$postData);
}
}
/**
* 发送消息回调函数
* @return \Closure
*/
protected function sendCallBack(){
return function ($postData){
try {
$client = new GuzzleHttpClient([
'timeout' => 5,
]);
$response = $client->request('POST', $this->webhookUrl, [
'headers' => [
'content-type' => 'application/json',
],
RequestOptions::JSON => $postData,
]);
$body = $response->getBody()->getContents();
$data = json_decode($body, true);
if (Arr::get($data, 'StatusMessage') != 'success') {
$error = ['action' => __METHOD__, 'post_data' => $postData, 'response' => $data];
Log::channel('daily')->error('FeishuBot sendMsg Exception',$error);
}
} catch (GuzzleException $e) {
$error = [ 'action' => __METHOD__, $e->getCode() => $e->getMessage(), 'post_data' => $postData];
Log::channel('daily')->error('FeishuBot sendMsg Exception',$error);
}
};
}
}
- /app/Handler/WorkWeixinWebhookHandler.php企业微信机器人调用
<?php declare(strict_types=1);
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Handler;
use GuzzleHttp\Client as GuzzleHttpClient;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\RequestOptions;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Handler\Slack\SlackRecord;
use Swoole\Coroutine;
use function Swoole\Coroutine;
/**
* Sends notifications through Slack Webhooks
*
* @author Haralan Dobrev <hkdobrev@gmail.com>
* @see https://api.slack.com/incoming-webhooks
*/
class WorkWeixinWebhookHandler extends AbstractProcessingHandler
{
/**
* Slack Webhook token
* @var string
*/
protected $webhookUrl;
/**
* Instance of the SlackRecord util class preparing data for Slack API.
* @var SlackRecord
*/
protected $token='';
protected $lock_time = 0;
public function __construct($config) {
if (!extension_loaded('curl')) {
throw new MissingExtensionException('The curl extension is needed to use the WorkWeixinWebhookHandler');
}
$bubble = $config['bubble'] ?? true;
parent::__construct($config['level'], $bubble);
$this->webhookUrl = $config['host'].$config['url'];
$this->token = $config['token'];
$this->lock_time = Arr::get($config,'lock_time',0)?:0;
}
public function getWebhookUrl(): string
{
return $this->webhookUrl;
}
/**
* {@inheritDoc}
*/
protected function write(array $record): void
{
if($this->lock_time){
$key = 'logs_work_weixin_lock:'.md5(Arr::get($record,'message',''));
if(Cache::get($key)){
return;
}
Cache::put($key,1,now()->addSecond($this->lock_time));
}
$postData = [
'msgtype'=>'markdown',
'markdown'=>[
'content'=>$this->getFormatter()->format($record)
]
];
$sendCallBack = $this->sendCallBack();
if(Coroutine::getcid()>0){
Coroutine\go($sendCallBack,$postData);
}else{
register_shutdown_function($sendCallBack,$postData);
}
}
/**
* 发送消息回调函数
* @return \Closure
*/
protected function sendCallBack(){
return function ($postData){
try {
$client = new GuzzleHttpClient([
'timeout' => 5,
]);
$response = $client->request('POST', $this->webhookUrl, [
RequestOptions::HEADERS=> [
'Content-Type' => 'application/json',
],
RequestOptions::BODY => json_encode($postData),
RequestOptions::QUERY=>[
'key'=>$this->token
]
]);
$body = $response->getBody()->getContents();
$data = json_decode($body, true);
if (Arr::get($data, 'errcode') !== 0) {
$error = [
'action' => __METHOD__,
'post_data' => $postData,
'response' => $data?:$body
];
Log::channel('daily')->error('WorkWeixin sendMsg Exception',$error);
}
} catch (GuzzleException $e) {
$error = [
'action' => __METHOD__,
$e->getCode() => $e->getMessage(),
'post_data' => $postData
];
Log::channel('daily')->error('WorkWeixin sendMsg Exception',$error);
}
};
}
}
无评论