From fba862afadc8ee6160b7a378ffbd00704b9e4bf0 Mon Sep 17 00:00:00 2001 From: Tibor Rac Date: Sun, 3 Nov 2024 11:39:08 +0100 Subject: [PATCH] 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();