From 66be5161cd63e8d3300de54e4c46c81e3704a379 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?U=C5=82adzimir=20Tsykun?= Date: Sun, 7 Jan 2024 17:22:40 +0100 Subject: [PATCH 1/3] Allow to use multiply DD clients with DSN config --- src/Client/DatadogDns.php | 100 +++++++++++++++++- src/Client/DatadogFactory.php | 22 ++++ .../CompilerPass/SqlLoggerPass.php | 3 +- src/DependencyInjection/Configuration.php | 32 ++++++ .../OkvpnDatadogExtension.php | 28 ++++- src/Resources/config/services.yml | 8 +- 6 files changed, 187 insertions(+), 6 deletions(-) diff --git a/src/Client/DatadogDns.php b/src/Client/DatadogDns.php index f53611d..ffc6f68 100644 --- a/src/Client/DatadogDns.php +++ b/src/Client/DatadogDns.php @@ -4,10 +4,106 @@ namespace Okvpn\Bundle\DatadogBundle\Client; -class DatadogDns +final class DatadogDns { - public static function parser(string $dsn): array + private string $originalDsn; + private string $scheme; + private string $host; + private int $port; + private string $namespace; + private array $tags = []; + + public function __construct(string $dsn) + { + $this->parser($dsn); + } + + public static function fromString(string $dsn): self + { + return new DatadogDns($dsn); + } + + private function parser(string $dsn): void + { + $this->originalDsn = $dsn; + + if (false === $dsn = parse_url($dsn)) { + throw new \InvalidArgumentException('The datadog DSN is invalid.'); + } + + if (!isset($dsn['scheme'])) { + throw new \InvalidArgumentException('The datadog DSN must contain a scheme.'); + } + + $this->scheme = $dsn['scheme']; + $this->host = $dsn['host'] ?? '127.0.0.1'; + $this->port = $dsn['port'] ?? 8125; + $this->namespace = str_replace('/', '', $dsn['path'] ?? 'app'); + + if (isset($dsn['query'])) { + parse_str($dsn['query'], $query); + if (isset($query['tags'])) { + $this->tags = explode(',', $query['tags']); + } + } + } + + /** + * @return string + */ + public function getOriginalDsn(): string { + return $this->originalDsn; + } + /** + * @return string + */ + public function getScheme(): string + { + return $this->scheme; + } + + /** + * @return string + */ + public function getHost(): string + { + return $this->host; + } + + /** + * @return int + */ + public function getPort(): int + { + return $this->port; + } + + /** + * @return string + */ + public function getNamespace(): string + { + return $this->namespace; + } + + /** + * @return array + */ + public function getTags(): array + { + return $this->tags; + } + + public function toArray(): array + { + return [ + 'scheme' => $this->getScheme(), + 'namespace' => $this->getNamespace(), + 'tags' => $this->getTags(), + 'host' => $this->getHost(), + 'port' => $this->getPort(), + ]; } } diff --git a/src/Client/DatadogFactory.php b/src/Client/DatadogFactory.php index b05aa50..7555d93 100644 --- a/src/Client/DatadogFactory.php +++ b/src/Client/DatadogFactory.php @@ -6,10 +6,32 @@ class DatadogFactory implements DatadogFactoryInterface { + protected static $clientFactories = [ + 'null' => NullDatadogClient::class, + 'mock' => MockClient::class, + 'datadog' => DatadogClient::class, + ]; + /** * {@inheritdoc} */ public function createClient(array $options): DogStatsInterface { + if (isset($options['dsn'])) { + $dsn = DatadogDns::fromString($options['dsn']); + $options = $dsn->toArray() + $options; + } + + $scheme = $options['scheme'] ?? 'datadog'; + if (!static::$clientFactories[$scheme]) { + throw new \InvalidArgumentException('The datadog DSN scheme "%s" does not exists. Allowed "%s"', $scheme, implode(",", static::$clientFactories)); + } + + return new static::$clientFactories[$scheme]($options); + } + + public static function setClientFactory(string $alias, string $className): void + { + static::$clientFactories[$alias] = $className; } } diff --git a/src/DependencyInjection/CompilerPass/SqlLoggerPass.php b/src/DependencyInjection/CompilerPass/SqlLoggerPass.php index 2bba0da..e801db4 100644 --- a/src/DependencyInjection/CompilerPass/SqlLoggerPass.php +++ b/src/DependencyInjection/CompilerPass/SqlLoggerPass.php @@ -7,7 +7,6 @@ use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\DefinitionDecorator; use Symfony\Component\DependencyInjection\Reference; class SqlLoggerPass implements CompilerPassInterface @@ -34,7 +33,7 @@ public function process(ContainerBuilder $container): void continue; } $loggerId = 'okvpn_datadog.sql_logger.' . $name; - $container->setDefinition($loggerId, class_exists(ChildDefinition::class) ? new ChildDefinition('okvpn_datadog.logger.sql') : new DefinitionDecorator('okvpn_datadog.logger.sql')); + $container->setDefinition($loggerId, new ChildDefinition('okvpn_datadog.logger.sql')); $configuration = $container->getDefinition($configuration); if ($configuration->hasMethodCall('setSQLLogger')) { $chainLoggerId = 'doctrine.dbal.logger.chain.' . $name; diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 266daad..5a9885c 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -4,6 +4,7 @@ namespace Okvpn\Bundle\DatadogBundle\DependencyInjection; +use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; @@ -74,6 +75,37 @@ public function getConfigTreeBuilder(): TreeBuilder ->defaultFalse() ->end(); + $this->addClientsSection($rootNode); + return $treeBuilder; } + + private function addClientsSection(ArrayNodeDefinition $rootNode): void + { + $rootNode->children() + ->arrayNode('clients') + ->useAttributeAsKey('alias', false) + ->beforeNormalization() + ->always() + ->then(static function ($v) { + if (is_iterable($v)) { + foreach ($v as $name => $client) { + if (is_string($client)) { + $client = ['dsn' => $client]; + } + $client['alias'] = $name; + $v[$name] = $client; + } + } + return $v; + }) + ->end() + ->arrayPrototype() + ->children() + ->scalarNode('alias')->isRequired()->end() + ->scalarNode('dsn')->isRequired()->end() + ->end() + ->end() + ->end(); + } } diff --git a/src/DependencyInjection/OkvpnDatadogExtension.php b/src/DependencyInjection/OkvpnDatadogExtension.php index 725f57b..b45d95a 100644 --- a/src/DependencyInjection/OkvpnDatadogExtension.php +++ b/src/DependencyInjection/OkvpnDatadogExtension.php @@ -4,8 +4,12 @@ namespace Okvpn\Bundle\DatadogBundle\DependencyInjection; +use Okvpn\Bundle\DatadogBundle\Client\DatadogFactoryInterface; +use Okvpn\Bundle\DatadogBundle\Client\DogStatsInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\DependencyInjection\Loader; @@ -28,11 +32,24 @@ public function load(array $configs, ContainerBuilder $container): void ->replaceArgument(6, $config['dedup_keep_time']); $container->getDefinition('okvpn_datadog.logger.artifact_storage') ->replaceArgument(0, $config['artifacts_path'] ?: $container->getParameter('kernel.logs_dir')); + $container->getDefinition('okvpn_datadog.client') ->replaceArgument(0, $config); + if (isset($config['clients'])) { + foreach ($config['clients'] as $client) { + $this->loadClient($container, $client); + } + + if (isset($config['clients']['default'])) { + $container->removeDefinition('okvpn_datadog.client'); + $container->setAlias('okvpn_datadog.client', 'okvpn_datadog.client.default')->setPublic(true); + $container->setAlias(DogStatsInterface::class, 'okvpn_datadog.client.default')->setPublic(true); + } + } + if (true === $config['profiling']) { - if (true === $config['doctrine']) { + if (true === $config['doctrine'] && class_exists('Doctrine\DBAL\Logging\SQLLogger')) { $loader->load('sqllogger.yml'); } $loader->load('listener.yml'); @@ -42,6 +59,15 @@ public function load(array $configs, ContainerBuilder $container): void $container->setParameter('okvpn_datadog.profiling', $config['profiling']); } + protected function loadClient(ContainerBuilder $container, array $client) + { + $ddDef = new Definition(DogStatsInterface::class, [$client]); + $ddDef->setFactory([new Reference(DatadogFactoryInterface::class), 'createClient']); + + $container->setDefinition($id = sprintf('okvpn_datadog.client.%s', $client['alias']), $ddDef); + $container->registerAliasForArgument($id, DogStatsInterface::class, $client['alias']); + } + protected function defaultHandlerExceptions(array $config): array { $config = $config['handle_exceptions'] ?? []; diff --git a/src/Resources/config/services.yml b/src/Resources/config/services.yml index 63704e1..20f33e1 100644 --- a/src/Resources/config/services.yml +++ b/src/Resources/config/services.yml @@ -17,9 +17,15 @@ services: - null - null + Okvpn\Bundle\DatadogBundle\Client\DatadogFactory: ~ + + Okvpn\Bundle\DatadogBundle\Client\DatadogFactoryInterface: + alias: Okvpn\Bundle\DatadogBundle\Client\DatadogFactory + okvpn_datadog.client: class: Okvpn\Bundle\DatadogBundle\Client\DatadogClient - arguments: [~] + factory: ['@Okvpn\Bundle\DatadogBundle\Client\DatadogFactoryInterface', 'createClient'] + arguments: [[]] public: true Okvpn\Bundle\DatadogBundle\Client\DogStatsInterface: From 2728c20fcd85a46415cae50ae986db9468777060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?U=C5=82adzimir=20Tsykun?= Date: Sun, 7 Jan 2024 17:33:23 +0100 Subject: [PATCH 2/3] Example usage multiply DD clients --- README.md | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index c5342aa..0125b3c 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,34 @@ return [ ``` okvpn_datadog: - profiling: true - namespace: app # You app namespace for custome metric app.*, see https://docs.datadoghq.com/developers/metrics/#naming-metrics + clients: + default: 'datadog://127.0.0.1/namespase' + + ## More clients + i2pd_client: 'datadog://10.10.1.1:8125/app?tags=tg1,tg2' + 'null': null://null + dns: '%env(DD_CLIENT)%' +``` + +Where env var looks like: +``` +DD_CLIENT=datadog://127.0.0.1:8125/app1?tags=tg1,tg2 +``` + +Access to client via DIC: + +```php +class FeedController extends Controller +{ + // Inject via arg for Symfony 4+ + #[Route(path: '/', name: 'feeds')] + public function feedsAction(DogStatsInterface $dogStats, DogStatsInterface $i2pdClient): Response + { + $dogStats->decrement('feed'); + + return $this->render('feed/feeds.html.twig'); + } +} ``` ## Custom metrics that provided by OkvpnDatadogBundle @@ -59,15 +85,7 @@ Where `app` metrics namespace. ## Configuration -And create config `config/packages/datadog.yml` with - -``` -okvpn_datadog: - namespace: app - port: 8125 -``` - -A more complex setup look like this `config.yml`: +A more complex setup look like this `config/packages/ddog.yml`: ``` From 21054bbfd0d7bf4e17c1fd666e4b021a415be110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?U=C5=82adzimir=20Tsykun?= Date: Sun, 7 Jan 2024 17:38:08 +0100 Subject: [PATCH 3/3] Allow to use multiply DD clients with DSN config --- README.md | 1 + src/DependencyInjection/OkvpnDatadogExtension.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0125b3c..4486efd 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ okvpn_datadog: ## More clients i2pd_client: 'datadog://10.10.1.1:8125/app?tags=tg1,tg2' 'null': null://null + mock: mock://mock dns: '%env(DD_CLIENT)%' ``` diff --git a/src/DependencyInjection/OkvpnDatadogExtension.php b/src/DependencyInjection/OkvpnDatadogExtension.php index b45d95a..1fb4561 100644 --- a/src/DependencyInjection/OkvpnDatadogExtension.php +++ b/src/DependencyInjection/OkvpnDatadogExtension.php @@ -49,7 +49,7 @@ public function load(array $configs, ContainerBuilder $container): void } if (true === $config['profiling']) { - if (true === $config['doctrine'] && class_exists('Doctrine\DBAL\Logging\SQLLogger')) { + if (true === $config['doctrine']) { $loader->load('sqllogger.yml'); } $loader->load('listener.yml');