如何在Symfony中对Monolog消息进行高级过滤?

时间:2016-10-28 06:44:23

标签: php symfony monolog

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

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

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

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

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

我正在寻找按内容过滤消息的解决方案。如何在Monolog中完成?

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

然而,这个想法不起作用,而另一个问题的答案表明,HandlerWrapper不适合解决这个问题。

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

1 个答案:

答案 0 :(得分:4)

我不确定为什么使用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 :))但是由于一些处理程序实现了其他接口(比如ConsoleHandler使用eventSubscriberInterface)这不起作用,导致我不想以某种黑客方式解决的问题。

不要忘记将此编译器传递添加到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']

对于装饰属性,您必须使用格式monolog.handler.$NAME_SPECIFIED_AS_KEY_IN_CONFIG,在这种情况下,它是graylog。

......就是这样

摘要

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

我希望这会有所帮助(尽管我的回答很晚了))。