How to do advanced filtering of Monolog messages in Symfony?
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 handlersmanually is just not what I wanted.
I hope this helps (even though I'm quite late for an answer :))