From fba862afadc8ee6160b7a378ffbd00704b9e4bf0 Mon Sep 17 00:00:00 2001 From: Tibor Rac Date: Sun, 3 Nov 2024 11:39:08 +0100 Subject: [PATCH 1/4] Add support for custom Gelf encoders in Monolog configuration This commit introduces the ability to specify custom encoders (`json` and `compressed_json`) for Gelf publishers in Monolog's configuration. --- DependencyInjection/Configuration.php | 8 +- DependencyInjection/MonologExtension.php | 19 ++++ .../DependencyInjection/ConfigurationTest.php | 22 +++++ .../MonologExtensionTest.php | 91 +++++++++++++++++++ 4 files changed, 139 insertions(+), 1 deletion(-) diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 255eba47..df22ac5c 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -50,7 +50,12 @@ * - [bubble]: bool, defaults to true * * - gelf: - * - publisher: {id: ...} or {hostname: ..., port: ..., chunk_size: ...} + * - publiser: + * - id: string, service id of a publisher implementation, optional if hostname is given + * - hostname: string, optional if id is given + * - [port]: int, defaults to 12201 + * - [chunk_size]: int, defaults to 1420 + * - [encoder]: string, its value can be 'json' or 'compressed_json' * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true * @@ -816,6 +821,7 @@ private function addGelfSection(ArrayNodeDefinition $handerNode) ->scalarNode('hostname')->end() ->scalarNode('port')->defaultValue(12201)->end() ->scalarNode('chunk_size')->defaultValue(1420)->end() + ->scalarNode('encoder')->end() ->end() ->validate() ->ifTrue(function ($v) { diff --git a/DependencyInjection/MonologExtension.php b/DependencyInjection/MonologExtension.php index 1d47a984..dfad84ae 100644 --- a/DependencyInjection/MonologExtension.php +++ b/DependencyInjection/MonologExtension.php @@ -24,6 +24,7 @@ use Symfony\Bridge\Monolog\Processor\SwitchUserTokenProcessor; use Symfony\Bridge\Monolog\Processor\TokenProcessor; use Symfony\Bridge\Monolog\Processor\WebProcessor; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Argument\BoundArgument; use Symfony\Component\DependencyInjection\ChildDefinition; @@ -227,10 +228,28 @@ private function buildHandler(ContainerBuilder $container, $name, array $handler ]); $transport->setPublic(false); + if (isset($handler['publisher']['encoder'])) { + if ('compressed_json' === $handler['publisher']['encoder']) { + $encoderClass = 'Gelf\Encoder\CompressedJsonEncoder'; + } elseif ('json' === $handler['publisher']['encoder']) { + $encoderClass = 'Gelf\Encoder\JsonEncoder'; + } else { + throw new InvalidConfigurationException('The gelf message encoder must be either "compressed_json" or "json".'); + } + + $encoder = new Definition($encoderClass); + $encoder->setPublic(false); + + $transport->addMethodCall('setMessageEncoder', [$encoder]); + } + $publisher = new Definition('Gelf\Publisher', []); $publisher->addMethodCall('addTransport', [$transport]); $publisher->setPublic(false); } elseif (class_exists('Gelf\MessagePublisher')) { + if (isset($handler['publisher']['encoder']) && 'compressed_json' !== $handler['publisher']['encoder']) { + throw new InvalidConfigurationException('The Gelf\MessagePublisher publisher supports only the compressed json encoding. Omit the option to use the default encoding or use "compressed_json" as the encoder option.'); + } $publisher = new Definition('Gelf\MessagePublisher', [ $handler['publisher']['hostname'], $handler['publisher']['port'], diff --git a/Tests/DependencyInjection/ConfigurationTest.php b/Tests/DependencyInjection/ConfigurationTest.php index 516565e0..227997d6 100644 --- a/Tests/DependencyInjection/ConfigurationTest.php +++ b/Tests/DependencyInjection/ConfigurationTest.php @@ -110,6 +110,28 @@ public function testGelfPublisherService($publisher) $this->assertEquals('gelf.publisher', $config['handlers']['gelf']['publisher']['id']); } + public function testGelfPublisherWithEncoder(): void + { + $configs = [ + [ + 'handlers' => [ + 'gelf' => [ + 'type' => 'gelf', + 'publisher' => [ + 'hostname' => 'localhost', + 'encoder' => 'compressed_json', + ], + ], + ], + ], + ]; + + $config = $this->process($configs); + + $this->assertEquals('localhost', $config['handlers']['gelf']['publisher']['hostname']); + $this->assertEquals('compressed_json', $config['handlers']['gelf']['publisher']['encoder']); + } + public function testArrays() { $configs = [ diff --git a/Tests/DependencyInjection/MonologExtensionTest.php b/Tests/DependencyInjection/MonologExtensionTest.php index a0c82005..01f4e205 100644 --- a/Tests/DependencyInjection/MonologExtensionTest.php +++ b/Tests/DependencyInjection/MonologExtensionTest.php @@ -186,6 +186,97 @@ public function testExceptionWhenUsingGelfWithoutPublisherHostname() $loader->load([['handlers' => ['gelf' => ['type' => 'gelf', 'publisher' => []]]]], $container); } + public function testExceptionWhenUsingLegacyGelfImplementationWithUnsupportedEncoder(): void + { + if (!class_exists('Gelf\MessagePublisher')) { + class_alias(\stdClass::class, 'Gelf\MessagePublisher'); + } + + $container = new ContainerBuilder(); + $loader = new MonologExtension(); + + $this->expectException(InvalidConfigurationException::class); + + $loader->load([['handlers' => ['gelf' => ['type' => 'gelf', 'publisher' => ['hostname' => 'localhost', 'encoder' => 'json']]]]], $container); + } + + /** + * @dataProvider encoderOptionsProvider + */ + public function testLegacyGelfImplementationEncoderOption(array $config): void + { + if (!class_exists('Gelf\MessagePublisher')) { + class_alias(\stdClass::class, 'Gelf\MessagePublisher'); + } + + $container = $this->getContainer($config); + $this->assertTrue($container->hasDefinition('monolog.handler.gelf')); + + $handler = $container->getDefinition('monolog.handler.gelf'); + /** @var Definition $publisher */ + $publisher = $handler->getArguments()[0]; + + $this->assertDICConstructorArguments($publisher, ['localhost', 12201, 1420]); + } + + public function encoderOptionsProvider(): array + { + return [ + [ + [['handlers' => ['gelf' => ['type' => 'gelf', 'publisher' => ['hostname' => 'localhost', 'encoder' => 'compressed_json']]]]], + ], + [ + [['handlers' => ['gelf' => ['type' => 'gelf', 'publisher' => ['hostname' => 'localhost']]]]], + ], + ]; + } + + public function testExceptionWhenUsingGelfWithInvalidEncoder(): void + { + if (!class_exists('Gelf\Transport\UdpTransport')) { + class_alias(\stdClass::class, 'Gelf\Transport\UdpTransport'); + } + + $container = new ContainerBuilder(); + $loader = new MonologExtension(); + + $this->expectException(InvalidConfigurationException::class); + + $loader->load([['handlers' => ['gelf' => ['type' => 'gelf', 'publisher' => ['hostname' => 'localhost', 'encoder' => 'invalid_encoder']]]]], $container); + } + + /** + * @dataProvider gelfEncoderProvider + */ + public function testGelfWithEncoder($encoderValue, $expectedClass): void + { + if (!class_exists('Gelf\Transport\UdpTransport')) { + class_alias(\stdClass::class, 'Gelf\Transport\UdpTransport'); + } + + $container = $this->getContainer([['handlers' => ['gelf' => ['type' => 'gelf', 'publisher' => ['hostname' => 'localhost', 'encoder' => $encoderValue]]]]]); + $this->assertTrue($container->hasDefinition('monolog.handler.gelf')); + + $handler = $container->getDefinition('monolog.handler.gelf'); + /** @var Definition $publisher */ + $publisher = $handler->getArguments()[0]; + /** @var Definition $transport */ + $transport = $publisher->getMethodCalls()[0][1][0]; + $encoder = $transport->getMethodCalls()[0][1][0]; + + $this->assertDICConstructorArguments($transport, ['localhost', 12201, 1420]); + $this->assertDICDefinitionClass($encoder, $expectedClass); + $this->assertDICConstructorArguments($handler, [$publisher, 'DEBUG', true]); + } + + public function gelfEncoderProvider(): array + { + return [ + ['json', 'Gelf\Encoder\JsonEncoder'], + ['compressed_json', 'Gelf\Encoder\CompressedJsonEncoder'], + ]; + } + public function testExceptionWhenUsingServiceWithoutId() { $container = new ContainerBuilder(); From e0a765612bea40ac427e55687d900c0f6c3a47f9 Mon Sep 17 00:00:00 2001 From: Tibor Rac Date: Sun, 3 Nov 2024 14:25:25 +0100 Subject: [PATCH 2/4] Fix compatibility issue, update class_alias to use DummyClassForClassExistsCheck Replaced instances of \stdClass in class_alias calls with DummyClassForClassExistsCheck as before php 8.3 only user created class can be aliased. --- .../Fixtures/DummyClassForClassExistsCheck.php | 16 ++++++++++++++++ .../DependencyInjection/MonologExtensionTest.php | 9 +++++---- 2 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 Tests/DependencyInjection/Fixtures/DummyClassForClassExistsCheck.php diff --git a/Tests/DependencyInjection/Fixtures/DummyClassForClassExistsCheck.php b/Tests/DependencyInjection/Fixtures/DummyClassForClassExistsCheck.php new file mode 100644 index 00000000..805cf2bb --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/DummyClassForClassExistsCheck.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MonologBundle\Tests\DependencyInjection\Fixtures; + +class DummyClassForClassExistsCheck +{ +} diff --git a/Tests/DependencyInjection/MonologExtensionTest.php b/Tests/DependencyInjection/MonologExtensionTest.php index 01f4e205..bea358cc 100644 --- a/Tests/DependencyInjection/MonologExtensionTest.php +++ b/Tests/DependencyInjection/MonologExtensionTest.php @@ -22,6 +22,7 @@ use Symfony\Bundle\MonologBundle\Tests\DependencyInjection\Fixtures\AsMonologProcessor\FooProcessor; use Symfony\Bundle\MonologBundle\Tests\DependencyInjection\Fixtures\AsMonologProcessor\FooProcessorWithPriority; use Symfony\Bundle\MonologBundle\Tests\DependencyInjection\Fixtures\AsMonologProcessor\RedeclareMethodProcessor; +use Symfony\Bundle\MonologBundle\Tests\DependencyInjection\Fixtures\DummyClassForClassExistsCheck; use Symfony\Bundle\MonologBundle\Tests\DependencyInjection\Fixtures\ServiceWithChannel; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -189,7 +190,7 @@ public function testExceptionWhenUsingGelfWithoutPublisherHostname() public function testExceptionWhenUsingLegacyGelfImplementationWithUnsupportedEncoder(): void { if (!class_exists('Gelf\MessagePublisher')) { - class_alias(\stdClass::class, 'Gelf\MessagePublisher'); + class_alias(DummyClassForClassExistsCheck::class, 'Gelf\MessagePublisher'); } $container = new ContainerBuilder(); @@ -206,7 +207,7 @@ class_alias(\stdClass::class, 'Gelf\MessagePublisher'); public function testLegacyGelfImplementationEncoderOption(array $config): void { if (!class_exists('Gelf\MessagePublisher')) { - class_alias(\stdClass::class, 'Gelf\MessagePublisher'); + class_alias(DummyClassForClassExistsCheck::class, 'Gelf\MessagePublisher'); } $container = $this->getContainer($config); @@ -234,7 +235,7 @@ public function encoderOptionsProvider(): array public function testExceptionWhenUsingGelfWithInvalidEncoder(): void { if (!class_exists('Gelf\Transport\UdpTransport')) { - class_alias(\stdClass::class, 'Gelf\Transport\UdpTransport'); + class_alias(DummyClassForClassExistsCheck::class, 'Gelf\Transport\UdpTransport'); } $container = new ContainerBuilder(); @@ -251,7 +252,7 @@ class_alias(\stdClass::class, 'Gelf\Transport\UdpTransport'); public function testGelfWithEncoder($encoderValue, $expectedClass): void { if (!class_exists('Gelf\Transport\UdpTransport')) { - class_alias(\stdClass::class, 'Gelf\Transport\UdpTransport'); + class_alias(DummyClassForClassExistsCheck::class, 'Gelf\Transport\UdpTransport'); } $container = $this->getContainer([['handlers' => ['gelf' => ['type' => 'gelf', 'publisher' => ['hostname' => 'localhost', 'encoder' => $encoderValue]]]]]); From 9ec2b46c193f599263743d74946fecb1f4aee115 Mon Sep 17 00:00:00 2001 From: Tibor Rac Date: Mon, 4 Nov 2024 20:31:18 +0100 Subject: [PATCH 3/4] Fix typo in configuration comment --- DependencyInjection/Configuration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index df22ac5c..3661370f 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -50,7 +50,7 @@ * - [bubble]: bool, defaults to true * * - gelf: - * - publiser: + * - publisher: * - id: string, service id of a publisher implementation, optional if hostname is given * - hostname: string, optional if id is given * - [port]: int, defaults to 12201 From ff5cf014b4e866042d6e81cb67770aec5b86c0fe Mon Sep 17 00:00:00 2001 From: Tibor Rac Date: Fri, 15 Nov 2024 23:22:02 +0100 Subject: [PATCH 4/4] Remove tests that fake Gelf classes --- .../DummyClassForClassExistsCheck.php | 16 ---- .../MonologExtensionTest.php | 92 ------------------- 2 files changed, 108 deletions(-) delete mode 100644 Tests/DependencyInjection/Fixtures/DummyClassForClassExistsCheck.php diff --git a/Tests/DependencyInjection/Fixtures/DummyClassForClassExistsCheck.php b/Tests/DependencyInjection/Fixtures/DummyClassForClassExistsCheck.php deleted file mode 100644 index 805cf2bb..00000000 --- a/Tests/DependencyInjection/Fixtures/DummyClassForClassExistsCheck.php +++ /dev/null @@ -1,16 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\MonologBundle\Tests\DependencyInjection\Fixtures; - -class DummyClassForClassExistsCheck -{ -} diff --git a/Tests/DependencyInjection/MonologExtensionTest.php b/Tests/DependencyInjection/MonologExtensionTest.php index bea358cc..a0c82005 100644 --- a/Tests/DependencyInjection/MonologExtensionTest.php +++ b/Tests/DependencyInjection/MonologExtensionTest.php @@ -22,7 +22,6 @@ use Symfony\Bundle\MonologBundle\Tests\DependencyInjection\Fixtures\AsMonologProcessor\FooProcessor; use Symfony\Bundle\MonologBundle\Tests\DependencyInjection\Fixtures\AsMonologProcessor\FooProcessorWithPriority; use Symfony\Bundle\MonologBundle\Tests\DependencyInjection\Fixtures\AsMonologProcessor\RedeclareMethodProcessor; -use Symfony\Bundle\MonologBundle\Tests\DependencyInjection\Fixtures\DummyClassForClassExistsCheck; use Symfony\Bundle\MonologBundle\Tests\DependencyInjection\Fixtures\ServiceWithChannel; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -187,97 +186,6 @@ public function testExceptionWhenUsingGelfWithoutPublisherHostname() $loader->load([['handlers' => ['gelf' => ['type' => 'gelf', 'publisher' => []]]]], $container); } - public function testExceptionWhenUsingLegacyGelfImplementationWithUnsupportedEncoder(): void - { - if (!class_exists('Gelf\MessagePublisher')) { - class_alias(DummyClassForClassExistsCheck::class, 'Gelf\MessagePublisher'); - } - - $container = new ContainerBuilder(); - $loader = new MonologExtension(); - - $this->expectException(InvalidConfigurationException::class); - - $loader->load([['handlers' => ['gelf' => ['type' => 'gelf', 'publisher' => ['hostname' => 'localhost', 'encoder' => 'json']]]]], $container); - } - - /** - * @dataProvider encoderOptionsProvider - */ - public function testLegacyGelfImplementationEncoderOption(array $config): void - { - if (!class_exists('Gelf\MessagePublisher')) { - class_alias(DummyClassForClassExistsCheck::class, 'Gelf\MessagePublisher'); - } - - $container = $this->getContainer($config); - $this->assertTrue($container->hasDefinition('monolog.handler.gelf')); - - $handler = $container->getDefinition('monolog.handler.gelf'); - /** @var Definition $publisher */ - $publisher = $handler->getArguments()[0]; - - $this->assertDICConstructorArguments($publisher, ['localhost', 12201, 1420]); - } - - public function encoderOptionsProvider(): array - { - return [ - [ - [['handlers' => ['gelf' => ['type' => 'gelf', 'publisher' => ['hostname' => 'localhost', 'encoder' => 'compressed_json']]]]], - ], - [ - [['handlers' => ['gelf' => ['type' => 'gelf', 'publisher' => ['hostname' => 'localhost']]]]], - ], - ]; - } - - public function testExceptionWhenUsingGelfWithInvalidEncoder(): void - { - if (!class_exists('Gelf\Transport\UdpTransport')) { - class_alias(DummyClassForClassExistsCheck::class, 'Gelf\Transport\UdpTransport'); - } - - $container = new ContainerBuilder(); - $loader = new MonologExtension(); - - $this->expectException(InvalidConfigurationException::class); - - $loader->load([['handlers' => ['gelf' => ['type' => 'gelf', 'publisher' => ['hostname' => 'localhost', 'encoder' => 'invalid_encoder']]]]], $container); - } - - /** - * @dataProvider gelfEncoderProvider - */ - public function testGelfWithEncoder($encoderValue, $expectedClass): void - { - if (!class_exists('Gelf\Transport\UdpTransport')) { - class_alias(DummyClassForClassExistsCheck::class, 'Gelf\Transport\UdpTransport'); - } - - $container = $this->getContainer([['handlers' => ['gelf' => ['type' => 'gelf', 'publisher' => ['hostname' => 'localhost', 'encoder' => $encoderValue]]]]]); - $this->assertTrue($container->hasDefinition('monolog.handler.gelf')); - - $handler = $container->getDefinition('monolog.handler.gelf'); - /** @var Definition $publisher */ - $publisher = $handler->getArguments()[0]; - /** @var Definition $transport */ - $transport = $publisher->getMethodCalls()[0][1][0]; - $encoder = $transport->getMethodCalls()[0][1][0]; - - $this->assertDICConstructorArguments($transport, ['localhost', 12201, 1420]); - $this->assertDICDefinitionClass($encoder, $expectedClass); - $this->assertDICConstructorArguments($handler, [$publisher, 'DEBUG', true]); - } - - public function gelfEncoderProvider(): array - { - return [ - ['json', 'Gelf\Encoder\JsonEncoder'], - ['compressed_json', 'Gelf\Encoder\CompressedJsonEncoder'], - ]; - } - public function testExceptionWhenUsingServiceWithoutId() { $container = new ContainerBuilder();