How to do advanced filtering of Monolog messages in Symfony?

♀尐吖头ヾ 提交于 2019-12-05 01:34:31

I'm not sure why using a HandlerWrapper is the wrong way to do it.

I had the same issue and figured a way how to wrap a handler in order to filter certain records.

In this answer I describe two ways to solve this, a more complex and an easy one.

(More or less) complex way

First thing I did, was to create a new class wich extends the HandlerWrapper and added some logic where I can filter records:

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
    }
}

Then I created a service definition and a CompilerPass where I can wrap the 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();
    }
}

As you can see I go over all definitions here and find the ones which have the GroupHandler set as their class. If this is the case, I add a new definition to the container which decorates the original handler with my CustomHandler.

Side note: At first I tried to wrap all handlers (except the CustomHandler of course :)) but due to some handlers implementing other interfaces (like the ConsoleHandler using the EventSubscriberInterface) this did not work and lead to issues I didn't want to solve in some hacky way.

Don't forget to add this compiler pass to the container in your AppBundle class

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

Now that everything is in place you have to group your handlers in order to make this work:

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

Easy way

We use the same CustomHandler as we did in the complex way, then we define our handlers in the config:

app/config(_prod|_dev).yml

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

Decorate the handler in your services.yml with your own CustomHandler

services.yml

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

For the decorates property you have to use the format monolog.handler.$NAME_SPECIFIED_AS_KEY_IN_CONFIG, in this case it was graylog.

... and thats it

Summary

While both ways work, I used the first one, as we have several symfony projects where I need this and decorating all handlers manually is just not what I wanted.

I hope this helps (even though I'm quite late for an answer :))

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!