简单两步就能将 Laravel Log 信息发到其他平台上
我们在写代码时,都想自己的代码尽可能的不影响现有的代码。
或者说,最大化不改动任何代码的情况下,如何嵌入我们的新功能?这是我们常说的「非侵入式」的开发方式。
使用「非侵入式」的开发模式,主要在提供第三方插件和功能中最为常见。今天借助「Rollbar」第三方工具来说说如何做到「非侵入式」开发。
本文主要能学到:
- Laravel Event / Listener 原理;
- Rollbar for Laravel 的使用
- 创建一个 Log to Dingding 群的功能
Laravel Event / Listener 原理
在 Laravel,主要利用 EventServiceProvider
来加载 Events / Listeners
:
<?phpnamespace Illuminate\Events;use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Queue\Factory as QueueFactoryContract;class EventServiceProvider extends ServiceProvider
{/*** Register the service provider.** @return void*/public function register(){$this->app->singleton('events', function ($app) {return (new Dispatcher($app))->setQueueResolver(function () use ($app) {return $app->make(QueueFactoryContract::class);});});}
}
复制代码
EventServiceProvider
返回的是 Dispatcher
对象。我们看看 Dispatcher
类:
<?phpnamespace Illuminate\Events;use Exception;
use ReflectionClass;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Container\Container;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
use Illuminate\Contracts\Broadcasting\Factory as BroadcastFactory;
use Illuminate\Contracts\Container\Container as ContainerContract;class Dispatcher implements DispatcherContract
{/*** The IoC container instance.** @var \Illuminate\Contracts\Container\Container*/protected $container;/*** The registered event listeners.** @var array*/protected $listeners = [];/*** The wildcard listeners.** @var array*/protected $wildcards = [];/*** The queue resolver instance.** @var callable*/protected $queueResolver;/*** Create a new event dispatcher instance.** @param \Illuminate\Contracts\Container\Container|null $container* @return void*/public function __construct(ContainerContract $container = null){$this->container = $container ?: new Container;}/*** Register an event listener with the dispatcher.** @param string|array $events* @param mixed $listener* @return void*/public function listen($events, $listener){foreach ((array) $events as $event) {if (Str::contains($event, '*')) {$this->setupWildcardListen($event, $listener);} else {$this->listeners[$event][] = $this->makeListener($listener);}}}...}
复制代码
主要作用是绑定 Events
和 Listeners
,当 Events
触发时,直接执行 Listeners
。
我们希望 log 除了在本地文件存储输出外,也想把 log 信息实时发到其他平台和渠道上,这时候我们就需要借助 LogServiceProvider
的 events / listeners
绑定实现了。现在来看看 LogServiceProvider
:
<?phpnamespace Illuminate\Log;use Monolog\Logger as Monolog;
use Illuminate\Support\ServiceProvider;class LogServiceProvider extends ServiceProvider
{/*** Register the service provider.** @return void*/public function register(){$this->app->singleton('log', function () {return $this->createLogger();});}/*** Create the logger.** @return \Illuminate\Log\Writer*/public function createLogger(){$log = new Writer(new Monolog($this->channel()), $this->app['events']);if ($this->app->hasMonologConfigurator()) {call_user_func($this->app->getMonologConfigurator(), $log->getMonolog());} else {$this->configureHandler($log);}return $log;}...
}
复制代码
这里将 $this->app['events']
也就是 Dispatcher
传入,用户事件的注册:
/*** Register a new callback handler for when a log event is triggered.** @param \Closure $callback* @return void** @throws \RuntimeException*/public function listen(Closure $callback){if (! isset($this->dispatcher)) {throw new RuntimeException('Events dispatcher has not been set.');}$this->dispatcher->listen(MessageLogged::class, $callback);}
复制代码
有了 ServiceProvider
和 listen
就可以做到「非入侵」开发了。
Rollbar
Rollbar error monitoring integration for Laravel projects. This library adds a listener to Laravel's logging component. Laravel's session information will be sent in to Rollbar, as well as some other helpful information such as 'environment', 'server', and 'session'.
参考:docs.rollbar.com/docs/larave…
简单使用
使用该工具,只要在其官网注册账号,并产生一个 access token
即可
安装该工具,也只需要简单的两步:
composer require rollbar/rollbar-laravel// .env
ROLLBAR_TOKEN=[your Rollbar project access token]// 如果 < Laravel 5.5,则需要在 app.php 中添加
Rollbar\Laravel\RollbarServiceProvider::class,
复制代码
测试,只要有 Log 输出,rollbar 后台都可以收到信息,方便查看,而再也不需要去看 log 文件了。
剖析实现原理
我们来看看 rollbar 是不是我们所设想的那样实现的?
我们先看看 RollbarServiceProvider
<?php namespace Rollbar\Laravel;use Illuminate\Support\ServiceProvider;
use InvalidArgumentException;
use Rollbar\Rollbar;
use Rollbar\Laravel\RollbarLogHandler;class RollbarServiceProvider extends ServiceProvider
{/*** Indicates if loading of the provider is deferred.** @var bool*/protected $defer = false;/*** Bootstrap the application events.*/public function boot(){// Don't boot rollbar if it is not configured.if ($this->stop() === true) {return;}$app = $this->app;// Listen to log messages.$app['log']->listen(function () use ($app) {$args = func_get_args();// Laravel 5.4 returns a MessageLogged instance onlyif (count($args) == 1) {$level = $args[0]->level;$message = $args[0]->message;$context = $args[0]->context;} else {$level = $args[0];$message = $args[1];$context = $args[2];}$app['Rollbar\Laravel\RollbarLogHandler']->log($level, $message, $context);});}/*** Register the service provider.*/public function register(){// Don't register rollbar if it is not configured.if ($this->stop() === true) {return;}$app = $this->app;$this->app->singleton('Rollbar\RollbarLogger', function ($app) {$defaults = ['environment' => $app->environment(),'root' => base_path(),'handle_exception' => true,'handle_error' => true,'handle_fatal' => true,];$config = array_merge($defaults, $app['config']->get('services.rollbar', []));$config['access_token'] = getenv('ROLLBAR_TOKEN') ?: $app['config']->get('services.rollbar.access_token');if (empty($config['access_token'])) {throw new InvalidArgumentException('Rollbar access token not configured');}$handleException = (bool) array_pull($config, 'handle_exception');$handleError = (bool) array_pull($config, 'handle_error');$handleFatal = (bool) array_pull($config, 'handle_fatal');Rollbar::init($config, $handleException, $handleError, $handleFatal);return Rollbar::logger();});$this->app->singleton('Rollbar\Laravel\RollbarLogHandler', function ($app) {$level = getenv('ROLLBAR_LEVEL') ?: $app['config']->get('services.rollbar.level', 'debug');return new RollbarLogHandler($app['Rollbar\RollbarLogger'], $app, $level);});}/*** Check if we should prevent the service from registering** @return boolean*/public function stop(){$level = getenv('ROLLBAR_LEVEL') ?: $this->app->config->get('services.rollbar.level', null);$token = getenv('ROLLBAR_TOKEN') ?: $this->app->config->get('services.rollbar.access_token', null);$hasToken = empty($token) === false;return $hasToken === false || $level === 'none';}
}
复制代码
这个比较好理解,先利用 register
注册两个 singleton
,然后在 boot
方法中,注册 listener
$app['log']->listen(function () use ($app){});
复制代码
其中 $app['log']
,就是我们的上文说的 LogServiceProvider
,将 listener
注册到 EventServiceProvider
中。
$this->dispatcher->listen(MessageLogged::class, $callback);
复制代码
最后我们看看 Rollbar
facades 返回的是:RollbarLogHandler
对象
<?php namespace Rollbar\Laravel\Facades;use Illuminate\Support\Facades\Facade;class Rollbar extends Facade
{/*** Get a schema builder instance for the default connection.** @return \Rollbar\Laravel\RollbarLogHandler*/protected static function getFacadeAccessor(){return 'Rollbar\Laravel\RollbarLogHandler';}
}复制代码
看看 RollbarLogHandler
实现,也主要是将 log 信息反馈到Rollbar 中,此处不做分析了。
模拟实现
通过对 Rollbar
简单的分析,就会发现原来通过简单 Listener
,不用改现在的任何功能和代码,就能实现将 log 实时发到你想接收的地方。
所以我们可以尝试也写一个这样的功能,将 log 信息发到钉钉上。
好了,我们开始写 Log2Dingding
插件。
根据之前的文章我们可以很方便的组织好插件结构:
composer.json
设置:
{"name": "fanly/log2dingding","description": "Laravel Log to DingDing","license": "MIT","authors": [{"name": "fanly","email": "yemeishu@126.com"}],"require": {},"extra": {"laravel": {"providers": ["Fanly\\Log2dingding\\FanlyLog2dingdingServiceProvider"]}},"autoload": {"psr-4": {"Fanly\\Log2dingding\\": "src/"}}
}复制代码
我们定义 ServiceProvider
:
<?php
/*** User: yemeishu* Date: 2018/5/13* Time: 下午2:56*/
namespace Fanly\Log2dingding;use Fanly\Log2dingding\Dingtalk\Messager;
use Illuminate\Support\ServiceProvider;
use Fanly\Log2dingding\Support\Client;class FanlyLog2dingdingServiceProvider extends ServiceProvider {protected function registerFacade(){// Don't register rollbar if it is not configured.if ($this->stop() === true) {return;}$this->app->singleton('fanlylog2dd', function ($app) {$config['access_token'] = getenv('FANLYLOG_TOKEN') ?: $app['config']->get('services.fanly.log2dd.access_token');if (empty($config['access_token'])) {throw new InvalidArgumentException('log2dd access token not configured');}return (new Messager(new Client()))->accessToken($config['access_token']);});}/*** Bootstrap the application services.*/public function boot(){// Don't boot rollbar if it is not configured.if ($this->stop() === true) {return;}$app = $this->app;// Listen to log messages.$app['log']->listen(function () use ($app) {$args = func_get_args();// Laravel 5.4 returns a MessageLogged instance onlyif (count($args) == 1) {$level = $args[0]->level;$message = $args[0]->message;$context = $args[0]->context;} else {$level = $args[0];$message = $args[1];$context = $args[2];}$app['fanlylog2dd']->message("[ $level ] $message\n".implode($context))->send();});}/*** Register the application services.*/public function register(){$this->registerFacade();}private function stop(){$level = getenv('FANLYLOG_LEVEL') ?: $this->app->config->get('services.rollbar.level', null);$token = getenv('FANLYLOG_TOKEN') ?: $this->app->config->get('services.rollbar.access_token', null);$hasToken = empty($token) === false;return $hasToken === false || $level === 'none';}
}
复制代码
我们主要是创建一个发钉钉消息的单例,然后再注册 listener
,只要获取 log 信息,就发送信息到钉钉上。
测试一下:
总结
最后做成插件,和 Rollbar
一样,引入:
composer require "fanly/log2dingding"// .env
FANLYLOG_TOKEN=56331868f7056a3e645e7dba034c5550e7af***
复制代码
同样的,其他信息都不需要设置,跑一个测试:
Laravel 框架的一大好处在于,可以以友好的方式实现我们「非入侵」开发,只要借助「ServiceProvider
」和「Events/Listner
」,就可以扩展我们的功能。
参考
- 「12步」制作 Laravel 插件 (一)mp.weixin.qq.com/s/AD05BiKjP…
- 「3步」发布 Laravel 插件 (二)mp.weixin.qq.com/s/RSYeHU7aR…
- fanly/log2dingding packagist.org/packages/fa…
简单两步就能将 Laravel Log 信息发到其他平台上相关推荐
- 简单两步解决Microsfot Edge浏览器打开PDF卡住的问题 - 【大鼓的电脑百科】
Hi,我是大鼓,欢迎来到[大鼓的电脑百科],这是我们的第一篇文章,希望这篇文章可以帮到你! 前言 在日常使用中,有时候会遇到使用Microsoft Edge打开PDF时,Microsoft Edge会 ...
- 简单两步彻底根除系统多余输入法
简单两步彻底根除系统多余输入法 大家想必都曾有过这样的遭遇:在Windows系统中不停地按下"Ctrl+Shift"组合键,以切换到你喜欢的输入法.每次都是这么切换来切换去的,是不 ...
- 机关办事必备!简单两步掌握一键卡复印技巧
相信许多朋友都有过在机关单位办事的经历,在大多数机关单位,甚至是银行等场所,时常会有证件复印的需求.使用传统的复印设备进行证件/卡复印,操作过程往往相对繁琐,需要多次扫描以及多次手动送纸,在一些人流量 ...
- 简单两步,去除网站首页后缀index.html
你们的网站还带着一个小尾巴,好low啊.太没技术含量了吧.这个公司正规吗? 公司网站的小尾巴,真的是又难看,又没技术含量.今天简单两步轻松去除. 一.修改默认主页设置 这这里我使用的是虚拟主机.登录阿 ...
- 简单两步自己动手制作联想windows7 sp1 64位旗舰版安装镜像文件windows7旗舰版主...
本帖最后由cjg1823于2011-8-1722:39编辑 写在开篇的话:本文所述方法,仅供学习研究,请勿用于商业用途,否则后果自负!笔者本人不会向任何人提供成品. windows7旗舰版主题本文是参 ...
- 利用Python简单两步监控电脑
文章目录 一.利用python定时截取电脑桌面,保存到指定目录 二.将监控程序设置为开机自动启动 1.新建start.bat文件,打开输入以下代码: 2.将start.bat添加到任务计划 三.附注过 ...
- 绿色软件下么——简单两步实现win 7上网自动拨号
一般我们在家里上网的话用的都是宽带拨号上网,但是每次要上网就要手动拨号一次很麻烦,可不可以用自动拨号的形式在实现上网呢,当然可以了,其实步骤还是很简单的,只要两个步骤就能实现了. 第一步:建立拨号连接 ...
- 简单两步解决nginx+php 的高并发502问题(nginx限流,定时重启php-fpm)
两个步骤解决502问题. 第一步:nginx设置 在nginx的http中添加 limit_conn_zone $binary_remote_addr zone=one:10m; limit_req_ ...
- 简单两步使用node发送qq邮件
node发送邮件非常简单,这里只做qq的演示,你可以举一反三. 使用nodemailer包 let transporter = nodemailer.createTransport({// 使用qq发 ...
最新文章
- 首页被锁定7939的解决办法
- 【Python面试】 说说Python模块主要分哪三类?
- 1031. 查验身份证(15)
- Json-转自菜鸟教程
- (转)python调取C/C++的dll生成方法
- SQLite的基本用法
- 计蒜客挑战难题:简单斐波那契
- 【kafka】kafka 新增节点 报错 InconsistentBrokerIdException Configured broker.id doesn‘t match
- java 传递intent_intent传递参数
- 基于词典和弱标注信息的电影评论情感分析系统
- correlation 蒙特卡洛_蒙特卡洛模拟法
- php简单的日历代码,php日历代码(附演示效果)
- 如何在PPT中插入HTML页面|如何使用控件将Pyecharts图表插入PPT|ActiveX
- 两套php代码使用同一个数据库,php开发公用同一个数据库
- 学会查看tomcat的日志文件
- 服务器防火墙关闭开机自启解决方案
- 书小宅之网页设计——二次贝塞尔曲线和三次贝塞尔曲线
- codeforces The Artful Expedient(数学思维题)
- SpringBoot:异步 定时 邮件任务
- Android scrollTo() scrollBy() Scroller讲解及应用
热门文章
- 【Java学习笔记之四】java进制转化
- 浪潮英特尔在德国发布KEEP升级计划 用户可提前体验英特尔KNM
- 《iOS 6核心开发手册(第4版)》——2.1节UIControl类
- 《HTML5游戏编程核心技术与实战》一2.6 其他全局属性
- 带有支付功能的产品如何进行测试
- Lock free queue 大比拼
- 如何给textbox中的文本设置垂直对齐,以及右对齐
- c语言tcp硬时事通讯程序代码,使用C语言编写基于TCP协议的Socket通讯程序实例分享...
- php进销存 手机版_酒水批发用传统本地化部署进销存与云进销存手机版的区别!...
- Oracle数据库设计规范