diff --git a/DependencyInjection/Compiler/AddProcessorsPass.php b/DependencyInjection/Compiler/AddProcessorsPass.php index e9d0f8af..43ed91b5 100644 --- a/DependencyInjection/Compiler/AddProcessorsPass.php +++ b/DependencyInjection/Compiler/AddProcessorsPass.php @@ -29,40 +29,65 @@ public function process(ContainerBuilder $container) return; } + $processors = []; + foreach ($container->findTaggedServiceIds('monolog.processor') as $id => $tags) { foreach ($tags as $tag) { - if (!empty($tag['channel']) && !empty($tag['handler'])) { - throw new \InvalidArgumentException(sprintf('you cannot specify both the "handler" and "channel" attributes for the "monolog.processor" tag on service "%s"', $id)); + if (!isset($tag['priority'])) { + $tag['priority'] = 0; } - if (!empty($tag['handler'])) { - $definition = $container->findDefinition(sprintf('monolog.handler.%s', $tag['handler'])); - $parentDef = $definition; - while (!$parentDef->getClass() && $parentDef instanceof ChildDefinition) { - $parentDef = $container->findDefinition($parentDef->getParent()); - } - $class = $container->getParameterBag()->resolveValue($parentDef->getClass()); - if (!method_exists($class, 'pushProcessor')) { - throw new \InvalidArgumentException(sprintf('The "%s" handler does not accept processors', $tag['handler'])); - } - } elseif (!empty($tag['channel'])) { - if ('app' === $tag['channel']) { - $definition = $container->getDefinition('monolog.logger'); - } else { - $definition = $container->getDefinition(sprintf('monolog.logger.%s', $tag['channel'])); - } - } else { - $definition = $container->getDefinition('monolog.logger_prototype'); - } + $processors[] = [ + 'id' => $id, + 'tag' => $tag, + ]; + } + } + + // Sort by priority so that higher-prio processors are added last. + // The effect is the monolog will call the higher-prio processors first + usort( + $processors, + function (array $left, array $right) { + return $left['tag']['priority'] <=> $right['tag']['priority']; + } + ); - if (!empty($tag['method'])) { - $processor = [new Reference($id), $tag['method']]; + foreach ($processors as $processor) { + $tag = $processor['tag']; + $id = $processor['id']; + + if (!empty($tag['channel']) && !empty($tag['handler'])) { + throw new \InvalidArgumentException(sprintf('you cannot specify both the "handler" and "channel" attributes for the "monolog.processor" tag on service "%s"', $id)); + } + + if (!empty($tag['handler'])) { + $definition = $container->findDefinition(sprintf('monolog.handler.%s', $tag['handler'])); + $parentDef = $definition; + while (!$parentDef->getClass() && $parentDef instanceof ChildDefinition) { + $parentDef = $container->findDefinition($parentDef->getParent()); + } + $class = $container->getParameterBag()->resolveValue($parentDef->getClass()); + if (!method_exists($class, 'pushProcessor')) { + throw new \InvalidArgumentException(sprintf('The "%s" handler does not accept processors', $tag['handler'])); + } + } elseif (!empty($tag['channel'])) { + if ('app' === $tag['channel']) { + $definition = $container->getDefinition('monolog.logger'); } else { - // If no method is defined, fallback to use __invoke - $processor = new Reference($id); + $definition = $container->getDefinition(sprintf('monolog.logger.%s', $tag['channel'])); } - $definition->addMethodCall('pushProcessor', [$processor]); + } else { + $definition = $container->getDefinition('monolog.logger_prototype'); + } + + if (!empty($tag['method'])) { + $processor = [new Reference($id), $tag['method']]; + } else { + // If no method is defined, fallback to use __invoke + $processor = new Reference($id); } + $definition->addMethodCall('pushProcessor', [$processor]); } } } diff --git a/Tests/DependencyInjection/Compiler/AddProcessorsPassTest.php b/Tests/DependencyInjection/Compiler/AddProcessorsPassTest.php index 746a3999..c7b7f732 100644 --- a/Tests/DependencyInjection/Compiler/AddProcessorsPassTest.php +++ b/Tests/DependencyInjection/Compiler/AddProcessorsPassTest.php @@ -38,6 +38,13 @@ public function testHandlerProcessors() $calls = $service->getMethodCalls(); $this->assertCount(1, $calls); $this->assertEquals(['pushProcessor', [new Reference('test2')]], $calls[0]); + + $service = $container->getDefinition('monolog.handler.priority_test'); + $calls = $service->getMethodCalls(); + $this->assertCount(3, $calls); + $this->assertEquals(['pushProcessor', [new Reference('processor-10')]], $calls[0]); + $this->assertEquals(['pushProcessor', [new Reference('processor+10')]], $calls[1]); + $this->assertEquals(['pushProcessor', [new Reference('processor+20')]], $calls[2]); } public function testFailureOnHandlerWithoutPushProcessor() @@ -76,9 +83,11 @@ protected function getContainer() $container->setParameter('monolog.handler.console.class', ConsoleHandler::class); $container->setDefinition('monolog.handler.test', new Definition('%monolog.handler.console.class%', [100, false])); $container->setDefinition('handler_test', new Definition('%monolog.handler.console.class%', [100, false])); + $container->setDefinition('monolog.handler.priority_test', new Definition('%monolog.handler.console.class%', [100, false])); $container->setAlias('monolog.handler.test2', 'handler_test'); $definition->addMethodCall('pushHandler', [new Reference('monolog.handler.test')]); $definition->addMethodCall('pushHandler', [new Reference('monolog.handler.test2')]); + $definition->addMethodCall('pushHandler', [new Reference('monolog.handler.priority_test')]); $service = new Definition('TestClass', ['false', new Reference('logger')]); $service->addTag('monolog.processor', ['handler' => 'test']); @@ -88,6 +97,18 @@ protected function getContainer() $service->addTag('monolog.processor', ['handler' => 'test2']); $container->setDefinition('test2', $service); + $service = new Definition('TestClass', ['false', new Reference('logger')]); + $service->addTag('monolog.processor', ['handler' => 'priority_test', 'priority' => 10]); + $container->setDefinition('processor+10', $service); + + $service = new Definition('TestClass', ['false', new Reference('logger')]); + $service->addTag('monolog.processor', ['handler' => 'priority_test', 'priority' => -10]); + $container->setDefinition('processor-10', $service); + + $service = new Definition('TestClass', ['false', new Reference('logger')]); + $service->addTag('monolog.processor', ['handler' => 'priority_test', 'priority' => 20]); + $container->setDefinition('processor+20', $service); + $container->getCompilerPassConfig()->setOptimizationPasses([]); $container->getCompilerPassConfig()->setRemovingPasses([]); $container->addCompilerPass(new AddProcessorsPass());