Symfony,Monolog,以及网站和定时任务错误邮件提醒

Symfony,Monolog,以及网站和定时任务错误邮件提醒

Chris Yue 2 comments
Posts

网站报错无法访问,结果被老板发现,怒气冲冲来电让你去修复问题,并一直追问:什么时候能弄好?!想必很多程序猿都有过这样的经历。作为一只高端猿,怎能如此被动,把发现网站出错的机会让给老板?

聪明的程序猿一定能想到:当网站出错的时候给自己发个邮件就好了嘛。没错,而且在基于 Symfony 框架的应用发邮件非常方便,所有要检查错误的地方都可以做同样的处理:

    // 假设在某个控制器的 action 里
    try {
        ...
    } catch (\Exception $ex) {
        $message = \Swift_Message::newInstance()
            ->setSubject('发生了一些错误')
            ->setFrom('system@example.com')
            ->setTo('recipient@example.com')
            ->setBody(
                $this->renderView(
                    'error/message.html.twig',
                    compact('ex')
                ),
                'text/html'
            );

        $this->get('mailer')->send($message);
    }

但是这么做有一个显而易见的问题,那就是你要不厌其烦的在所有你认为会出现错误的地方都要写上这样的代码。除了繁琐,一旦有些地方你忘记了做错误处理,那么也有网站出错却收不到报错邮件的风险。

不过如果是基于 Symfony 框架,因为异常发生时会抛出事件(网络访问是 kernel.exception,命令是 console.exception),只要我们监听事件做相应的处理就行了。以 console.exception 为例,我们可以监听命令异常事件,来统一处理命令的异常:

# app/config/services.yml
services:
    app.listener.console_exception:
        class: AppBundle\EventListener\ConsoleExceptionListener
        arguments: ['@mailer', '@templating']
        tags:
            - { name: kernel.event_listener, event: console.exception }
<?php

namespace AppBundle\EventListener;

use Symfony\Component\Console\Event\ConsoleExceptionEvent;
use Symfony\Component\Templating\EngineInterface;

class ConsoleExceptionListener
{
    private $mailer;
    private $templating;

    public function __construct(\Swift_Mailer $mailer, EngineInterface $templating)
    {
        $this->mailer = $mailer;
        $this->templating = $templating;
    }

    public function onConsoleException(ConsoleExceptionEvent $event)
    {
        $command = $event->getCommand();
        $exception = $event->getException();

        $message = \Swift_Message::newInstance()
            ->setSubject('发生了一些错误')
            ->setFrom('system@example.com')
            ->setTo('recipient@example.com')
            ->setBody(
                $this->templating->render(
                    'error/message.html.twig',
                    compact('ex')
                ),
                'text/html'
            );

        $this->get('mailer')->send($message);
    }
}

如果你的站点每天都会有一些定时任务要处理,而这些定时任务一旦报出异常,你都会收报错邮件了。

如果你是一个比较懒,就爱榨干框架默认功能的人,Symfony 也能满足你的需求。实际上,Symfony 框架里自带的 Monolog Bundle,虽然是一个用来记录日志的工具,但 Monolog 是支持使用邮件来记录日志的,你所需要做的,就只是改改配置文件而已:

# config_{prod|dev}.yml
monolog:
    handlers:
        swift:
            type:       swift_mailer
            from_email: '%mailer_user%'
            to_email:   'i@chrisyue.com'
            subject:    '测试日志'
            level:      debug # 在开发环境可以改成 error 或者 critical

修改完配置,你会发现每次访问页面(开发环境),都会有邮件发送到你指定的邮箱。

小提示:level 为日志信息等级过滤选项,信息等级从低到高(严重)分为:debug, info, notice, warning, error, critical, alert, emergency

