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 :))