【问题标题】:How to do advanced filtering of Monolog messages in Symfony?如何在 Symfony 中对 Monolog 消息进行高级过滤?
【发布时间】:2017-03-11 00:24:43
【问题描述】:

我在我的Symfony 2.8 项目中使用MonologBundle 来管理日志消息。使用不同的Handlers 可以同时将日志写入文件并通过电子邮件发送。

我想减少通过邮件收到的消息数量。我已经使用DeduplicationHandlerFingersCrossed 处理程序按错误级别进行过滤并避免重复消息。这工作正常,但还不够。

例如我想减少关于PageNotFound 错误的邮件数量。当然,如果找不到/existingPage,我希望得到通知,但我对有关/.well-known/... 文件的消息不感兴趣。

另一个示例是有关第三方 CSV 解析器组件中的错误的消息。有几个已知且无害的错误我不感兴趣,但当然其他错误也很重要。

这些错误/消息是由第三方代码生成的,我无法影响来源。我只能完全忽略这些消息,但这不是我想要的。

我正在寻找一种按内容过滤邮件的解决方案。在 Monolog 中如何做到这一点?

我已经尝试使用HandlerWrapper 来解决这个问题,并在another question 中讨论了这个问题:想法是,HandlerWrapper 充当过滤器。 HandlerWrapper 由 Monolog 调用,它检查消息内容并决定是否应该处理它(例如丢弃所有消息,包括文本“./well-known/”)。如果消息通过,HandlerWrapper 应该简单地将其交给其嵌套/包装的处理程序。否则,该消息将被跳过而不进行进一步处理。

但是这个想法没有奏效,另一个问题的答案表明,HandlerWrapper 不是解决这个问题的正确方法。

所以新的/实际的问题是:如何为 Monolog 消息创建过滤器,让我控制是否应该处理特定消息?

【问题讨论】:

    标签: php symfony monolog


    【解决方案1】:

    我不确定为什么使用 HandlerWrapper 是错误的做法。

    我遇到了同样的问题,并想出了一种方法来包装处理程序以过滤某些记录。

    在这个答案中,我描述了解决这个问题的两种方法,一种更复杂,一种更简单。

    (或多或少)复杂的方式

    我做的第一件事是创建一个新类,它扩展了 HandlerWrapper 并添加了一些可以过滤记录的逻辑:

    use Monolog\Handler\HandlerWrapper;
    
    class CustomHandler extends HandlerWrapper
    {
        public function isHandling(array $record)
        {
            if ($this->shouldFilter($record)) {
                return false;
            }
    
            return $this->handler->isHandling($record);
        }
    
        public function handle(array $record)
        {
            if (!$this->isHandling($record)) {
                return false;
            }
    
            return $this->handler->handle($record);
        }
    
        public function handleBatch(array $records)
        {
            foreach ($records as $record) {
                $this->handle($record);
            }
        }
    
        private function shouldFilter(array $record)
        {
            return mt_rand(0, 1) === 1;; // add logic here
        }
    }
    

    然后我创建了一个服务定义和一个 CompilerPass,我可以在其中包装 GroupHandler

    services.yml

    CustomHandler:
        class: CustomHandler
        abstract: true
        arguments: ['']
    
    use Monolog\Handler\GroupHandler;
    use Symfony\Component\DependencyInjection\ChildDefinition;
    use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
    use Symfony\Component\DependencyInjection\ContainerBuilder;
    use Symfony\Component\DependencyInjection\Definition;
    use Symfony\Component\DependencyInjection\Reference;
    
    class CustomMonologHandlerPass implements CompilerPassInterface
    {
        public function process(ContainerBuilder $container)
        {
            if (!$container->hasDefinition(CustomHandler::class)) {
                return;
            }
    
            $definitions = $container->getDefinitions();
            foreach ($definitions as $serviceId => $definition) {
                if (!$this->isValidDefinition($definition)) {
                    continue;
                }
    
                $cacheId = $serviceId . '.wrapper';
    
                $container
                    ->setDefinition($cacheId, new ChildDefinition(CustomHandler::class))
                    ->replaceArgument(0, new Reference($cacheId . '.inner'))
                    ->setDecoratedService($serviceId);
            }
        }
    
        private function isValidDefinition(Definition $definition): bool
        {
            return GroupHandler::class === $definition->getClass();
        }
    }
    

    如您所见,我在这里查看了所有定义,并找到了将 GroupHandler 设置为其类的定义。如果是这种情况,我会在容器中添加一个新定义,用我的 CustomHandler 装饰原始处理程序。

    旁注起初我尝试包装所有处理程序(当然除了 CustomHandler :)),但由于一些处理程序实现了其他接口(例如使用 EventSubscriberInterface 的ConsoleHandler ) 这不起作用,并导致了我不想以某种骇人听闻的方式解决的问题。

    不要忘记将此编译器传递添加到 AppBundle 类中的容器

    class AppBundle extends Bundle
    {
        public function build(ContainerBuilder $container)
        {
            $container->addCompilerPass(new CustomMonologHandlerPass());
        }
    }
    

    现在一切就绪,您必须对处理程序进行分组才能完成这项工作:

    app/config(_prod|_dev).yml

    monolog:
        handlers:
            my_group:
                type: group
                members: [ 'graylog' ]
            graylog:
                type: gelf
                publisher:
                    id: my.publisher
                level: debug
                formatter: my.formatter
    

    简单的方法

    我们使用与复杂方式相同的 CustomHandler,然后我们在配置中定义我们的处理程序:

    app/config(_prod|_dev).yml

    monolog:
        handlers:
            graylog:
                type: gelf
                publisher:
                    id: my.publisher
                level: debug
                formatter: my.formatter
    

    用你自己的 CustomHandler 装饰你的 services.yml 中的处理程序

    services.yml

    CustomHandler:
        class: CustomHandler
        decorates: monolog.handler.graylog
        arguments: ['@CustomHandler.inner']
    

    对于 decorates 属性,您必须使用格式 monolog.handler.$NAME_SPECIFIED_AS_KEY_IN_CONFIG,在本例中为 graylog。

    ...就是这样

    总结

    虽然这两种方法都有效,但我使用了第一种,因为我们有几个 symfony 项目我需要它并装饰所有处理程序 手动不是我想要的。

    我希望这会有所帮助(即使我的答案已经很晚了:))

    【讨论】:

    • 我能够使用“简单的方法”使其工作。我真的不明白“复杂方式”应该如何工作。
    猜你喜欢
    • 2019-08-07
    • 2012-02-09
    • 1970-01-01
    • 1970-01-01
    • 2020-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-23
    相关资源
    最近更新 更多