我们在写代码时,都想自己的代码尽可能的不影响现有的代码。

或者说,最大化不改动任何代码的情况下,如何嵌入我们的新功能?这是我们常说的「非侵入式」的开发方式。

使用「非侵入式」的开发模式,主要在提供第三方插件和功能中最为常见。今天借助「Rollbar」第三方工具来说说如何做到「非侵入式」开发。

本文主要能学到:

  1. Laravel Event / Listener 原理;
  2. Rollbar for Laravel 的使用
  3. 创建一个 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);}}}...}
复制代码

主要作用是绑定 EventsListeners,当 Events触发时,直接执行 Listeners

我们希望 log 除了在本地文件存储输出外,也想把 log 信息实时发到其他平台和渠道上,这时候我们就需要借助 LogServiceProviderevents / 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);}
复制代码

有了 ServiceProviderlisten 就可以做到「非入侵」开发了。

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 信息发到其他平台上相关推荐

  1. 简单两步解决Microsfot Edge浏览器打开PDF卡住的问题 - 【大鼓的电脑百科】

    Hi,我是大鼓,欢迎来到[大鼓的电脑百科],这是我们的第一篇文章,希望这篇文章可以帮到你! 前言 在日常使用中,有时候会遇到使用Microsoft Edge打开PDF时,Microsoft Edge会 ...

  2. 简单两步彻底根除系统多余输入法

    简单两步彻底根除系统多余输入法 大家想必都曾有过这样的遭遇:在Windows系统中不停地按下"Ctrl+Shift"组合键,以切换到你喜欢的输入法.每次都是这么切换来切换去的,是不 ...

  3. 机关办事必备!简单两步掌握一键卡复印技巧

    相信许多朋友都有过在机关单位办事的经历,在大多数机关单位,甚至是银行等场所,时常会有证件复印的需求.使用传统的复印设备进行证件/卡复印,操作过程往往相对繁琐,需要多次扫描以及多次手动送纸,在一些人流量 ...

  4. 简单两步,去除网站首页后缀index.html

    你们的网站还带着一个小尾巴,好low啊.太没技术含量了吧.这个公司正规吗? 公司网站的小尾巴,真的是又难看,又没技术含量.今天简单两步轻松去除. 一.修改默认主页设置 这这里我使用的是虚拟主机.登录阿 ...

  5. 简单两步自己动手制作联想windows7 sp1 64位旗舰版安装镜像文件windows7旗舰版主...

    本帖最后由cjg1823于2011-8-1722:39编辑 写在开篇的话:本文所述方法,仅供学习研究,请勿用于商业用途,否则后果自负!笔者本人不会向任何人提供成品. windows7旗舰版主题本文是参 ...

  6. 利用Python简单两步监控电脑

    文章目录 一.利用python定时截取电脑桌面,保存到指定目录 二.将监控程序设置为开机自动启动 1.新建start.bat文件,打开输入以下代码: 2.将start.bat添加到任务计划 三.附注过 ...

  7. 绿色软件下么——简单两步实现win 7上网自动拨号

    一般我们在家里上网的话用的都是宽带拨号上网,但是每次要上网就要手动拨号一次很麻烦,可不可以用自动拨号的形式在实现上网呢,当然可以了,其实步骤还是很简单的,只要两个步骤就能实现了. 第一步:建立拨号连接 ...

  8. 简单两步解决nginx+php 的高并发502问题(nginx限流,定时重启php-fpm)

    两个步骤解决502问题. 第一步:nginx设置 在nginx的http中添加 limit_conn_zone $binary_remote_addr zone=one:10m; limit_req_ ...

  9. 简单两步使用node发送qq邮件

    node发送邮件非常简单,这里只做qq的演示,你可以举一反三. 使用nodemailer包 let transporter = nodemailer.createTransport({// 使用qq发 ...

最新文章

  1. 首页被锁定7939的解决办法
  2. 【Python面试】 说说Python模块主要分哪三类?
  3. 1031. 查验身份证(15)
  4. Json-转自菜鸟教程
  5. (转)python调取C/C++的dll生成方法
  6. SQLite的基本用法
  7. 计蒜客挑战难题:简单斐波那契
  8. 【kafka】kafka 新增节点 报错 InconsistentBrokerIdException Configured broker.id doesn‘t match
  9. java 传递intent_intent传递参数
  10. 基于词典和弱标注信息的电影评论情感分析系统
  11. correlation 蒙特卡洛_蒙特卡洛模拟法
  12. php简单的日历代码,php日历代码(附演示效果)
  13. 如何在PPT中插入HTML页面|如何使用控件将Pyecharts图表插入PPT|ActiveX
  14. 两套php代码使用同一个数据库,php开发公用同一个数据库
  15. 学会查看tomcat的日志文件
  16. 服务器防火墙关闭开机自启解决方案
  17. 书小宅之网页设计——二次贝塞尔曲线和三次贝塞尔曲线
  18. codeforces The Artful Expedient(数学思维题)
  19. SpringBoot:异步 定时 邮件任务
  20. Android scrollTo() scrollBy() Scroller讲解及应用

热门文章

  1. 【Java学习笔记之四】java进制转化
  2. 浪潮英特尔在德国发布KEEP升级计划 用户可提前体验英特尔KNM
  3. 《iOS 6核心开发手册(第4版)》——2.1节UIControl类
  4. 《HTML5游戏编程核心技术与实战》一2.6 其他全局属性
  5. 带有支付功能的产品如何进行测试
  6. Lock free queue 大比拼
  7. 如何给textbox中的文本设置垂直对齐,以及右对齐
  8. c语言tcp硬时事通讯程序代码,使用C语言编写基于TCP协议的Socket通讯程序实例分享...
  9. php进销存 手机版_酒水批发用传统本地化部署进销存与云进销存手机版的区别!...
  10. Oracle数据库设计规范