diff --git a/composer.json b/composer.json index a07537ba0..ce0bbf139 100644 --- a/composer.json +++ b/composer.json @@ -88,9 +88,9 @@ }, "require-dev": { "ext-grpc": "*", - "grpc/grpc": "^1.30", "bamarni/composer-bin-plugin": "^1.8", "dg/bypass-finals": "^1.4", + "grpc/grpc": "^1.30", "guzzlehttp/guzzle": "^7.4", "guzzlehttp/psr7": "^2.1", "mikey179/vfsstream": "^1.6.11", @@ -193,6 +193,15 @@ ], "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\Instrumentation": [ "OpenTelemetry\\Example\\ExampleInstrumentation" + ], + "OpenTelemetry\\SDK\\Common\\Configuration\\Resolver\\ResolverInterface": [ + "OpenTelemetry\\SDK\\Common\\Configuration\\Resolver\\SdkConfigurationResolver" + ], + "OpenTelemetry\\Config\\SDK\\Configuration\\Environment\\EnvSourceProvider": [ + "OpenTelemetry\\Config\\SDK\\Configuration\\Environment\\Adapter\\SymfonyDotenvProvider", + "OpenTelemetry\\Config\\SDK\\Configuration\\Environment\\Adapter\\VlucasPhpdotenvProvider", + + "OpenTelemetry\\Tests\\Integration\\SDK\\Common\\Configuration\\TestEnvSourceProvider" ] } } diff --git a/deptrac.yaml b/deptrac.yaml index 8afb8a3fa..5e2704cc2 100644 --- a/deptrac.yaml +++ b/deptrac.yaml @@ -117,7 +117,12 @@ deptrac: value: deptrac/polyfills/.* - type: directory value: vendor/symfony/polyfill-php83 - + - name: DotenvProvider + collectors: + - type: className + regex: ^Symfony\\Component\\Dotenv\\* + - type: className + regex: ^Dotenv\\* ruleset: Context: - FFI @@ -134,6 +139,7 @@ deptrac: - Contrib - Extension - Polyfills + - DotenvProvider API: - Context - PsrLog diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 64b8d19b7..1082718c9 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -55,3 +55,15 @@ parameters: message: "#.*expects Google\\\\Protobuf\\\\RepeatedField.*#" paths: - src/Contrib/Otlp + - + message: "#^Call to (static )?method .* on an unknown class .*#" + paths: + - src/Config/SDK/Configuration/Environment/Adapter/ + - + message: "#^Instantiated class .* not found\\.#" + paths: + - src/Config/SDK/Configuration/Environment/Adapter/ + - + message: "#^Caught class .* not found\\.#" + paths: + - src/Config/SDK/Configuration/Environment/Adapter/ diff --git a/src/Config/SDK/Configuration.php b/src/Config/SDK/Configuration.php index c5833a91d..f0d4ba8da 100644 --- a/src/Config/SDK/Configuration.php +++ b/src/Config/SDK/Configuration.php @@ -10,10 +10,12 @@ use OpenTelemetry\API\Configuration\Context; use OpenTelemetry\Config\SDK\ComponentProvider\OpenTelemetrySdk; use OpenTelemetry\Config\SDK\Configuration\ConfigurationFactory; +use OpenTelemetry\Config\SDK\Configuration\Environment\EnvReader; use OpenTelemetry\Config\SDK\Configuration\Environment\EnvSourceReader; use OpenTelemetry\Config\SDK\Configuration\Environment\PhpIniEnvSource; use OpenTelemetry\Config\SDK\Configuration\Environment\ServerEnvSource; use OpenTelemetry\SDK\SdkBuilder; +use WeakMap; final class Configuration { @@ -37,24 +39,28 @@ public static function parseFile( string|array $file, ?string $cacheFile = null, bool $debug = true, + ?EnvReader $envReader = null, ): Configuration { - return new self(self::factory()->parseFile($file, $cacheFile, $debug)); + return new self(self::factory($envReader)->parseFile($file, $cacheFile, $debug)); } /** * @return ConfigurationFactory */ - private static function factory(): ConfigurationFactory + private static function factory(?EnvReader $envReader): ConfigurationFactory { - static $factory; + static $defaultEnvReader; + static $factories = new WeakMap(); - return $factory ??= new ConfigurationFactory( + $envReader ??= $defaultEnvReader ??= new EnvSourceReader([ + new ServerEnvSource(), + new PhpIniEnvSource(), + ]); + + return $factories[$envReader] ??= new ConfigurationFactory( self::loadComponentProviders(), new OpenTelemetrySdk(), - new EnvSourceReader([ - new ServerEnvSource(), - new PhpIniEnvSource(), - ]), + $envReader, ); } diff --git a/src/Config/SDK/Configuration/Environment/Adapter/SymfonyDotenvProvider.php b/src/Config/SDK/Configuration/Environment/Adapter/SymfonyDotenvProvider.php new file mode 100644 index 000000000..fe8690fb1 --- /dev/null +++ b/src/Config/SDK/Configuration/Environment/Adapter/SymfonyDotenvProvider.php @@ -0,0 +1,37 @@ +bootEnv($installPath . '/.env'); + $env = $_SERVER; + } catch (PathException) { + } finally { + [$_SERVER, $_ENV] = $backup; + } + + return new ArrayEnvSource(array_diff_key($env, $_SERVER)); + } +} diff --git a/src/Config/SDK/Configuration/Environment/Adapter/VlucasPhpdotenvProvider.php b/src/Config/SDK/Configuration/Environment/Adapter/VlucasPhpdotenvProvider.php new file mode 100644 index 000000000..72c724830 --- /dev/null +++ b/src/Config/SDK/Configuration/Environment/Adapter/VlucasPhpdotenvProvider.php @@ -0,0 +1,33 @@ +load(); + } catch (InvalidPathException) { + } finally { + [$_SERVER, $_ENV] = $backup; + } + + return new ArrayEnvSource(array_diff_key($env, $_SERVER)); + } +} diff --git a/src/Config/SDK/Configuration/Environment/EnvSourceProvider.php b/src/Config/SDK/Configuration/Environment/EnvSourceProvider.php new file mode 100644 index 000000000..f1a7846c6 --- /dev/null +++ b/src/Config/SDK/Configuration/Environment/EnvSourceProvider.php @@ -0,0 +1,10 @@ +env instanceof EnvSource) { + $this->env = ($this->env)(); + } + + return $this->env->readRaw($name); + } +} diff --git a/src/Config/SDK/Instrumentation.php b/src/Config/SDK/Instrumentation.php index ca12c3191..0410a9d76 100644 --- a/src/Config/SDK/Instrumentation.php +++ b/src/Config/SDK/Instrumentation.php @@ -11,9 +11,11 @@ use OpenTelemetry\API\Instrumentation\AutoInstrumentation\ConfigurationRegistry; use OpenTelemetry\Config\SDK\ComponentProvider\InstrumentationConfigurationRegistry; use OpenTelemetry\Config\SDK\Configuration\ConfigurationFactory; +use OpenTelemetry\Config\SDK\Configuration\Environment\EnvReader; use OpenTelemetry\Config\SDK\Configuration\Environment\EnvSourceReader; use OpenTelemetry\Config\SDK\Configuration\Environment\PhpIniEnvSource; use OpenTelemetry\Config\SDK\Configuration\Environment\ServerEnvSource; +use WeakMap; final class Instrumentation { @@ -39,24 +41,28 @@ public static function parseFile( string|array $file, ?string $cacheFile = null, bool $debug = true, + ?EnvReader $envReader = null, ): Instrumentation { - return new self(self::factory()->parseFile($file, $cacheFile, $debug)); + return new self(self::factory($envReader)->parseFile($file, $cacheFile, $debug)); } /** * @return ConfigurationFactory */ - private static function factory(): ConfigurationFactory + private static function factory(?EnvReader $envReader): ConfigurationFactory { - static $factory; + static $defaultEnvReader; + static $factories = new WeakMap(); - return $factory ??= new ConfigurationFactory( + $envReader ??= $defaultEnvReader ??= new EnvSourceReader([ + new ServerEnvSource(), + new PhpIniEnvSource(), + ]); + + return $factories[$envReader] ??= new ConfigurationFactory( self::loadComponentProviders(), new InstrumentationConfigurationRegistry(), - new EnvSourceReader([ - new ServerEnvSource(), - new PhpIniEnvSource(), - ]), + $envReader, ); } diff --git a/src/Config/SDK/composer.json b/src/Config/SDK/composer.json index f45736b5b..caadbf896 100644 --- a/src/Config/SDK/composer.json +++ b/src/Config/SDK/composer.json @@ -22,7 +22,7 @@ "open-telemetry/context": "^1.0", "open-telemetry/sdk": "^1.0", "symfony/config": "^5.4 || ^6.4 || ^7.0", - "tbachert/spi": "^1.0.1" + "tbachert/spi": "^1.0.5" }, "autoload": { "psr-4": { @@ -82,6 +82,10 @@ "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Instrumentation\\General\\HttpConfigProvider", "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Instrumentation\\General\\PeerConfigProvider" + ], + "OpenTelemetry\\Config\\SDK\\Configuration\\Environment\\EnvSourceProvider": [ + "OpenTelemetry\\Config\\SDK\\Configuration\\Environment\\Adapter\\SymfonyDotenvProvider", + "OpenTelemetry\\Config\\SDK\\Configuration\\Environment\\Adapter\\VlucasPhpdotenvProvider" ] } } diff --git a/src/SDK/Common/Configuration/Resolver/CompositeResolver.php b/src/SDK/Common/Configuration/Resolver/CompositeResolver.php index 1054b6239..a77a34c50 100644 --- a/src/SDK/Common/Configuration/Resolver/CompositeResolver.php +++ b/src/SDK/Common/Configuration/Resolver/CompositeResolver.php @@ -4,6 +4,7 @@ namespace OpenTelemetry\SDK\Common\Configuration\Resolver; +use Nevay\SPI\ServiceLoader; use OpenTelemetry\SDK\Common\Configuration\Configuration; /** @@ -18,6 +19,7 @@ public static function instance(): self { static $instance; $instance ??= new self([ + ...ServiceLoader::load(ResolverInterface::class), new EnvironmentResolver(), new PhpIniResolver(), ]); diff --git a/src/SDK/Common/Configuration/Resolver/EnvironmentResolver.php b/src/SDK/Common/Configuration/Resolver/EnvironmentResolver.php index 91f83879a..950cfbd74 100644 --- a/src/SDK/Common/Configuration/Resolver/EnvironmentResolver.php +++ b/src/SDK/Common/Configuration/Resolver/EnvironmentResolver.php @@ -25,12 +25,7 @@ public function hasVariable(string $variableName): bool return !Configuration::isEmpty($env); } - /** - * @psalm-suppress InvalidReturnStatement - * @psalm-suppress InvalidReturnType - */ - #[\Override] - public function retrieveValue(string $variableName) + public function retrieveValue(string $variableName): mixed { $value = getenv($variableName); if ($value === false) { diff --git a/src/SDK/Common/Configuration/Resolver/PhpIniResolver.php b/src/SDK/Common/Configuration/Resolver/PhpIniResolver.php index be171f7e0..f0e6a596e 100644 --- a/src/SDK/Common/Configuration/Resolver/PhpIniResolver.php +++ b/src/SDK/Common/Configuration/Resolver/PhpIniResolver.php @@ -17,7 +17,7 @@ public function __construct(private readonly PhpIniAccessor $accessor = new PhpI } #[\Override] - public function retrieveValue(string $variableName) + public function retrieveValue(string $variableName): mixed { $value = $this->accessor->get($variableName) ?: ''; if (is_array($value)) { @@ -27,7 +27,6 @@ public function retrieveValue(string $variableName) return $value; } - #[\Override] public function hasVariable(string $variableName): bool { $value = $this->accessor->get($variableName); diff --git a/src/SDK/Common/Configuration/Resolver/ResolverInterface.php b/src/SDK/Common/Configuration/Resolver/ResolverInterface.php index 4e88f3ff6..586c8c4c0 100644 --- a/src/SDK/Common/Configuration/Resolver/ResolverInterface.php +++ b/src/SDK/Common/Configuration/Resolver/ResolverInterface.php @@ -6,10 +6,7 @@ interface ResolverInterface { - /** - * @return mixed - */ - public function retrieveValue(string $variableName); + public function retrieveValue(string $variableName): mixed; public function hasVariable(string $variableName): bool; } diff --git a/src/SDK/Common/Configuration/Resolver/SdkConfigurationResolver.php b/src/SDK/Common/Configuration/Resolver/SdkConfigurationResolver.php new file mode 100644 index 000000000..10079df38 --- /dev/null +++ b/src/SDK/Common/Configuration/Resolver/SdkConfigurationResolver.php @@ -0,0 +1,48 @@ +getEnvSource(...)); + } + + $envSources[] = new ServerEnvSource(); + $envSources[] = new PhpIniEnvSource(); + + $this->reader = new EnvSourceReader($envSources); + } + + public function retrieveValue(string $variableName): mixed + { + return $this->reader->read($variableName); + } + + public function hasVariable(string $variableName): bool + { + return !Configuration::isEmpty($this->reader->read($variableName)); + } +} diff --git a/src/SDK/composer.json b/src/SDK/composer.json index ea21aee2e..aeffbb3ee 100644 --- a/src/SDK/composer.json +++ b/src/SDK/composer.json @@ -32,7 +32,7 @@ "ramsey/uuid": "^3.0 || ^4.0", "symfony/polyfill-mbstring": "^1.23", "symfony/polyfill-php82": "^1.26", - "tbachert/spi": "^1.0.1" + "tbachert/spi": "^1.0.5" }, "autoload": { "psr-4": { @@ -64,6 +64,9 @@ "OpenTelemetry\\API\\Configuration\\ConfigEnv\\EnvComponentLoader": [ "OpenTelemetry\\API\\Instrumentation\\Configuration\\General\\ConfigEnv\\EnvComponentLoaderHttpConfig", "OpenTelemetry\\API\\Instrumentation\\Configuration\\General\\ConfigEnv\\EnvComponentLoaderPeerConfig" + ], + "OpenTelemetry\\SDK\\Common\\Configuration\\Resolver\\ResolverInterface": [ + "OpenTelemetry\\SDK\\Common\\Configuration\\Resolver\\SdkConfigurationResolver" ] } } diff --git a/tests/Integration/SDK/Common/Configuration/TestEnvSourceProvider.php b/tests/Integration/SDK/Common/Configuration/TestEnvSourceProvider.php new file mode 100644 index 000000000..a3a3b814e --- /dev/null +++ b/tests/Integration/SDK/Common/Configuration/TestEnvSourceProvider.php @@ -0,0 +1,20 @@ + 'from-test', + 'CONFIG_SOURCE_TEST_ONLY' => 'from-test', + ]); + } +} diff --git a/tests/Integration/SDK/Common/Configuration/test_configuration_resolves_with_registered_providers.phpt b/tests/Integration/SDK/Common/Configuration/test_configuration_resolves_with_registered_providers.phpt new file mode 100644 index 000000000..40595c883 --- /dev/null +++ b/tests/Integration/SDK/Common/Configuration/test_configuration_resolves_with_registered_providers.phpt @@ -0,0 +1,28 @@ +--TEST-- +SDK environment configuration test with SPI. +--INI-- +CONFIG_SOURCE=from-ini +CONFIG_SOURCE_INI_ONLY=from-ini +--ENV-- +CONFIG_SOURCE=from-env +CONFIG_SOURCE_ENV_ONLY=from-env +--FILE-- + +--EXPECTF-- +from-test +from-test +from-env +from-ini