From 4694fc2f807ff2eb7f7c38d394f993c051f6c4a9 Mon Sep 17 00:00:00 2001 From: HypeMC <2445045+HypeMC@users.noreply.github.com> Date: Tue, 10 May 2022 14:32:29 +0200 Subject: [PATCH] Add ability to configure the PsrLogMessageProcessor (#417) --- DependencyInjection/Configuration.php | 15 ++++- DependencyInjection/MonologExtension.php | 57 ++++++++++++++----- Resources/config/schema/monolog-1.0.xsd | 9 ++- .../DependencyInjection/ConfigurationTest.php | 35 ++++++++++++ .../FixtureMonologExtensionTest.php | 50 ++++++++++++++++ .../process_psr_3_messages_with_arguments.xml | 16 ++++++ ...ocess_psr_3_messages_without_arguments.xml | 13 +++++ .../process_psr_3_messages_with_arguments.yml | 10 ++++ ...ocess_psr_3_messages_without_arguments.yml | 6 ++ 9 files changed, 194 insertions(+), 17 deletions(-) create mode 100644 Tests/DependencyInjection/Fixtures/xml/process_psr_3_messages_with_arguments.xml create mode 100644 Tests/DependencyInjection/Fixtures/xml/process_psr_3_messages_without_arguments.xml create mode 100644 Tests/DependencyInjection/Fixtures/yml/process_psr_3_messages_with_arguments.yml create mode 100644 Tests/DependencyInjection/Fixtures/yml/process_psr_3_messages_without_arguments.yml diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 339f524b..4a7b36a5 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -11,12 +11,12 @@ namespace Symfony\Bundle\MonologBundle\DependencyInjection; +use Monolog\Logger; use Symfony\Component\Config\Definition\BaseNode; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; -use Monolog\Logger; /** * This class contains the configuration information for the bundle @@ -426,7 +426,18 @@ public function getConfigTreeBuilder() ->scalarNode('app_name')->defaultNull()->end() ->booleanNode('fill_extra_context')->defaultFalse()->end() // sentry ->booleanNode('include_stacktraces')->defaultFalse()->end() - ->booleanNode('process_psr_3_messages')->defaultNull()->end() + ->arrayNode('process_psr_3_messages') + ->addDefaultsIfNotSet() + ->beforeNormalization() + ->ifTrue(static function ($v) { return !\is_array($v); }) + ->then(static function ($v) { return ['enabled' => $v]; }) + ->end() + ->children() + ->booleanNode('enabled')->defaultNull()->end() + ->scalarNode('date_format')->end() + ->booleanNode('remove_used_context_fields')->end() + ->end() + ->end() ->scalarNode('path')->defaultValue('%kernel.logs_dir%/%kernel.environment%.log')->end() // stream and rotating ->scalarNode('file_permission') // stream and rotating ->defaultNull() diff --git a/DependencyInjection/MonologExtension.php b/DependencyInjection/MonologExtension.php index d793d1cc..cb884faf 100644 --- a/DependencyInjection/MonologExtension.php +++ b/DependencyInjection/MonologExtension.php @@ -13,9 +13,10 @@ use Monolog\Attribute\AsMonologProcessor; use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; +use Monolog\Handler\HandlerInterface; use Monolog\Logger; use Monolog\Processor\ProcessorInterface; -use Monolog\Handler\HandlerInterface; +use Monolog\Processor\PsrLogMessageProcessor; use Monolog\ResettableInterface; use Symfony\Bridge\Monolog\Handler\FingersCrossed\HttpCodeActivationStrategy; use Symfony\Bridge\Monolog\Processor\SwitchUserTokenProcessor; @@ -203,21 +204,13 @@ private function buildHandler(ContainerBuilder $container, $name, array $handler $definition->setConfigurator(['Symfony\\Bundle\\MonologBundle\\MonologBundle', 'includeStacktraces']); } - if (null === $handler['process_psr_3_messages']) { - $handler['process_psr_3_messages'] = !isset($handler['handler']) && !$handler['members']; + if (null === $handler['process_psr_3_messages']['enabled']) { + $handler['process_psr_3_messages']['enabled'] = !isset($handler['handler']) && !$handler['members']; } - if ($handler['process_psr_3_messages']) { - if (method_exists($handlerClass, 'pushProcessor')) { - $processorId = 'monolog.processor.psr_log_message'; - if (!$container->hasDefinition($processorId)) { - $processor = new Definition('Monolog\\Processor\\PsrLogMessageProcessor'); - $processor->setPublic(false); - $container->setDefinition($processorId, $processor); - } - - $definition->addMethodCall('pushProcessor', [new Reference($processorId)]); - } + if ($handler['process_psr_3_messages']['enabled'] && method_exists($handlerClass, 'pushProcessor')) { + $processorId = $this->buildPsrLogMessageProcessor($container, $handler['process_psr_3_messages']); + $definition->addMethodCall('pushProcessor', [new Reference($processorId)]); } switch ($handler['type']) { @@ -1040,4 +1033,40 @@ private function getHandlerClassByType($handlerType) return $typeToClassMapping[$handlerType]; } + + private function buildPsrLogMessageProcessor(ContainerBuilder $container, array $processorOptions): string + { + static $hasConstructorArguments; + + if (!isset($hasConstructorArguments)) { + $reflectionConstructor = (new \ReflectionClass(PsrLogMessageProcessor::class))->getConstructor(); + $hasConstructorArguments = null !== $reflectionConstructor && $reflectionConstructor->getNumberOfParameters() > 0; + unset($reflectionConstructor); + } + + $processorId = 'monolog.processor.psr_log_message'; + $processorArguments = []; + + unset($processorOptions['enabled']); + + if (!empty($processorOptions)) { + if (!$hasConstructorArguments) { + throw new \RuntimeException('Monolog 1.26 or higher is required for the "date_format" and "remove_used_context_fields" options to be used.'); + } + $processorArguments = [ + $processorOptions['date_format'] ?? null, + $processorOptions['remove_used_context_fields'] ?? false, + ]; + $processorId .= '.'.ContainerBuilder::hash($processorArguments); + } + + if (!$container->hasDefinition($processorId)) { + $processor = new Definition(PsrLogMessageProcessor::class); + $processor->setPublic(false); + $processor->setArguments($processorArguments); + $container->setDefinition($processorId, $processor); + } + + return $processorId; + } } diff --git a/Resources/config/schema/monolog-1.0.xsd b/Resources/config/schema/monolog-1.0.xsd index eb00e68a..b00e969e 100644 --- a/Resources/config/schema/monolog-1.0.xsd +++ b/Resources/config/schema/monolog-1.0.xsd @@ -29,6 +29,7 @@ + @@ -192,7 +193,13 @@ - + + + + + + + diff --git a/Tests/DependencyInjection/ConfigurationTest.php b/Tests/DependencyInjection/ConfigurationTest.php index 7548af94..2d537eee 100644 --- a/Tests/DependencyInjection/ConfigurationTest.php +++ b/Tests/DependencyInjection/ConfigurationTest.php @@ -489,6 +489,41 @@ public function testConsoleFormatterOptionsRename() $this->assertArrayNotHasKey('console_formater_options', $config['handlers']['both2']); } + /** + * @dataProvider processPsr3MessagesProvider + */ + public function testWithProcessPsr3Messages(array $configuration, array $processedConfiguration): void + { + $configs = [ + [ + 'handlers' => [ + 'main' => ['type' => 'stream'] + $configuration, + ], + ], + ]; + + $config = $this->process($configs); + + $this->assertEquals($processedConfiguration, $config['handlers']['main']['process_psr_3_messages']); + } + + public function processPsr3MessagesProvider(): iterable + { + yield 'Not specified' => [[], ['enabled' => null]]; + yield 'Null' => [['process_psr_3_messages' => null], ['enabled' => true]]; + yield 'True' => [['process_psr_3_messages' => true], ['enabled' => true]]; + yield 'False' => [['process_psr_3_messages' => false], ['enabled' => false]]; + + yield 'Date format' => [ + ['process_psr_3_messages' => ['date_format' => 'Y']], + ['date_format' => 'Y', 'enabled' => null], + ]; + yield 'Enabled false & remove used' => [ + ['process_psr_3_messages' => ['enabled' => false, 'remove_used_context_fields' => true]], + ['enabled' => false, 'remove_used_context_fields' => true], + ]; + } + /** * Processes an array of configurations and returns a compiled version. * diff --git a/Tests/DependencyInjection/FixtureMonologExtensionTest.php b/Tests/DependencyInjection/FixtureMonologExtensionTest.php index fa73d732..5515ff21 100644 --- a/Tests/DependencyInjection/FixtureMonologExtensionTest.php +++ b/Tests/DependencyInjection/FixtureMonologExtensionTest.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\MonologBundle\Tests\DependencyInjection; use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; +use Monolog\Processor\PsrLogMessageProcessor; use Symfony\Bridge\Monolog\Processor\SwitchUserTokenProcessor; use Symfony\Bundle\MonologBundle\DependencyInjection\Compiler\LoggerChannelPass; use Symfony\Bundle\MonologBundle\DependencyInjection\MonologExtension; @@ -280,6 +281,55 @@ public function testPsr3MessageProcessingDisabled() $this->assertNotContainsEquals(['pushProcessor', [new Reference('monolog.processor.psr_log_message')]], $methodCalls, 'The PSR-3 processor should not be enabled'); } + public function testPsrLogMessageProcessorHasConstructorArguments(): void + { + $reflectionConstructor = (new \ReflectionClass(PsrLogMessageProcessor::class))->getConstructor(); + if (null === $reflectionConstructor || $reflectionConstructor->getNumberOfParameters() <= 0) { + $this->markTestSkipped('Monolog >= 1.26 is needed.'); + } + + $container = $this->getContainer('process_psr_3_messages_with_arguments'); + + $processors = [ + 'monolog.processor.psr_log_message' => ['name' => 'without_arguments', 'arguments' => []], + 'monolog.processor.psr_log_message.'.ContainerBuilder::hash($arguments = ['Y', false]) => [ + 'name' => 'with_arguments', + 'arguments' => $arguments, + ], + ]; + foreach ($processors as $processorId => $settings) { + $this->assertTrue($container->hasDefinition($processorId)); + $processor = $container->getDefinition($processorId); + $this->assertDICConstructorArguments($processor, $settings['arguments']); + + $this->assertTrue($container->hasDefinition($handlerId = 'monolog.handler.'.$settings['name'])); + $handler = $container->getDefinition($handlerId); + $this->assertDICDefinitionMethodCallAt(0, $handler, 'pushProcessor', [new Reference($processorId)]); + } + } + + public function testPsrLogMessageProcessorDoesNotHaveConstructorArguments(): void + { + $reflectionConstructor = (new \ReflectionClass(PsrLogMessageProcessor::class))->getConstructor(); + if (null !== $reflectionConstructor && $reflectionConstructor->getNumberOfParameters() > 0) { + $this->markTestSkipped('Monolog < 1.26 is needed.'); + } + + $container = $this->getContainer('process_psr_3_messages_without_arguments'); + + $this->assertTrue($container->hasDefinition($processorId = 'monolog.processor.psr_log_message')); + $processor = $container->getDefinition($processorId); + $this->assertDICConstructorArguments($processor, []); + + $this->assertTrue($container->hasDefinition($handlerId = 'monolog.handler.without_arguments')); + $handler = $container->getDefinition($handlerId); + $this->assertDICDefinitionMethodCallAt(0, $handler, 'pushProcessor', [new Reference($processorId)]); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Monolog 1.26 or higher is required for the "date_format" and "remove_used_context_fields" options to be used.'); + $this->getContainer('process_psr_3_messages_with_arguments'); + } + public function testNativeMailer() { $container = $this->getContainer('native_mailer'); diff --git a/Tests/DependencyInjection/Fixtures/xml/process_psr_3_messages_with_arguments.xml b/Tests/DependencyInjection/Fixtures/xml/process_psr_3_messages_with_arguments.xml new file mode 100644 index 00000000..693f7fe8 --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/xml/process_psr_3_messages_with_arguments.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/Tests/DependencyInjection/Fixtures/xml/process_psr_3_messages_without_arguments.xml b/Tests/DependencyInjection/Fixtures/xml/process_psr_3_messages_without_arguments.xml new file mode 100644 index 00000000..288eb750 --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/xml/process_psr_3_messages_without_arguments.xml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/Tests/DependencyInjection/Fixtures/yml/process_psr_3_messages_with_arguments.yml b/Tests/DependencyInjection/Fixtures/yml/process_psr_3_messages_with_arguments.yml new file mode 100644 index 00000000..d98cb8a0 --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/yml/process_psr_3_messages_with_arguments.yml @@ -0,0 +1,10 @@ +monolog: + handlers: + with_arguments: + type: stream + process_psr_3_messages: + date_format: 'Y' + without_arguments: + type: stream + process_psr_3_messages: + enabled: true diff --git a/Tests/DependencyInjection/Fixtures/yml/process_psr_3_messages_without_arguments.yml b/Tests/DependencyInjection/Fixtures/yml/process_psr_3_messages_without_arguments.yml new file mode 100644 index 00000000..b04e190c --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/yml/process_psr_3_messages_without_arguments.yml @@ -0,0 +1,6 @@ +monolog: + handlers: + without_arguments: + type: stream + process_psr_3_messages: + enabled: true