hyperf中使用sentry

更新于 2019-11-12  约 7 分钟

sentry文档:

https://docs.sentry.io/

在熟悉了hyperf架构异常捕捉机制之后,觉得sentry可以以非常合理的方式使用在异常捕捉机制中。

首先安装sentry:

composer require sentry/sdk:2.0.3

安装完后在hyperf.php文件中添加

Sentry\init(\['dsn' => 'your dsn']);

在App\Exception\Handle\AppExceptionHandle中

use Sentry;
public function handle(Throwable $throwable, ResponseInterface $response)
{
    Sentry\captureException($throwable);
    return $response->withStatus(500)->withBody(new SwooleStream('Internal Server Error.'));
}

这时候捕捉异常发现,sentry服务端并没有接收到异常。查看源码发现,sentry的发送机制是在于一次请求结束后才清空信息队列,而hyperf是基于swoole的架构, 一次请求之后,并不会释放进程,所以sentry服务端无法接收到数据,Sentry\Transport\HttpTransport构造方法中的第三个参数解释为:

This flag controls whether to delay sending of the events until the shutdown of the application

默认为true,推送异常后sentry 并不会第一时间就把异常推送到服务端去而将会等到进程结束后才发送异常。 而init方法在sentry\ClientBuilder->createTransportInstance方法中创建的HttpTransport的第三个参数为true,修改为false再次捕捉异常便可以接收到请求了。

但是这时随之而来又一个问题,sentry的推送机制使用的是curl库,而swoole并不支持curl协程化,也就意味着,如果在生产环境中,同时出现大量的异常,会导致进程阻塞,hyperf的性能将严重下降。

异步处理刻不容缓!

再查看了文档之后,curl推送是通过Transport类来处理的,而默认的Transport是HttpTransport也就是同步推送机制,同时也提供了异步Transport, spoolTransport。

删除hyperf.php的Sentryinit方法
在App\Exception\Handle\AppExceptionHandle中修改为如下

use Sentry\ClientBuilder;
use Sentry\Transport\SpoolTransport;
use Sentry\Transport\HttpTransport;
use Sentry\State\Hub;
use Sentry\Spool\MemorySpool;
use Http\Discovery\HttpAsyncClientDiscovery;
use Http\Discovery\MessageFactoryDiscovery;
use Sentry\Options;

public function handle(Throwable $throwable, ResponseInterface $response)
{
    // 这里为了方便在每次捕捉异常的时候都会重新初始化一次相关对象,应该全局单例保存起来
    $options = ['dsn' => 'https://<key>@sentry.io/<project>'\];
    $optionObj = new Options($options);
    $spool = new MemorySpool();
    $transport = new SpoolTransport($spool);
    $httpTransport = new HttpTransport($optionObj, HttpAsyncClientDiscovery::find(), MessageFactoryDiscovery::find());
    $builder = ClientBuilder::create($options);
    $builder->setTransport($transport);
    Hub::getCurrent()->bindClient($builder->getClient());
    Hub::getCurrent()->captureException($throwable);
    // 调用这个方法就会开始清空之前捕捉异常的队列,思考之后觉的放在定时任务里定时清空队列比较合理。
    $spool->flushQueue($httpTransport);
}

开始捕捉异常,怎么sentry服务端没有接收到请求,留下了没有技术的泪水。

排查问题后发现是因为HttpAsyncClientDiscovery::find()创建的异步httpTransport的url配置问题,仔细看了一遍源码后,发现可以使用ClientBuilder->createTransportInstance方法,该方法默认是private,将其修改为public便可以使用它创建transport对象(ps 如果可以的话可以自己模仿写一个而不修改源码)。修改后的代码如下所示

App\Exception\Handle\AppExceptionHandle如下所示


use Sentry\ClientBuilder;
use Sentry\Transport\SpoolTransport;
use Sentry\State\Hub;
use Sentry\Spool\MemorySpool;

public function handle(Throwable $throwable, ResponseInterface $response)
{
    // 这里为了方便在每次捕捉异常的时候都会重新初始化一次相关对象,应该全局单例保存起来
    $options = ['dsn' => 'https://<key>@sentry.io/<project>'\];
    $spool = new MemorySpool();
    $transport = new SpoolTransport($spool);
    $builder = ClientBuilder::create($options);
    $httpTransport = $builder->createTransportInstance();
    $builder->setTransport($transport);
    Hub::getCurrent()->bindClient($builder->getClient());
    Hub::getCurrent()->captureException($throwable);
    // 调用这个方法就会开始清空之前捕捉异常的队列,思考之后觉的放在定时任务里定时清空队列比较合理。
    $spool->flushQueue($httpTransport);
}

捕捉异常,sentry服务端接收到异常。 在这里$spool->flushQueue($httpTransport); 将会主动推送捕捉的队列异常,思考之后觉的放在定时任务里定时清空队列比较合理。

阅读 889更新于 2019-11-12

推荐阅读
目录