From 558d30d8fc4077f9ddc84a3b169367a099cc32e3 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Thu, 25 Sep 2025 08:11:30 +0200 Subject: [PATCH] Add handler_default_channels configuration option --- config/schema/monolog-1.0.xsd | 2 + src/DependencyInjection/Configuration.php | 15 +++-- src/DependencyInjection/MonologExtension.php | 12 +++- .../DependencyInjection/ConfigurationTest.php | 47 ++++++++++++++++ .../FixtureMonologExtensionTestCase.php | 55 +++++++++++++++++++ .../xml/handlers_with_default_channels.xml | 40 ++++++++++++++ .../yml/handlers_with_default_channels.yml | 24 ++++++++ 7 files changed, 188 insertions(+), 7 deletions(-) create mode 100644 tests/DependencyInjection/Fixtures/xml/handlers_with_default_channels.xml create mode 100644 tests/DependencyInjection/Fixtures/yml/handlers_with_default_channels.yml diff --git a/config/schema/monolog-1.0.xsd b/config/schema/monolog-1.0.xsd index 9dc49ed0..3576a2f3 100644 --- a/config/schema/monolog-1.0.xsd +++ b/config/schema/monolog-1.0.xsd @@ -11,6 +11,7 @@ + @@ -93,6 +94,7 @@ + diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 5c3a7e3a..ebeb1434 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -390,6 +390,8 @@ public function getConfigTreeBuilder(): TreeBuilder $treeBuilder = new TreeBuilder('monolog'); $rootNode = $treeBuilder->getRootNode(); + $this->addChannelsSection($rootNode, 'handler_default_channels'); + $handlers = $rootNode ->fixXmlConfig('channel') ->fixXmlConfig('handler') @@ -634,6 +636,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->end() ->scalarNode('formatter')->end() ->booleanNode('nested')->defaultFalse()->end() + ->booleanNode('use_default_channels')->defaultTrue()->end() ->end(); $this->addGelfSection($handlerNode); @@ -1086,11 +1089,11 @@ private function addVerbosityLevelSection(ArrayNodeDefinition $handlerNode) ; } - private function addChannelsSection(ArrayNodeDefinition $handlerNode) + private function addChannelsSection(ArrayNodeDefinition $handlerNode, string $nodeName = 'channels') { $handlerNode ->children() - ->arrayNode('channels') + ->arrayNode($nodeName) ->fixXmlConfig('channel', 'elements') ->canBeUnset() ->beforeNormalization() @@ -1106,7 +1109,7 @@ private function addChannelsSection(ArrayNodeDefinition $handlerNode) ->thenUnset() ->end() ->validate() - ->always(function ($v) { + ->always(function ($v) use ($nodeName) { $isExclusive = null; if (isset($v['type'])) { $isExclusive = 'exclusive' === $v['type']; @@ -1116,13 +1119,13 @@ private function addChannelsSection(ArrayNodeDefinition $handlerNode) foreach ($v['elements'] as $element) { if (0 === strpos($element, '!')) { if (false === $isExclusive) { - throw new InvalidConfigurationException('Cannot combine exclusive/inclusive definitions in channels list.'); + throw new InvalidConfigurationException(\sprintf('Cannot combine exclusive/inclusive definitions in %s list.', $nodeName)); } $elements[] = substr($element, 1); $isExclusive = true; } else { if (true === $isExclusive) { - throw new InvalidConfigurationException('Cannot combine exclusive/inclusive definitions in channels list'); + throw new InvalidConfigurationException(\sprintf('Cannot combine exclusive/inclusive definitions in %s list', $nodeName)); } $elements[] = $element; $isExclusive = false; @@ -1141,7 +1144,7 @@ private function addChannelsSection(ArrayNodeDefinition $handlerNode) ->scalarNode('type') ->validate() ->ifNotInArray(['inclusive', 'exclusive']) - ->thenInvalid('The type of channels has to be inclusive or exclusive') + ->thenInvalid(\sprintf('The type of %s has to be inclusive or exclusive', $nodeName)) ->end() ->end() ->arrayNode('elements') diff --git a/src/DependencyInjection/MonologExtension.php b/src/DependencyInjection/MonologExtension.php index afa2b280..88c00e2d 100644 --- a/src/DependencyInjection/MonologExtension.php +++ b/src/DependencyInjection/MonologExtension.php @@ -76,6 +76,7 @@ public function load(array $configs, ContainerBuilder $container) $handlers[$handler['priority']][] = [ 'id' => $this->buildHandler($container, $name, $handler), 'channels' => empty($handler['channels']) ? null : $handler['channels'], + 'use_default_channels' => $handler['use_default_channels'], ]; } @@ -92,10 +93,19 @@ public function load(array $configs, ContainerBuilder $container) } } + $defaultChannels = $config['handler_default_channels'] ?? null; $handlersToChannels = []; foreach ($sortedHandlers as $handler) { if (!\in_array($handler['id'], $this->nestedHandlers)) { - $handlersToChannels[$handler['id']] = $handler['channels']; + $channels = $handler['channels']; + if (null !== $defaultChannels && $handler['use_default_channels']) { + if (null === $channels) { + $channels = $defaultChannels; + } elseif ($channels['type'] === $defaultChannels['type']) { + $channels['elements'] = array_unique(array_merge($channels['elements'], $defaultChannels['elements'])); + } + } + $handlersToChannels[$handler['id']] = $channels; } } $container->setParameter('monolog.handlers_to_channels', $handlersToChannels); diff --git a/tests/DependencyInjection/ConfigurationTest.php b/tests/DependencyInjection/ConfigurationTest.php index d581a049..66f03a31 100644 --- a/tests/DependencyInjection/ConfigurationTest.php +++ b/tests/DependencyInjection/ConfigurationTest.php @@ -324,6 +324,31 @@ public function testWithType() $this->assertEquals('B', $config['handlers']['foo']['channels']['elements'][1]); } + public function testWithUseDefaultChannels() + { + $configs = [ + [ + 'handlers' => [ + 'foo' => [ + 'type' => 'stream', + 'path' => '/foo', + 'use_default_channels' => true, + ], + 'bar' => [ + 'type' => 'stream', + 'path' => '/bar', + 'use_default_channels' => false, + ], + ], + ], + ]; + + $config = $this->process($configs); + + $this->assertTrue($config['handlers']['foo']['use_default_channels']); + $this->assertFalse($config['handlers']['bar']['use_default_channels']); + } + public function testWithFilePermission() { $configs = [ @@ -555,6 +580,28 @@ public static function processPsr3MessagesProvider(): iterable ]; } + /** + * @dataProvider provideHandlerDefaultChannels + */ + public function testHandlerDefaultChannels($configuration, ?array $processedConfiguration) + { + $config = $this->process([['handler_default_channels' => $configuration]]); + + $this->assertEquals($processedConfiguration, $config['handler_default_channels']); + } + + public static function provideHandlerDefaultChannels(): iterable + { + yield 'None' => [null, null]; + yield 'Empty array' => [[], null]; + yield 'As string' => ['!foo', ['type' => 'exclusive', 'elements' => ['foo']]]; + yield 'As array' => [['foo', 'bar'], ['type' => 'inclusive', 'elements' => ['foo', 'bar']]]; + yield 'With elements key' => [['elements' => ['!foo', '!bar']], ['type' => 'exclusive', 'elements' => ['foo', 'bar']]]; + yield 'With type key' => [['type' => 'exclusive', 'elements' => ['!foo']], ['type' => 'exclusive', 'elements' => ['foo']]]; + yield 'XML' => [['channel' => ['foo', 'bar']], ['type' => 'inclusive', 'elements' => ['foo', 'bar']]]; + yield 'XML with type' => [['type' => 'exclusive', 'channel' => ['!foo']], ['type' => 'exclusive', 'elements' => ['foo']]]; + } + /** * Processes an array of configurations and returns a compiled version. * diff --git a/tests/DependencyInjection/FixtureMonologExtensionTestCase.php b/tests/DependencyInjection/FixtureMonologExtensionTestCase.php index fb6dbf96..5cf1b53d 100644 --- a/tests/DependencyInjection/FixtureMonologExtensionTestCase.php +++ b/tests/DependencyInjection/FixtureMonologExtensionTestCase.php @@ -145,6 +145,61 @@ public function testHandlersWithChannels() ); } + public function testHandlersWithDefaultChannels() + { + $container = $this->getContainer('handlers_with_default_channels'); + + $this->assertFalse($container->hasParameter('monolog.handler_default_channels')); + + $this->assertEquals( + [ + 'monolog.handler.one' => ['type' => 'inclusive', 'elements' => ['foo']], + 'monolog.handler.two' => ['type' => 'exclusive', 'elements' => ['bar', 'baz']], + 'monolog.handler.three' => ['type' => 'exclusive', 'elements' => ['bar', 'baz', 'foo']], + 'monolog.handler.four' => ['type' => 'exclusive', 'elements' => ['foo', 'bar']], + 'monolog.handler.five' => null, + 'monolog.handler.six' => ['type' => 'exclusive', 'elements' => ['foo', 'bar']], + ], + $container->getParameter('monolog.handlers_to_channels') + ); + + $this->assertTrue($container->hasDefinition('monolog.logger')); + $this->assertTrue($container->hasDefinition('monolog.logger.foo')); + $this->assertTrue($container->hasDefinition('monolog.logger.bar')); + $this->assertTrue($container->hasDefinition('monolog.logger.baz')); + $this->assertTrue($container->hasDefinition('monolog.handler.one')); + $this->assertTrue($container->hasDefinition('monolog.handler.two')); + $this->assertTrue($container->hasDefinition('monolog.handler.three')); + $this->assertTrue($container->hasDefinition('monolog.handler.four')); + $this->assertTrue($container->hasDefinition('monolog.handler.five')); + $this->assertTrue($container->hasDefinition('monolog.handler.six')); + + $logger = $container->getDefinition('monolog.logger'); + $this->assertCount(6, $logger->getMethodCalls()); + $this->assertDICDefinitionMethodCallAt(5, $logger, 'pushHandler', [new Reference('monolog.handler.two')]); + $this->assertDICDefinitionMethodCallAt(4, $logger, 'pushHandler', [new Reference('monolog.handler.three')]); + $this->assertDICDefinitionMethodCallAt(3, $logger, 'pushHandler', [new Reference('monolog.handler.four')]); + $this->assertDICDefinitionMethodCallAt(2, $logger, 'pushHandler', [new Reference('monolog.handler.five')]); + $this->assertDICDefinitionMethodCallAt(1, $logger, 'pushHandler', [new Reference('monolog.handler.six')]); + $this->assertDICDefinitionMethodCallAt(0, $logger, 'useMicrosecondTimestamps', ['%monolog.use_microseconds%']); + + $logger = $container->getDefinition('monolog.logger.foo'); + $this->assertCount(3, $logger->getMethodCalls()); + $this->assertDICDefinitionMethodCallAt(2, $logger, 'pushHandler', [new Reference('monolog.handler.one')]); + $this->assertDICDefinitionMethodCallAt(1, $logger, 'pushHandler', [new Reference('monolog.handler.two')]); + $this->assertDICDefinitionMethodCallAt(0, $logger, 'pushHandler', [new Reference('monolog.handler.five')]); + + $logger = $container->getDefinition('monolog.logger.bar'); + $this->assertCount(1, $logger->getMethodCalls()); + $this->assertDICDefinitionMethodCallAt(0, $logger, 'pushHandler', [new Reference('monolog.handler.five')]); + + $logger = $container->getDefinition('monolog.logger.baz'); + $this->assertCount(3, $logger->getMethodCalls()); + $this->assertDICDefinitionMethodCallAt(2, $logger, 'pushHandler', [new Reference('monolog.handler.four')]); + $this->assertDICDefinitionMethodCallAt(1, $logger, 'pushHandler', [new Reference('monolog.handler.five')]); + $this->assertDICDefinitionMethodCallAt(0, $logger, 'pushHandler', [new Reference('monolog.handler.six')]); + } + /** @group legacy */ public function testSingleEmailRecipient() { diff --git a/tests/DependencyInjection/Fixtures/xml/handlers_with_default_channels.xml b/tests/DependencyInjection/Fixtures/xml/handlers_with_default_channels.xml new file mode 100644 index 00000000..eebd5e37 --- /dev/null +++ b/tests/DependencyInjection/Fixtures/xml/handlers_with_default_channels.xml @@ -0,0 +1,40 @@ + + + + + + foo + bar + baz + + + !foo + !bar + + + + + foo + + + + + !bar + !baz + + + + + !bar + !baz + + + + + + + diff --git a/tests/DependencyInjection/Fixtures/yml/handlers_with_default_channels.yml b/tests/DependencyInjection/Fixtures/yml/handlers_with_default_channels.yml new file mode 100644 index 00000000..ff9a113e --- /dev/null +++ b/tests/DependencyInjection/Fixtures/yml/handlers_with_default_channels.yml @@ -0,0 +1,24 @@ +monolog: + channels: [ 'foo', 'bar', 'baz' ] + handler_default_channels: [ '!foo', '!bar' ] + handlers: + one: + type: stream + use_default_channels: true + channels: foo + two: + type: stream + use_default_channels: false + channels: [ '!bar', '!baz' ] + three: + type: stream + use_default_channels: true + channels: [ '!bar', '!baz' ] + four: + type: stream + use_default_channels: true + five: + type: stream + use_default_channels: false + six: + type: stream