An open API service indexing awesome lists of open source software.

https://github.com/fanly/log2dingding


https://github.com/fanly/log2dingding

dingding laravel log

Last synced: 6 months ago
JSON representation

Awesome Lists containing this project

README

          

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

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

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

本文主要能学到:

> 1. Laravel Event / Listener 原理;
> 2. Rollbar for Laravel 的使用
> 3. 创建一个 Log to Dingding 群的功能

## Laravel Event / Listener 原理

在 Laravel,主要利用 `EventServiceProvider` 来加载 `Events / Listeners`:

```php
app->singleton('events', function ($app) {
return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
return $app->make(QueueFactoryContract::class);
});
});
}
}
```

`EventServiceProvider` 返回的是 `Dispatcher` 对象。我们看看 `Dispatcher` 类:

```php
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`:

```php
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` 传入,用户事件的注册:

```php
/**
* 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'.
>
> 参考:[https://docs.rollbar.com/docs/laravel](https://docs.rollbar.com/docs/laravel)

### 简单使用

使用该工具,只要在其官网注册账号,并产生一个 `access token` 即可

安装该工具,也只需要简单的两步:

```bash
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 文件了。

![](http://ow20g4tgj.bkt.clouddn.com/2018-05-13-15261913480148.jpg)

### 剖析实现原理

我们来看看 rollbar 是不是我们所设想的那样实现的?

![](http://ow20g4tgj.bkt.clouddn.com/2018-05-13-15261914714008.jpg)

我们先看看 `RollbarServiceProvider`

```php
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 only
if (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`

```php
$app['log']->listen(function () use ($app){});
```

其中 `$app['log']`,就是我们的上文说的 `LogServiceProvider`,将 `listener` 注册到 `EventServiceProvider` 中。

```php
$this->dispatcher->listen(MessageLogged::class, $callback);
```

最后我们看看 `Rollbar` facades 返回的是:`RollbarLogHandler` 对象

```php
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 only
if (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 信息,就发送信息到钉钉上。

测试一下:

![](http://ow20g4tgj.bkt.clouddn.com/2018-05-13-15261992511566.jpg)

## 总结

最后做成插件,和 `Rollbar` 一样,引入:

```bash
composer require "fanly/log2dingding"

// .env
FANLYLOG_TOKEN=56331868f7056a3e645e7dba034c5550e7af***
```

同样的,其他信息都不需要设置,跑一个测试:

![](http://ow20g4tgj.bkt.clouddn.com/2018-05-13-15262016735782.jpg)

Laravel 框架的一大好处在于,可以以友好的方式实现我们「非入侵」开发,只要借助「`ServiceProvider`」和「`Events/Listner`」,就可以扩展我们的功能。

*参考*

* 「12步」制作 Laravel 插件 (一)[https://mp.weixin.qq.com/s/AD05BiKjPsI2ehC-mhQJQw](https://mp.weixin.qq.com/s/AD05BiKjPsI2ehC-mhQJQw)
* 「3步」发布 Laravel 插件 (二)[https://mp.weixin.qq.com/s/RSYeHU7aR4gyJyLNwdjbJg](https://mp.weixin.qq.com/s/RSYeHU7aR4gyJyLNwdjbJg)
* fanly/log2dingding [https://packagist.org/packages/fanly/log2dingding](https://packagist.org/packages/fanly/log2dingding)