在开发环境将日志发送到邮箱,只是举一个例子,实际上不会有人愿意把开发日志发到邮箱里。但对于生产环境,在发生错误的情况下发邮件就相当有用了。上面的生产环境的配置已经可以起到一个提醒作用,只要出现 error 或者 critical 以上的日志,就会发送邮件。但不足的是,它只会发送报错的信息,但有很多有帮助的信息,比如访问的哪个项目,访问的哪个页面(或路由),路由参数是什么等上下文,因为其本身可能只是 info 级别的消息,并不会出现在邮件里,从而就算知道错误发生了,debug 起来也因缺乏线索而特别困难。

然而 Monolog 已经帮我解决了这个问题,在 Monolog 里面有一个 fingers_crossed 的 handler。fingers crossed 是一种手势,见 Joey 示范:

fingers crossed

因为 fingers crossed 手势外观给人感觉像是“过滤”(实际上原意应该是“袖珍十字架”,向上帝求好运),所以这个 handler 的作用也就是起一个过滤的作用。比如,当一个请求或者一个命令执行时,只有在产生某种等级的日志时,才决定记日志。注意他跟之前 swift 的 level 的区别:swift.level 只是定义了日志信息只保留某个等级及其以上等级的信息,而完全不记录此等级以下的信息;而 fingers_crossed 的 action_level 是指,某次请求或者命令执行,一旦出现 action_level 指定的等级及其以上等级,这次请求所有的信息都将被记日志,这就很好的解决了之前所说的那个问题:

monolog:
    handlers:
        main:
            type:         fingers_crossed
            action_level: critical
            handler:      swift
        swift:
            type:       swift_mailer
            from_email: '%mailer_user%'
            to_email:   'i@chrisyue.com'
            subject:    '网站发生了一些错误'
            level:      debug # 注意这里一定要设置比较低的信息等级比如 debug 或者 info,否则 swift 还是会将内容再做一次过滤

目前对于生产机的定时任务来说,目前的错误监控方案的可用度算是比较高了,然而对于网站来说,如果你的网站访问量比较高,一秒钟可能都有成百甚至上千个点击,一旦出现错误,你的邮箱估计会瞬间被巨量的错误日志邮件所淹没……

这个问题,可以利用 Monolog 的 deduplication handler 来解决:

monolog:
    handlers:
        main:
            ...
            handler:      deduplicated
        deduplicated:
            type:    deduplication
            handler: swift
        swift:
            ...

经过这样处理之后,如果在短期时间内有大量重复内容邮件发送,一分钟也只发一封

最后,之前我们自己使用 mailer 服务发送邮件的时候,可以自己写一个比较美观的邮件内容模板,而 Monolog 的 swift handler 目前只发送文本格式的邮件,比较丑陋。似乎是 2.7 版本之后,Symfony 提供了默认的基于 text/html 格式的模板,看起来比纯文本格式的邮件内容还是好多了:

monolog:
    handlers:
        ...
        swift:
            ...
            formatter:  monolog.formatter.html
            content_type: text/html
错误信息邮件

如果没有什么特殊需求,在不写一行代码的情况下,错误邮件警告的方案通过 Monolog 的配置文件就已经能做得很好了,赞一个!

Symfony,Monolog,以及网站和定时任务错误邮件提醒 by Chris Yue is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

微信赞赏码

写作累,服务器还越来越贵
求分担,祝愿好人一生平安
天使打赏人

2 Comments

fab

六月 4, 2018 在 3:44 下午

楼主说的最后几个配置项是在哪里添加的啊?能不能以一个具体的项目举个例子,比如从 composer 安装 最新 monolog扩展开始详细的介绍下步骤。

    Chris Yue

    六月 5, 2018 在 8:31 上午

    文章中有写啊,config_dev.yml 或者 config_prod.ymlapp/config 目录下),而且 monolog Symfony2/3 默认就有啊不用安装

    不过你要是用的 Symfony4 的话,就不是这个文件了,但配置项应该都是一样的。Symfony4 下我也没用过,不敢乱说

     

发表评论

9 + 1 =