diff --git a/Makefile b/Makefile index 1f2184851..7e9d2f553 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,8 @@ update: ## Update dependencies $(DC_RUN_PHP) env XDEBUG_MODE=off composer update update-lowest: ## Update dependencies to lowest supported versions $(DC_RUN_PHP) env XDEBUG_MODE=off composer update --prefer-lowest +dump: ## Dump autoload + $(DC_RUN_PHP) env XDEBUG_MODE=off composer dump-autoload test: test-unit test-integration ## Run unit and integration tests test-unit: ## Run unit tests $(DC_RUN_PHP) env XDEBUG_MODE=coverage vendor/bin/phpunit --testsuite unit --colors=always diff --git a/composer.json b/composer.json index ef494a888..026a57a91 100644 --- a/composer.json +++ b/composer.json @@ -44,7 +44,8 @@ "open-telemetry/exporter-otlp": "1.0.x-dev", "open-telemetry/exporter-zipkin": "1.0.x-dev", "open-telemetry/extension-propagator-b3": "1.0.x-dev", - "open-telemetry/extension-propagator-jaeger": "0.0.2", + "open-telemetry/extension-propagator-cloudtrace": "0.0.4", + "open-telemetry/extension-propagator-jaeger": "0.0.3", "open-telemetry/gen-otlp-protobuf": "1.0.x-dev", "open-telemetry/sdk": "1.0.x-dev", "open-telemetry/sdk-configuration": "0.1.x-dev", @@ -59,17 +60,13 @@ }, "files": [ "src/Context/fiber/initialize_fiber_handler.php", + "src/API/_register.php", "src/API/Trace/functions.php", + "src/Config/SDK/_register.php", "src/Contrib/Otlp/_register.php", "src/Contrib/Grpc/_register.php", "src/Contrib/Zipkin/_register.php", - "src/Extension/Propagator/B3/_register.php", - "src/Extension/Propagator/CloudTrace/_register.php", - "src/Extension/Propagator/Jaeger/_register.php", - "src/SDK/Logs/Exporter/_register.php", - "src/SDK/Metrics/MetricExporter/_register.php", - "src/SDK/Propagation/_register.php", - "src/SDK/Trace/SpanExporter/_register.php", + "src/SDK/_register.php", "src/SDK/Common/Dev/Compatibility/_load.php", "src/SDK/Common/Util/functions.php", "src/SDK/_autoload.php" @@ -80,7 +77,11 @@ "OpenTelemetry\\Tests\\": "tests/", "OpenTelemetry\\Example\\": "examples/src/", "ExampleSDK\\ComponentProvider\\": "tests/Unit/Config/SDK/Configuration/ExampleSdk/" - } + }, + "files": [ + "examples/src/_register.php", + "tests/_register.php" + ] }, "require-dev": { "ext-grpc": "*", @@ -123,52 +124,18 @@ "target-directory": "vendor-bin", "forward-command": true }, - "spi": { - "OpenTelemetry\\Config\\SDK\\Configuration\\ComponentProvider": [ - "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Propagator\\TextMapPropagatorB3", - "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Propagator\\TextMapPropagatorB3Multi", - "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Propagator\\TextMapPropagatorBaggage", - "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Propagator\\TextMapPropagatorComposite", - "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Propagator\\TextMapPropagatorJaeger", - "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Propagator\\TextMapPropagatorTraceContext", - - "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Trace\\SamplerAlwaysOff", - "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Trace\\SamplerAlwaysOn", - "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Trace\\SamplerParentBased", - "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Trace\\SamplerTraceIdRatioBased", - "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Trace\\SpanExporterConsole", - "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Trace\\SpanExporterOtlp", - "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Trace\\SpanExporterZipkin", - "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Trace\\SpanProcessorBatch", - "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Trace\\SpanProcessorSimple", - - "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Metrics\\AggregationResolverDefault", - "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Metrics\\MetricExporterConsole", - "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Metrics\\MetricExporterOtlp", - "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Metrics\\MetricReaderPeriodic", - - "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Logs\\LogRecordExporterConsole", - "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Logs\\LogRecordExporterOtlp", - "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Logs\\LogRecordProcessorBatch", - "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Logs\\LogRecordProcessorSimple", - - "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Instrumentation\\General\\HttpConfigProvider", - "OpenTelemetry\\Config\\SDK\\ComponentProvider\\Instrumentation\\General\\PeerConfigProvider", - - "OpenTelemetry\\Example\\ExampleConfigProvider", - - "OpenTelemetry\\Tests\\Integration\\Config\\ComponentProvider\\Metrics\\AggregationResolverExplicitBucketHistogram", - "OpenTelemetry\\Tests\\Integration\\Config\\ComponentProvider\\Metrics\\MetricExporterPrometheus", - "OpenTelemetry\\Tests\\Integration\\Config\\ComponentProvider\\Metrics\\MetricReaderPull", - "OpenTelemetry\\Tests\\Integration\\Config\\ComponentProvider\\Propagator\\TextMapPropagatorXray", - "OpenTelemetry\\Tests\\Integration\\Config\\ComponentProvider\\Propagator\\TextMapPropagatorOtTrace" - ], - "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [ - "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager" + "spi-config": { + "autoload-files": [ + "examples/src/_register.php", + "src/API/_register.php", + "src/Config/SDK/_register.php", + "src/Contrib/Otlp/_register.php", + "src/Contrib/Grpc/_register.php", + "src/Contrib/Zipkin/_register.php", + "src/SDK/_register.php", + "tests/_register.php" ], - "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\Instrumentation": [ - "OpenTelemetry\\Example\\ExampleInstrumentation" - ] + "prune-autoload-files": true } } } diff --git a/deptrac.baseline.yaml b/deptrac.baseline.yaml index cacb15fb7..ebcd48726 100644 --- a/deptrac.baseline.yaml +++ b/deptrac.baseline.yaml @@ -1,8 +1,2 @@ deptrac: skip_violations: - /src/Extension/Propagator/B3/_register.php: - - OpenTelemetry\SDK\Registry - /src/Extension/Propagator/CloudTrace/_register.php: - - OpenTelemetry\SDK\Registry - /src/Extension/Propagator/Jaeger/_register.php: - - OpenTelemetry\SDK\Registry diff --git a/deptrac.yaml b/deptrac.yaml index 3cca3903c..f32203c5a 100644 --- a/deptrac.yaml +++ b/deptrac.yaml @@ -131,6 +131,7 @@ deptrac: SDK: - +API - ConfigSDK + - Extension - SemConv - PsrHttp - HttpPlug diff --git a/docs/upgrading.md b/docs/upgrading.md new file mode 100644 index 000000000..c25931b2f --- /dev/null +++ b/docs/upgrading.md @@ -0,0 +1,27 @@ +# Upgrading notes for major versions + +## 1.x -> 2.x + +### SDK + +#### SDK\Registry removed +`SDK\Registry` has been removed, and the technique of registering components (eg propagators, transports, +auto-instrumentations) has been replaced with [Nevay/SPI](https://github.com/Nevay/spi/) ServiceLoader. + +The ServiceLoader should be configured through existing `_register.php` files, which should *only* contain calls to +SPI's `ServiceLoader::register()` method. + +SPI has a composer plugin which will scan for `ServiceLoader::register()` calls in `autoload.files` and generate a +`GeneratedServiceProviderData.php` file in `vendor/composer/`. The plugin will then remove those `autoload.files` entries +from composer's generated output to avoid double-loading. +Pre-generating the services in this way avoids a race-condition in 1.x where composer's `autoload.files` are executed in an +undefined order, and services may not be registered in time for the SDK to use them. + +For SPI to work correctly, the composer plugin _should_ be allowed to run. If the plugin is not allowed to run, then +services will still register at runtime, however this might still suffer from the same race-condition as `1.x`. + +#### FactoryInterfaces updated +Various component factory interfaces (eg `TextMapPropagatorFactoryInterface`, `TransportFactoryInterface`) have been +updated to include `priority()` and `type()` methods. These are used in conjunction with SPI ServiceLoader to associate +a type (eg `otlp`) with a factory, and to allow SDK-provided factories to be replaced by user-provided factories (by +providing a higher priority for the same type). diff --git a/examples/autoload_sdk_with_custom_transport.php b/examples/autoload_sdk_with_custom_transport.php index 28d742caf..e53969f33 100644 --- a/examples/autoload_sdk_with_custom_transport.php +++ b/examples/autoload_sdk_with_custom_transport.php @@ -4,6 +4,9 @@ namespace OpenTelemetry\Example; +use Nevay\SPI\ServiceLoader; +use OpenTelemetry\SDK\Common\Export\TransportFactoryInterface; + putenv('OTEL_PHP_AUTOLOAD_ENABLED=true'); putenv('OTEL_TRACES_EXPORTER=otlp'); putenv('OTEL_EXPORTER_OTLP_PROTOCOL=grpc'); @@ -45,9 +48,19 @@ public function forceFlush(?\OpenTelemetry\SDK\Common\Future\CancellationInterfa } }; } + + public function type(): string + { + return 'grpc'; + } + + public function priority(): int + { + return 100; + } }; -\OpenTelemetry\SDK\Registry::registerTransportFactory('grpc', $factory, true); +ServiceLoader::register(TransportFactoryInterface::class, $factory::class); $instrumentation = new \OpenTelemetry\API\Instrumentation\CachedInstrumentation('demo'); diff --git a/examples/logs/exporters/otlp_http.php b/examples/logs/exporters/otlp_http.php index 27af3a6ca..870b967d5 100644 --- a/examples/logs/exporters/otlp_http.php +++ b/examples/logs/exporters/otlp_http.php @@ -7,7 +7,7 @@ use OpenTelemetry\API\Logs\LogRecord; use OpenTelemetry\API\Logs\Severity; use OpenTelemetry\Contrib\Otlp\LogsExporter; -use OpenTelemetry\Contrib\Otlp\OtlpHttpTransportFactory; +use OpenTelemetry\SDK\Common\Export\Http\PsrTransportFactory; use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactory; use OpenTelemetry\SDK\Logs\EventLoggerProvider; use OpenTelemetry\SDK\Logs\LoggerProvider; @@ -16,7 +16,7 @@ require __DIR__ . '/../../../vendor/autoload.php'; -$transport = (new OtlpHttpTransportFactory())->create('http://collector:4318/v1/logs', 'application/json'); +$transport = (new PsrTransportFactory())->create('http://collector:4318/v1/logs', 'application/json'); $exporter = new LogsExporter($transport); $loggerProvider = new LoggerProvider( diff --git a/examples/src/ExampleConfig.php b/examples/src/ExampleConfig.php index e2385bd4f..8afba601d 100644 --- a/examples/src/ExampleConfig.php +++ b/examples/src/ExampleConfig.php @@ -13,4 +13,9 @@ public function __construct( public readonly bool $enabled = true, ) { } + + public static function default(): self + { + return new self('example'); + } } diff --git a/examples/src/ExampleInstrumentation.php b/examples/src/ExampleInstrumentation.php index d1ccf73a3..bef052547 100644 --- a/examples/src/ExampleInstrumentation.php +++ b/examples/src/ExampleInstrumentation.php @@ -4,7 +4,6 @@ namespace OpenTelemetry\Example; -use Exception; use OpenTelemetry\API\Configuration\ConfigProperties; use OpenTelemetry\API\Instrumentation\AutoInstrumentation\Context as InstrumentationContext; use OpenTelemetry\API\Instrumentation\AutoInstrumentation\HookManagerInterface; @@ -16,7 +15,7 @@ final class ExampleInstrumentation implements Instrumentation { public function register(HookManagerInterface $hookManager, ConfigProperties $configuration, InstrumentationContext $context): void { - $config = $configuration->get(ExampleConfig::class) ?? throw new Exception('example instrumentation must be configured'); + $config = $configuration->get(ExampleConfig::class) ?? ExampleConfig::default(); if (!$config->enabled) { return; } diff --git a/examples/src/TestResourceDetector.php b/examples/src/TestResourceDetector.php new file mode 100644 index 000000000..3dbf1ee3a --- /dev/null +++ b/examples/src/TestResourceDetector.php @@ -0,0 +1,22 @@ + 'test-value', + ]; + + return ResourceInfo::create(Attributes::create($attributes), ResourceAttributes::SCHEMA_URL); + } +} diff --git a/examples/src/TestResourceDetectorFactory.php b/examples/src/TestResourceDetectorFactory.php new file mode 100644 index 000000000..f1bac2d30 --- /dev/null +++ b/examples/src/TestResourceDetectorFactory.php @@ -0,0 +1,26 @@ +create('http://collector:4318/v1/traces', 'application/x-protobuf'); +$transport = (new PsrTransportFactory())->create('http://collector:4318/v1/traces', 'application/x-protobuf'); $exporter = new SpanExporter($transport); echo 'Starting OTLP example'; diff --git a/examples/traces/exporters/otlp_http_json.php b/examples/traces/exporters/otlp_http_json.php index c89c267a6..e33bb78eb 100644 --- a/examples/traces/exporters/otlp_http_json.php +++ b/examples/traces/exporters/otlp_http_json.php @@ -7,12 +7,12 @@ require __DIR__ . '/../../../vendor/autoload.php'; use OpenTelemetry\Contrib\Otlp\ContentTypes; -use OpenTelemetry\Contrib\Otlp\OtlpHttpTransportFactory; use OpenTelemetry\Contrib\Otlp\SpanExporter; +use OpenTelemetry\SDK\Common\Export\Http\PsrTransportFactory; use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor; use OpenTelemetry\SDK\Trace\TracerProvider; -$transport = (new OtlpHttpTransportFactory())->create('http://collector:4318/v1/traces', ContentTypes::JSON); +$transport = (new PsrTransportFactory())->create('http://collector:4318/v1/traces', ContentTypes::JSON); $exporter = new SpanExporter($transport); echo 'Starting OTLP+json example'; diff --git a/examples/traces/features/auto_root_span.php b/examples/traces/features/auto_root_span.php index 5ac62379b..3051a2636 100644 --- a/examples/traces/features/auto_root_span.php +++ b/examples/traces/features/auto_root_span.php @@ -11,8 +11,11 @@ putenv('OTEL_TRACES_EXPORTER=console'); putenv('OTEL_METRICS_EXPORTER=none'); putenv('OTEL_LOGS_EXPORTER=console'); +putenv('OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318'); +putenv('OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf'); putenv('OTEL_PROPAGATORS=tracecontext'); putenv('OTEL_PHP_EXPERIMENTAL_AUTO_ROOT_SPAN=true'); +putenv('OTEL_PHP_DETECTORS=sdk,test'); //Usage: php -S localhost:8080 examples/traces/features/auto_root_span.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 807247557..da5316b39 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -50,4 +50,4 @@ parameters: - message: "#.*return with type T is not subtype.*#" paths: - - src/SDK/Common/InstrumentationScope \ No newline at end of file + - src/SDK/Common/InstrumentationScope diff --git a/src/API/_register.php b/src/API/_register.php new file mode 100644 index 000000000..aefc26f03 --- /dev/null +++ b/src/API/_register.php @@ -0,0 +1,9 @@ +create( + return new ConsoleExporter(Loader::transportFactory('stream')->create( endpoint: 'php://stdout', contentType: 'application/json', )); diff --git a/src/Config/SDK/ComponentProvider/Logs/LogRecordExporterOtlp.php b/src/Config/SDK/ComponentProvider/Logs/LogRecordExporterOtlp.php index e580e7782..baec95070 100644 --- a/src/Config/SDK/ComponentProvider/Logs/LogRecordExporterOtlp.php +++ b/src/Config/SDK/ComponentProvider/Logs/LogRecordExporterOtlp.php @@ -14,8 +14,8 @@ use OpenTelemetry\Contrib\Otlp\OtlpUtil; use OpenTelemetry\Contrib\Otlp\Protocols; use OpenTelemetry\SDK\Common\Configuration\Parser\MapParser; +use OpenTelemetry\SDK\Common\Services\Loader; use OpenTelemetry\SDK\Logs\LogRecordExporterInterface; -use OpenTelemetry\SDK\Registry; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\NodeBuilder; @@ -45,7 +45,7 @@ public function createPlugin(array $properties, Context $context): LogRecordExpo $headers = array_column($properties['headers'], 'value', 'name') + MapParser::parse($properties['headers_list']); - return new LogsExporter(Registry::transportFactory($protocol)->create( + return new LogsExporter(Loader::transportFactory($protocol)->create( endpoint: $properties['endpoint'] . OtlpUtil::path(Signals::LOGS, $protocol), contentType: Protocols::contentType($protocol), headers: $headers, diff --git a/src/Config/SDK/ComponentProvider/Metrics/MetricExporterOtlp.php b/src/Config/SDK/ComponentProvider/Metrics/MetricExporterOtlp.php index f92872b49..ce0855898 100644 --- a/src/Config/SDK/ComponentProvider/Metrics/MetricExporterOtlp.php +++ b/src/Config/SDK/ComponentProvider/Metrics/MetricExporterOtlp.php @@ -14,9 +14,9 @@ use OpenTelemetry\Contrib\Otlp\OtlpUtil; use OpenTelemetry\Contrib\Otlp\Protocols; use OpenTelemetry\SDK\Common\Configuration\Parser\MapParser; +use OpenTelemetry\SDK\Common\Services\Loader; use OpenTelemetry\SDK\Metrics\Data\Temporality; use OpenTelemetry\SDK\Metrics\MetricExporterInterface; -use OpenTelemetry\SDK\Registry; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\NodeBuilder; @@ -54,7 +54,7 @@ public function createPlugin(array $properties, Context $context): MetricExporte 'lowmemory' => null, }; - return new MetricExporter(Registry::transportFactory($protocol)->create( + return new MetricExporter(Loader::transportFactory($protocol)->create( endpoint: $properties['endpoint'] . OtlpUtil::path(Signals::METRICS, $protocol), contentType: Protocols::contentType($protocol), headers: $headers, diff --git a/src/Config/SDK/ComponentProvider/Propagator/TextMapPropagatorCloudTrace.php b/src/Config/SDK/ComponentProvider/Propagator/TextMapPropagatorCloudTrace.php new file mode 100644 index 000000000..5e69b10f3 --- /dev/null +++ b/src/Config/SDK/ComponentProvider/Propagator/TextMapPropagatorCloudTrace.php @@ -0,0 +1,34 @@ + + */ +#[PackageDependency('open-telemetry/extension-propagator-cloudtrace', '^0.0.4')] +final class TextMapPropagatorCloudTrace implements ComponentProvider +{ + /** + * @param array{} $properties + */ + public function createPlugin(array $properties, Context $context): TextMapPropagatorInterface + { + return CloudTracePropagator::getInstance(); + } + + public function getConfig(ComponentProviderRegistry $registry, NodeBuilder $builder): ArrayNodeDefinition + { + return $builder->arrayNode('cloudtrace'); + } +} diff --git a/src/Config/SDK/ComponentProvider/Propagator/TextMapPropagatorJaeger.php b/src/Config/SDK/ComponentProvider/Propagator/TextMapPropagatorJaeger.php index 39eed7948..d17be129a 100644 --- a/src/Config/SDK/ComponentProvider/Propagator/TextMapPropagatorJaeger.php +++ b/src/Config/SDK/ComponentProvider/Propagator/TextMapPropagatorJaeger.php @@ -16,7 +16,7 @@ /** * @implements ComponentProvider */ -#[PackageDependency('open-telemetry/extension-propagator-jaeger', '^0.0.2')] +#[PackageDependency('open-telemetry/extension-propagator-jaeger', '^0.0.3')] final class TextMapPropagatorJaeger implements ComponentProvider { /** diff --git a/src/Config/SDK/ComponentProvider/Trace/SpanExporterConsole.php b/src/Config/SDK/ComponentProvider/Trace/SpanExporterConsole.php index 6596b014d..e9fdc006b 100644 --- a/src/Config/SDK/ComponentProvider/Trace/SpanExporterConsole.php +++ b/src/Config/SDK/ComponentProvider/Trace/SpanExporterConsole.php @@ -7,7 +7,7 @@ use OpenTelemetry\Config\SDK\Configuration\ComponentProvider; use OpenTelemetry\Config\SDK\Configuration\ComponentProviderRegistry; use OpenTelemetry\Config\SDK\Configuration\Context; -use OpenTelemetry\SDK\Registry; +use OpenTelemetry\SDK\Common\Services\Loader; use OpenTelemetry\SDK\Trace\SpanExporter\ConsoleSpanExporter; use OpenTelemetry\SDK\Trace\SpanExporterInterface; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; @@ -23,7 +23,7 @@ final class SpanExporterConsole implements ComponentProvider */ public function createPlugin(array $properties, Context $context): SpanExporterInterface { - return new ConsoleSpanExporter(Registry::transportFactory('stream')->create( + return new ConsoleSpanExporter(Loader::transportFactory('stream')->create( endpoint: 'php://stdout', contentType: 'application/json', )); diff --git a/src/Config/SDK/ComponentProvider/Trace/SpanExporterOtlp.php b/src/Config/SDK/ComponentProvider/Trace/SpanExporterOtlp.php index 83140c5fd..e94247d15 100644 --- a/src/Config/SDK/ComponentProvider/Trace/SpanExporterOtlp.php +++ b/src/Config/SDK/ComponentProvider/Trace/SpanExporterOtlp.php @@ -14,7 +14,7 @@ use OpenTelemetry\Contrib\Otlp\Protocols; use OpenTelemetry\Contrib\Otlp\SpanExporter; use OpenTelemetry\SDK\Common\Configuration\Parser\MapParser; -use OpenTelemetry\SDK\Registry; +use OpenTelemetry\SDK\Common\Services\Loader; use OpenTelemetry\SDK\Trace\SpanExporterInterface; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\NodeBuilder; @@ -45,7 +45,7 @@ public function createPlugin(array $properties, Context $context): SpanExporterI $headers = array_column($properties['headers'], 'value', 'name') + MapParser::parse($properties['headers_list']); - return new SpanExporter(Registry::transportFactory($protocol)->create( + return new SpanExporter(Loader::transportFactory($protocol)->create( endpoint: $properties['endpoint'] . OtlpUtil::path(Signals::TRACE, $protocol), contentType: Protocols::contentType($protocol), headers: $headers, diff --git a/src/Config/SDK/ComponentProvider/Trace/SpanExporterZipkin.php b/src/Config/SDK/ComponentProvider/Trace/SpanExporterZipkin.php index 4451ef151..936a5efee 100644 --- a/src/Config/SDK/ComponentProvider/Trace/SpanExporterZipkin.php +++ b/src/Config/SDK/ComponentProvider/Trace/SpanExporterZipkin.php @@ -10,7 +10,7 @@ use OpenTelemetry\Config\SDK\Configuration\Context; use OpenTelemetry\Config\SDK\Configuration\Validation; use OpenTelemetry\Contrib\Zipkin; -use OpenTelemetry\SDK\Registry; +use OpenTelemetry\SDK\Common\Services\Loader; use OpenTelemetry\SDK\Trace\SpanExporterInterface; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\NodeBuilder; @@ -29,7 +29,7 @@ final class SpanExporterZipkin implements ComponentProvider */ public function createPlugin(array $properties, Context $context): SpanExporterInterface { - return new Zipkin\Exporter(Registry::transportFactory('http')->create( + return new Zipkin\Exporter(Loader::transportFactory('http')->create( endpoint: $properties['endpoint'], contentType: 'application/json', timeout: $properties['timeout'], diff --git a/src/Config/SDK/_register.php b/src/Config/SDK/_register.php new file mode 100644 index 000000000..a2201f5cb --- /dev/null +++ b/src/Config/SDK/_register.php @@ -0,0 +1,64 @@ +buildTransport($protocol)); } + public function type(): string + { + return 'otlp'; + } + + public function priority(): int + { + return 0; + } + /** * @psalm-suppress UndefinedClass */ @@ -45,8 +55,7 @@ private function buildTransport(string $protocol): TransportInterface $compression = $this->getCompression(); $timeout = $this->getTimeout(); - $factoryClass = Registry::transportFactory($protocol); - $factory = $this->transportFactory ?: new $factoryClass(); + $factory = $this->transportFactory ?? Loader::transportFactory($protocol); return $factory->create( $endpoint, diff --git a/src/Contrib/Otlp/MetricExporterFactory.php b/src/Contrib/Otlp/MetricExporterFactory.php index 1ba7488f3..7bd9d28c7 100644 --- a/src/Contrib/Otlp/MetricExporterFactory.php +++ b/src/Contrib/Otlp/MetricExporterFactory.php @@ -10,10 +10,10 @@ use OpenTelemetry\SDK\Common\Configuration\Variables; use OpenTelemetry\SDK\Common\Export\TransportFactoryInterface; use OpenTelemetry\SDK\Common\Export\TransportInterface; +use OpenTelemetry\SDK\Common\Services\Loader; use OpenTelemetry\SDK\Metrics\Data\Temporality; use OpenTelemetry\SDK\Metrics\MetricExporterFactoryInterface; use OpenTelemetry\SDK\Metrics\MetricExporterInterface; -use OpenTelemetry\SDK\Registry; class MetricExporterFactory implements MetricExporterFactoryInterface { @@ -36,6 +36,16 @@ public function create(): MetricExporterInterface return new MetricExporter($this->buildTransport($protocol), $temporality); } + public function type(): string + { + return 'otlp'; + } + + public function priority(): int + { + return 0; + } + /** * @psalm-suppress UndefinedClass */ @@ -51,8 +61,7 @@ private function buildTransport(string $protocol): TransportInterface $compression = $this->getCompression(); $timeout = $this->getTimeout(); - $factoryClass = Registry::transportFactory($protocol); - $factory = $this->transportFactory ?: new $factoryClass(); + $factory = $this->transportFactory ?? Loader::transportFactory($protocol); return $factory->create( $endpoint, diff --git a/src/Contrib/Otlp/OtlpHttpTransportFactory.php b/src/Contrib/Otlp/OtlpHttpTransportFactory.php deleted file mode 100644 index f40af681a..000000000 --- a/src/Contrib/Otlp/OtlpHttpTransportFactory.php +++ /dev/null @@ -1,34 +0,0 @@ -create($endpoint, $contentType, $headers, $compression, $timeout, $retryDelay, $maxRetries, $cacert, $cert, $key); - } -} diff --git a/src/Contrib/Otlp/SpanExporterFactory.php b/src/Contrib/Otlp/SpanExporterFactory.php index 1ef7d49aa..ac77c3ec4 100644 --- a/src/Contrib/Otlp/SpanExporterFactory.php +++ b/src/Contrib/Otlp/SpanExporterFactory.php @@ -11,7 +11,7 @@ use OpenTelemetry\SDK\Common\Configuration\Variables; use OpenTelemetry\SDK\Common\Export\TransportFactoryInterface; use OpenTelemetry\SDK\Common\Export\TransportInterface; -use OpenTelemetry\SDK\Registry; +use OpenTelemetry\SDK\Common\Services\Loader; use OpenTelemetry\SDK\Trace\SpanExporter\SpanExporterFactoryInterface; use OpenTelemetry\SDK\Trace\SpanExporterInterface; @@ -48,8 +48,7 @@ private function buildTransport(): TransportInterface $compression = $this->getCompression(); $timeout = $this->getTimeout(); - $factoryClass = Registry::transportFactory($protocol); - $factory = $this->transportFactory ?: new $factoryClass(); + $factory = $this->transportFactory ?? Loader::transportFactory($protocol); return $factory->create($endpoint, $contentType, $headers, $compression, $timeout); } @@ -91,4 +90,14 @@ private function getTimeout(): float return $value/1000; } + + public function type(): string + { + return 'otlp'; + } + + public function priority(): int + { + return 0; + } } diff --git a/src/Contrib/Otlp/StdoutLogsExporterFactory.php b/src/Contrib/Otlp/StdoutLogsExporterFactory.php index d39c71e6a..8a8a7fde3 100644 --- a/src/Contrib/Otlp/StdoutLogsExporterFactory.php +++ b/src/Contrib/Otlp/StdoutLogsExporterFactory.php @@ -16,4 +16,14 @@ public function create(): LogRecordExporterInterface return new LogsExporter($transport); } + + public function type(): string + { + return 'otlp/stdout'; + } + + public function priority(): int + { + return 0; + } } diff --git a/src/Contrib/Otlp/StdoutMetricExporterFactory.php b/src/Contrib/Otlp/StdoutMetricExporterFactory.php index 3eeba09e2..73fac05c8 100644 --- a/src/Contrib/Otlp/StdoutMetricExporterFactory.php +++ b/src/Contrib/Otlp/StdoutMetricExporterFactory.php @@ -16,4 +16,14 @@ public function create(): MetricExporterInterface return new MetricExporter($transport); } + + public function type(): string + { + return 'otlp/stdout'; + } + + public function priority(): int + { + return 0; + } } diff --git a/src/Contrib/Otlp/StdoutSpanExporterFactory.php b/src/Contrib/Otlp/StdoutSpanExporterFactory.php index d51cbaa75..831fbfc50 100644 --- a/src/Contrib/Otlp/StdoutSpanExporterFactory.php +++ b/src/Contrib/Otlp/StdoutSpanExporterFactory.php @@ -16,4 +16,14 @@ public function create(): SpanExporterInterface return new SpanExporter($transport); } + + public function type(): string + { + return 'otlp/stdout'; + } + + public function priority(): int + { + return 0; + } } diff --git a/src/Contrib/Otlp/_register.php b/src/Contrib/Otlp/_register.php index 5f9d15af6..31060a6b2 100644 --- a/src/Contrib/Otlp/_register.php +++ b/src/Contrib/Otlp/_register.php @@ -1,13 +1,23 @@ $value !== 'none'); } - if (!$compression) { + if ($compression === 'none' || !$compression) { return []; } if (!str_contains((string) $compression, ',')) { diff --git a/src/SDK/Common/Export/Stream/StreamTransportFactory.php b/src/SDK/Common/Export/Stream/StreamTransportFactory.php index c6544bbe5..1568e78fc 100644 --- a/src/SDK/Common/Export/Stream/StreamTransportFactory.php +++ b/src/SDK/Common/Export/Stream/StreamTransportFactory.php @@ -116,4 +116,14 @@ private static function createHeaderArray(string $contentType, array $headers): return $header; } + + public function type(): string + { + return 'stream'; + } + + public function priority(): int + { + return 0; + } } diff --git a/src/SDK/Common/Export/TransportFactoryInterface.php b/src/SDK/Common/Export/TransportFactoryInterface.php index a2dd3a1e1..92ad49876 100644 --- a/src/SDK/Common/Export/TransportFactoryInterface.php +++ b/src/SDK/Common/Export/TransportFactoryInterface.php @@ -4,7 +4,9 @@ namespace OpenTelemetry\SDK\Common\Export; -interface TransportFactoryInterface +use OpenTelemetry\SDK\Common\Services\SpiLoadableInterface; + +interface TransportFactoryInterface extends SpiLoadableInterface { public const COMPRESSION_GZIP = 'gzip'; public const COMPRESSION_DEFLATE = 'deflate'; diff --git a/src/SDK/Common/Services/Loader.php b/src/SDK/Common/Services/Loader.php new file mode 100644 index 000000000..b4915a1cc --- /dev/null +++ b/src/SDK/Common/Services/Loader.php @@ -0,0 +1,99 @@ + $class + * @phan-suppress PhanTypeNonVarPassByRef + */ + private static function getFactory(string $class, string $type): mixed + { + $factories = iterator_to_array(ServiceLoader::load($class)); + array_multisort( + array_map(static fn ($factory) => $factory->priority(), $factories), + SORT_DESC, + $factories, + ); + foreach ($factories as $factory) { + if ($factory->type() === $type) { + return $factory; + } + } + + return null; + } + + public static function spanExporterFactory(string $exporter): SpanExporterFactoryInterface + { + return self::getFactory(SpanExporterFactoryInterface::class, $exporter) + ?? throw new RuntimeException('Span exporter factory not defined for: ' . $exporter); + } + + public static function logRecordExporterFactory(string $exporter): LogRecordExporterFactoryInterface + { + return self::getFactory(LogRecordExporterFactoryInterface::class, $exporter) + ?? throw new RuntimeException('LogRecord exporter factory not defined for: ' . $exporter); + } + + /** + * Get transport factory registered for protocol. If $protocol contains a content-type eg `http/xyz` then + * only the first part, `http`, is used. + */ + public static function transportFactory(string $protocol): TransportFactoryInterface + { + $type = explode('/', $protocol)[0]; + + return self::getFactory(TransportFactoryInterface::class, $type) + ?? throw new RuntimeException('Transport factory not defined for protocol: ' . $type); + } + + public static function metricExporterFactory(string $exporter): MetricExporterFactoryInterface + { + return self::getFactory(MetricExporterFactoryInterface::class, $exporter) + ?? throw new RuntimeException('Metric exporter factory not registered for protocol: ' . $exporter); + } + + public static function textMapPropagator(string $name): TextMapPropagatorInterface + { + $factory = self::getFactory(TextMapPropagatorFactoryInterface::class, $name) + ?? throw new RuntimeException('Text map propagator not registered for: ' . $name); + + return $factory->create(); + } + + public static function resourceDetector(string $name): ResourceDetectorInterface + { + $factory = self::getFactory(ResourceDetectorFactoryInterface::class, $name) + ?? throw new RuntimeException('Resource detector not registered for: ' . $name); + + return $factory->create(); + } + + /** + * @return ResourceDetectorInterface[] + */ + public static function resourceDetectors(): array + { + $factories = iterator_to_array(ServiceLoader::load(ResourceDetectorFactoryInterface::class)); + + return array_map(fn (ResourceDetectorFactoryInterface $factory) => $factory->create(), $factories); + } +} diff --git a/src/SDK/Common/Services/SpiLoadableInterface.php b/src/SDK/Common/Services/SpiLoadableInterface.php new file mode 100644 index 000000000..714e412a1 --- /dev/null +++ b/src/SDK/Common/Services/SpiLoadableInterface.php @@ -0,0 +1,11 @@ +create('php://stdout', 'application/json'); + $transport = Loader::transportFactory('stream')->create('php://stdout', 'application/json'); return new ConsoleExporter($transport); } + + public function type(): string + { + return 'console'; + } + + public function priority(): int + { + return 0; + } } diff --git a/src/SDK/Logs/Exporter/InMemoryExporterFactory.php b/src/SDK/Logs/Exporter/InMemoryExporterFactory.php index 6f24defe0..aad5389d4 100644 --- a/src/SDK/Logs/Exporter/InMemoryExporterFactory.php +++ b/src/SDK/Logs/Exporter/InMemoryExporterFactory.php @@ -13,4 +13,14 @@ public function create(): LogRecordExporterInterface { return new InMemoryExporter(); } + + public function type(): string + { + return 'memory'; + } + + public function priority(): int + { + return 0; + } } diff --git a/src/SDK/Logs/Exporter/_register.php b/src/SDK/Logs/Exporter/_register.php deleted file mode 100644 index 96958baa8..000000000 --- a/src/SDK/Logs/Exporter/_register.php +++ /dev/null @@ -1,6 +0,0 @@ -create(); } diff --git a/src/SDK/Logs/LogRecordExporterFactoryInterface.php b/src/SDK/Logs/LogRecordExporterFactoryInterface.php index 523bec1ba..153be9989 100644 --- a/src/SDK/Logs/LogRecordExporterFactoryInterface.php +++ b/src/SDK/Logs/LogRecordExporterFactoryInterface.php @@ -4,7 +4,9 @@ namespace OpenTelemetry\SDK\Logs; -interface LogRecordExporterFactoryInterface +use OpenTelemetry\SDK\Common\Services\SpiLoadableInterface; + +interface LogRecordExporterFactoryInterface extends SpiLoadableInterface { public function create(): LogRecordExporterInterface; } diff --git a/src/SDK/Metrics/MeterProviderFactory.php b/src/SDK/Metrics/MeterProviderFactory.php index 0f62cec5f..993812b61 100644 --- a/src/SDK/Metrics/MeterProviderFactory.php +++ b/src/SDK/Metrics/MeterProviderFactory.php @@ -9,13 +9,13 @@ use OpenTelemetry\SDK\Common\Configuration\Configuration; use OpenTelemetry\SDK\Common\Configuration\KnownValues; use OpenTelemetry\SDK\Common\Configuration\Variables; +use OpenTelemetry\SDK\Common\Services\Loader; use OpenTelemetry\SDK\Metrics\Exemplar\ExemplarFilter\AllExemplarFilter; use OpenTelemetry\SDK\Metrics\Exemplar\ExemplarFilter\NoneExemplarFilter; use OpenTelemetry\SDK\Metrics\Exemplar\ExemplarFilter\WithSampledTraceExemplarFilter; use OpenTelemetry\SDK\Metrics\Exemplar\ExemplarFilterInterface; use OpenTelemetry\SDK\Metrics\MetricExporter\NoopMetricExporter; use OpenTelemetry\SDK\Metrics\MetricReader\ExportingReader; -use OpenTelemetry\SDK\Registry; use OpenTelemetry\SDK\Resource\ResourceInfo; use OpenTelemetry\SDK\Resource\ResourceInfoFactory; use OpenTelemetry\SDK\Sdk; @@ -42,7 +42,7 @@ public function create(?ResourceInfo $resource = null): MeterProviderInterface $exporterName = $exporters[0]; try { - $factory = Registry::metricExporterFactory($exporterName); + $factory = Loader::metricExporterFactory($exporterName); $exporter = $factory->create(); } catch (\Throwable $t) { self::logWarning(sprintf('Unable to create %s meter provider: %s', $exporterName, $t->getMessage())); diff --git a/src/SDK/Metrics/MetricExporter/ConsoleMetricExporterFactory.php b/src/SDK/Metrics/MetricExporter/ConsoleMetricExporterFactory.php index 19088738d..df29ce3ac 100644 --- a/src/SDK/Metrics/MetricExporter/ConsoleMetricExporterFactory.php +++ b/src/SDK/Metrics/MetricExporter/ConsoleMetricExporterFactory.php @@ -13,4 +13,14 @@ public function create(): MetricExporterInterface { return new ConsoleMetricExporter(); } + + public function type(): string + { + return 'console'; + } + + public function priority(): int + { + return 0; + } } diff --git a/src/SDK/Metrics/MetricExporter/InMemoryExporterFactory.php b/src/SDK/Metrics/MetricExporter/InMemoryExporterFactory.php index c72c7b169..f95eaa923 100644 --- a/src/SDK/Metrics/MetricExporter/InMemoryExporterFactory.php +++ b/src/SDK/Metrics/MetricExporter/InMemoryExporterFactory.php @@ -13,4 +13,14 @@ public function create(): MetricExporterInterface { return new InMemoryExporter(); } + + public function type(): string + { + return 'memory'; + } + + public function priority(): int + { + return 0; + } } diff --git a/src/SDK/Metrics/MetricExporter/NoopMetricExporterFactory.php b/src/SDK/Metrics/MetricExporter/NoopMetricExporterFactory.php index ab2ab2af3..7abe564c1 100644 --- a/src/SDK/Metrics/MetricExporter/NoopMetricExporterFactory.php +++ b/src/SDK/Metrics/MetricExporter/NoopMetricExporterFactory.php @@ -13,4 +13,14 @@ public function create(): MetricExporterInterface { return new NoopMetricExporter(); } + + public function type(): string + { + return 'none'; + } + + public function priority(): int + { + return 0; + } } diff --git a/src/SDK/Metrics/MetricExporter/_register.php b/src/SDK/Metrics/MetricExporter/_register.php deleted file mode 100644 index fba543d02..000000000 --- a/src/SDK/Metrics/MetricExporter/_register.php +++ /dev/null @@ -1,7 +0,0 @@ -getMessage()); + switch ($name) { + case KnownValues::VALUE_NOOP: + case KnownValues::VALUE_NONE: + return NoopTextMapPropagator::getInstance(); + default: + try { + return Loader::textMapPropagator($name); + } catch (\RuntimeException $e) { + self::logWarning($e->getMessage()); + } } return NoopTextMapPropagator::getInstance(); diff --git a/src/SDK/Propagation/TextMapPropagatorFactoryInterface.php b/src/SDK/Propagation/TextMapPropagatorFactoryInterface.php new file mode 100644 index 000000000..02858063f --- /dev/null +++ b/src/SDK/Propagation/TextMapPropagatorFactoryInterface.php @@ -0,0 +1,13 @@ + $factory - * @throws TypeError - */ - public static function registerTransportFactory(string $protocol, TransportFactoryInterface|string $factory, bool $clobber = false): void - { - if (!$clobber && array_key_exists($protocol, self::$transportFactories)) { - return; - } - if (!is_subclass_of($factory, TransportFactoryInterface::class)) { - throw new TypeError( - sprintf( - 'Cannot register transport factory: %s must exist and implement %s', - is_string($factory) ? $factory : $factory::class, - TransportFactoryInterface::class - ) - ); - } - self::$transportFactories[$protocol] = $factory; - } - - /** - * @param SpanExporterFactoryInterface|class-string $factory - * @throws TypeError - */ - public static function registerSpanExporterFactory(string $exporter, SpanExporterFactoryInterface|string $factory, bool $clobber = false): void - { - if (!$clobber && array_key_exists($exporter, self::$spanExporterFactories)) { - return; - } - if (!is_subclass_of($factory, SpanExporterFactoryInterface::class)) { - throw new TypeError( - sprintf( - 'Cannot register span exporter factory: %s must exist and implement %s', - is_string($factory) ? $factory : $factory::class, - SpanExporterFactoryInterface::class - ) - ); - } - self::$spanExporterFactories[$exporter] = $factory; - } - - /** - * @param MetricExporterFactoryInterface|class-string $factory - * @throws TypeError - */ - public static function registerMetricExporterFactory(string $exporter, MetricExporterFactoryInterface|string $factory, bool $clobber = false): void - { - if (!$clobber && array_key_exists($exporter, self::$metricExporterFactories)) { - return; - } - if (!is_subclass_of($factory, MetricExporterFactoryInterface::class)) { - throw new TypeError( - sprintf( - 'Cannot register metric factory: %s must exist and implement %s', - is_string($factory) ? $factory : $factory::class, - MetricExporterFactoryInterface::class - ) - ); - } - self::$metricExporterFactories[$exporter] = $factory; - } - - /** - * @param LogRecordExporterFactoryInterface|class-string $factory - * @throws TypeError - */ - public static function registerLogRecordExporterFactory(string $exporter, LogRecordExporterFactoryInterface|string $factory, bool $clobber = false): void - { - if (!$clobber && array_key_exists($exporter, self::$logRecordExporterFactories)) { - return; - } - if (!is_subclass_of($factory, LogRecordExporterFactoryInterface::class)) { - throw new TypeError( - sprintf( - 'Cannot register LogRecord exporter factory: %s must exist and implement %s', - is_string($factory) ? $factory : $factory::class, - LogRecordExporterFactoryInterface::class - ) - ); - } - self::$logRecordExporterFactories[$exporter] = $factory; - } - - public static function registerTextMapPropagator(string $name, TextMapPropagatorInterface $propagator, bool $clobber = false): void - { - if (!$clobber && array_key_exists($name, self::$textMapPropagators)) { - return; - } - self::$textMapPropagators[$name] = $propagator; - } - - public static function registerResourceDetector(string $name, ResourceDetectorInterface $detector): void - { - self::$resourceDetectors[$name] = $detector; - } - - public static function spanExporterFactory(string $exporter): SpanExporterFactoryInterface - { - if (!array_key_exists($exporter, self::$spanExporterFactories)) { - throw new RuntimeException('Span exporter factory not defined for: ' . $exporter); - } - $class = self::$spanExporterFactories[$exporter]; - $factory = (is_callable($class)) ? $class : new $class(); - assert($factory instanceof SpanExporterFactoryInterface); - - return $factory; - } - - public static function logRecordExporterFactory(string $exporter): LogRecordExporterFactoryInterface - { - if (!array_key_exists($exporter, self::$logRecordExporterFactories)) { - throw new RuntimeException('LogRecord exporter factory not defined for: ' . $exporter); - } - $class = self::$logRecordExporterFactories[$exporter]; - $factory = (is_callable($class)) ? $class : new $class(); - assert($factory instanceof LogRecordExporterFactoryInterface); - - return $factory; - } - - /** - * Get transport factory registered for protocol. If $protocol contains a content-type eg `http/xyz` then - * only the first part, `http`, is used. - */ - public static function transportFactory(string $protocol): TransportFactoryInterface - { - $protocol = explode('/', $protocol)[0]; - if (!array_key_exists($protocol, self::$transportFactories)) { - throw new RuntimeException('Transport factory not defined for protocol: ' . $protocol); - } - $class = self::$transportFactories[$protocol]; - $factory = (is_callable($class)) ? $class : new $class(); - assert($factory instanceof TransportFactoryInterface); - - return $factory; - } - - public static function metricExporterFactory(string $exporter): MetricExporterFactoryInterface - { - if (!array_key_exists($exporter, self::$metricExporterFactories)) { - throw new RuntimeException('Metric exporter factory not registered for protocol: ' . $exporter); - } - $class = self::$metricExporterFactories[$exporter]; - $factory = (is_callable($class)) ? $class : new $class(); - assert($factory instanceof MetricExporterFactoryInterface); - - return $factory; - } - - public static function textMapPropagator(string $name): TextMapPropagatorInterface - { - if (!array_key_exists($name, self::$textMapPropagators)) { - throw new RuntimeException('Text map propagator not registered for: ' . $name); - } - - return self::$textMapPropagators[$name]; - } - - public static function resourceDetector(string $name): ResourceDetectorInterface - { - if (!array_key_exists($name, self::$resourceDetectors)) { - throw new RuntimeException('Resource detector not registered for: ' . $name); - } - - return self::$resourceDetectors[$name]; - } - - /** - * @return array - */ - public static function resourceDetectors(): array - { - return array_values(self::$resourceDetectors); - } -} diff --git a/src/SDK/Resource/ResourceDetectorFactoryInterface.php b/src/SDK/Resource/ResourceDetectorFactoryInterface.php new file mode 100644 index 000000000..fd2698897 --- /dev/null +++ b/src/SDK/Resource/ResourceDetectorFactoryInterface.php @@ -0,0 +1,12 @@ +getResource(); } @@ -84,7 +84,7 @@ public static function defaultResource(): ResourceInfo break; default: try { - $resourceDetectors[] = Registry::resourceDetector($detector); + $resourceDetectors[] = Loader::resourceDetector($detector); } catch (RuntimeException $e) { self::logWarning($e->getMessage()); } diff --git a/src/SDK/Trace/ExporterFactory.php b/src/SDK/Trace/ExporterFactory.php index 9b652cc2f..eb3efadb2 100644 --- a/src/SDK/Trace/ExporterFactory.php +++ b/src/SDK/Trace/ExporterFactory.php @@ -7,7 +7,7 @@ use InvalidArgumentException; use OpenTelemetry\SDK\Common\Configuration\Configuration; use OpenTelemetry\SDK\Common\Configuration\Variables; -use OpenTelemetry\SDK\Registry; +use OpenTelemetry\SDK\Common\Services\Loader; use RuntimeException; class ExporterFactory @@ -25,7 +25,7 @@ public function create(): ?SpanExporterInterface if ($exporter === 'none') { return null; } - $factory = Registry::spanExporterFactory($exporter); + $factory = Loader::spanExporterFactory($exporter); return $factory->create(); } diff --git a/src/SDK/Trace/SpanExporter/ConsoleSpanExporterFactory.php b/src/SDK/Trace/SpanExporter/ConsoleSpanExporterFactory.php index 7e45fb549..40b04cbd2 100644 --- a/src/SDK/Trace/SpanExporter/ConsoleSpanExporterFactory.php +++ b/src/SDK/Trace/SpanExporter/ConsoleSpanExporterFactory.php @@ -4,15 +4,25 @@ namespace OpenTelemetry\SDK\Trace\SpanExporter; -use OpenTelemetry\SDK\Registry; +use OpenTelemetry\SDK\Common\Services\Loader; use OpenTelemetry\SDK\Trace\SpanExporterInterface; class ConsoleSpanExporterFactory implements SpanExporterFactoryInterface { public function create(): SpanExporterInterface { - $transport = Registry::transportFactory('stream')->create('php://stdout', 'application/json'); + $transport = Loader::transportFactory('stream')->create('php://stdout', 'application/json'); return new ConsoleSpanExporter($transport); } + + public function type(): string + { + return 'console'; + } + + public function priority(): int + { + return 0; + } } diff --git a/src/SDK/Trace/SpanExporter/InMemorySpanExporterFactory.php b/src/SDK/Trace/SpanExporter/InMemorySpanExporterFactory.php index c19686fac..473a82fc5 100644 --- a/src/SDK/Trace/SpanExporter/InMemorySpanExporterFactory.php +++ b/src/SDK/Trace/SpanExporter/InMemorySpanExporterFactory.php @@ -12,4 +12,14 @@ public function create(): SpanExporterInterface { return new InMemoryExporter(); } + + public function type(): string + { + return 'memory'; + } + + public function priority(): int + { + return 0; + } } diff --git a/src/SDK/Trace/SpanExporter/SpanExporterFactoryInterface.php b/src/SDK/Trace/SpanExporter/SpanExporterFactoryInterface.php index 8d44b35eb..98d24bd51 100644 --- a/src/SDK/Trace/SpanExporter/SpanExporterFactoryInterface.php +++ b/src/SDK/Trace/SpanExporter/SpanExporterFactoryInterface.php @@ -4,9 +4,10 @@ namespace OpenTelemetry\SDK\Trace\SpanExporter; +use OpenTelemetry\SDK\Common\Services\SpiLoadableInterface; use OpenTelemetry\SDK\Trace\SpanExporterInterface; -interface SpanExporterFactoryInterface +interface SpanExporterFactoryInterface extends SpiLoadableInterface { public function create(): SpanExporterInterface; } diff --git a/src/SDK/Trace/SpanExporter/_register.php b/src/SDK/Trace/SpanExporter/_register.php deleted file mode 100644 index aad07be42..000000000 --- a/src/SDK/Trace/SpanExporter/_register.php +++ /dev/null @@ -1,7 +0,0 @@ -assertInstanceOf(TransportInterface::class, $transport); } + + public function test_type(): void + { + $factory = new GrpcTransportFactory(); + $this->assertSame('grpc', $factory->type()); + } + + public function test_priority(): void + { + $factory = new GrpcTransportFactory(); + $this->assertSame(0, $factory->priority()); + } } diff --git a/tests/Unit/Contrib/Otlp/LogsExporterFactoryTest.php b/tests/Unit/Contrib/Otlp/LogsExporterFactoryTest.php index 9a2d22f57..ae374a711 100644 --- a/tests/Unit/Contrib/Otlp/LogsExporterFactoryTest.php +++ b/tests/Unit/Contrib/Otlp/LogsExporterFactoryTest.php @@ -171,4 +171,16 @@ public static function configProvider(): array ], ]; } + + public function test_type(): void + { + $factory = new LogsExporterFactory(); + $this->assertSame('otlp', $factory->type()); + } + + public function test_priority(): void + { + $factory = new LogsExporterFactory(); + $this->assertSame(0, $factory->priority()); + } } diff --git a/tests/Unit/Contrib/Otlp/MetricExporterFactoryTest.php b/tests/Unit/Contrib/Otlp/MetricExporterFactoryTest.php index 774800dd4..faaf33ded 100644 --- a/tests/Unit/Contrib/Otlp/MetricExporterFactoryTest.php +++ b/tests/Unit/Contrib/Otlp/MetricExporterFactoryTest.php @@ -229,4 +229,16 @@ public static function configProvider(): array ], ]; } + + public function test_type(): void + { + $factory = new MetricExporterFactory(); + $this->assertSame('otlp', $factory->type()); + } + + public function test_priority(): void + { + $factory = new MetricExporterFactory(); + $this->assertSame(0, $factory->priority()); + } } diff --git a/tests/Unit/Contrib/Otlp/OtlpHttpTransportFactoryTest.php b/tests/Unit/Contrib/Otlp/OtlpHttpTransportFactoryTest.php deleted file mode 100644 index a0c11ddeb..000000000 --- a/tests/Unit/Contrib/Otlp/OtlpHttpTransportFactoryTest.php +++ /dev/null @@ -1,27 +0,0 @@ -factory = new OtlpHttpTransportFactory(); - } - - public function test_factory_creates_psr_transport(): void - { - $transport = $this->factory->create('http://example.com', 'application/x-protobuf'); - $this->assertInstanceOf(PsrTransport::class, $transport); - } -} diff --git a/tests/Unit/Contrib/Otlp/SpanExporterFactoryTest.php b/tests/Unit/Contrib/Otlp/SpanExporterFactoryTest.php index 015801562..e055797dc 100644 --- a/tests/Unit/Contrib/Otlp/SpanExporterFactoryTest.php +++ b/tests/Unit/Contrib/Otlp/SpanExporterFactoryTest.php @@ -174,4 +174,16 @@ public static function configProvider(): array ], ]; } + + public function test_type(): void + { + $factory = new SpanExporterFactory(); + $this->assertSame('otlp', $factory->type()); + } + + public function test_priority(): void + { + $factory = new SpanExporterFactory(); + $this->assertSame(0, $factory->priority()); + } } diff --git a/tests/Unit/SDK/Common/Export/Http/PsrTransportTest.php b/tests/Unit/SDK/Common/Export/Http/PsrTransportTest.php index dab731d93..800a1b094 100644 --- a/tests/Unit/SDK/Common/Export/Http/PsrTransportTest.php +++ b/tests/Unit/SDK/Common/Export/Http/PsrTransportTest.php @@ -203,4 +203,14 @@ public function test_force_flush_closed_returns_false(): void $this->assertFalse($transport->forceFlush()); } + + public function test_type(): void + { + $this->assertSame('http', $this->factory->type()); + } + + public function test_priority(): void + { + $this->assertSame(0, $this->factory->priority()); + } } diff --git a/tests/Unit/SDK/Common/Export/Http/PsrUtilsTest.php b/tests/Unit/SDK/Common/Export/Http/PsrUtilsTest.php index c47430eef..b215148e3 100644 --- a/tests/Unit/SDK/Common/Export/Http/PsrUtilsTest.php +++ b/tests/Unit/SDK/Common/Export/Http/PsrUtilsTest.php @@ -108,9 +108,11 @@ public static function compressionProvider(): array return [ ['gzip', ['gzip']], ['', []], + ['none', []], ['gzip,br', ['gzip','br']], ['gzip , brotli', ['gzip','brotli']], [['gzip'], ['gzip']], + [['gzip', 'none'], ['gzip']], ]; } } diff --git a/tests/Unit/SDK/Common/Export/Stream/StreamTransportFactoryTest.php b/tests/Unit/SDK/Common/Export/Stream/StreamTransportFactoryTest.php index cd6b2cc0a..cc30a3d05 100644 --- a/tests/Unit/SDK/Common/Export/Stream/StreamTransportFactoryTest.php +++ b/tests/Unit/SDK/Common/Export/Stream/StreamTransportFactoryTest.php @@ -18,4 +18,16 @@ public function test_creates_stream(): void $this->expectOutputString('payload'); $transport->send('payload')->await(); } + + public function test_type(): void + { + $factory = new StreamTransportFactory(); + $this->assertSame('stream', $factory->type()); + } + + public function test_priority(): void + { + $factory = new StreamTransportFactory(); + $this->assertSame(0, $factory->priority()); + } } diff --git a/tests/Unit/SDK/FactoryRegistryTest.php b/tests/Unit/SDK/Common/Services/LoaderTest.php similarity index 51% rename from tests/Unit/SDK/FactoryRegistryTest.php rename to tests/Unit/SDK/Common/Services/LoaderTest.php index d58842375..50f30d048 100644 --- a/tests/Unit/SDK/FactoryRegistryTest.php +++ b/tests/Unit/SDK/Common/Services/LoaderTest.php @@ -2,26 +2,33 @@ declare(strict_types=1); -namespace OpenTelemetry\Tests\Unit\SDK; +namespace OpenTelemetry\Tests\Unit\SDK\Common\Services; +use Nevay\SPI\ServiceLoader; use OpenTelemetry\Context\Propagation\TextMapPropagatorInterface; +use OpenTelemetry\SDK\Common\Attribute\Attributes; use OpenTelemetry\SDK\Common\Export\TransportFactoryInterface; +use OpenTelemetry\SDK\Common\Services\Loader; use OpenTelemetry\SDK\Logs\LogRecordExporterFactoryInterface; use OpenTelemetry\SDK\Metrics\MetricExporterFactoryInterface; -use OpenTelemetry\SDK\Registry; +use OpenTelemetry\SDK\Resource\ResourceDetectorFactoryInterface; +use OpenTelemetry\SDK\Resource\ResourceDetectorInterface; +use OpenTelemetry\SDK\Resource\ResourceInfo; use OpenTelemetry\SDK\Trace\SpanExporter\SpanExporterFactoryInterface; +use OpenTelemetry\Tests\TestState; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; -use TypeError; -#[CoversClass(Registry::class)] -class FactoryRegistryTest extends TestCase +#[CoversClass(Loader::class)] +class LoaderTest extends TestCase { + use TestState; + #[DataProvider('transportProtocolsProvider')] public function test_default_transport_factories(string $name): void { - $factory = Registry::transportFactory($name); + $factory = Loader::transportFactory($name); $this->assertInstanceOf(TransportFactoryInterface::class, $factory); } @@ -40,7 +47,7 @@ public static function transportProtocolsProvider(): array #[DataProvider('spanExporterProvider')] public function test_default_span_exporter_factories(string $name): void { - $factory = Registry::spanExporterFactory($name); + $factory = Loader::spanExporterFactory($name); $this->assertInstanceOf(SpanExporterFactoryInterface::class, $factory); } @@ -57,7 +64,7 @@ public static function spanExporterProvider(): array #[DataProvider('metricExporterProvider')] public function test_default_metric_exporter_factories(string $name): void { - $factory = Registry::metricExporterFactory($name); + $factory = Loader::metricExporterFactory($name); $this->assertInstanceOf(MetricExporterFactoryInterface::class, $factory); } @@ -73,7 +80,7 @@ public static function metricExporterProvider(): array #[DataProvider('logRecordExporterProvider')] public function test_default_log_record_exporter_factories(string $name): void { - $factory = Registry::logRecordExporterFactory($name); + $factory = Loader::logRecordExporterFactory($name); $this->assertInstanceOf(LogRecordExporterFactoryInterface::class, $factory); } @@ -88,7 +95,7 @@ public static function logRecordExporterProvider(): array #[DataProvider('textMapPropagator')] public function test_default_text_map_propagator(string $name): void { - $propagator = Registry::textMapPropagator($name); + $propagator = Loader::textMapPropagator($name); $this->assertInstanceOf(TextMapPropagatorInterface::class, $propagator); } @@ -106,39 +113,56 @@ public static function textMapPropagator(): array ]; } - #[DataProvider('invalidFactoryProvider')] - public function test_register_invalid_transport_factory($factory): void + public function test_retrieve_from_spi(): void { - $this->expectException(TypeError::class); - Registry::registerTransportFactory('http', $factory, true); + $this->assertFileExists(dirname(__DIR__, 5) . '/vendor/composer/GeneratedServiceProviderData.php'); + $this->assertInstanceOf(ResourceDetectorInterface::class, Loader::resourceDetector('test')); } - #[DataProvider('invalidFactoryProvider')] - public function test_register_invalid_span_exporter_factory($factory): void + public function test_add_to_spi(): void { - $this->expectException(TypeError::class); - Registry::registerSpanExporterFactory('foo', $factory, true); + $factory = new class() implements ResourceDetectorFactoryInterface { + public function create(): ResourceDetectorInterface + { + return new class() implements ResourceDetectorInterface { + public function getResource(): ResourceInfo + { + return ResourceInfo::create(Attributes::create(['foo-resource' => 'foo'])); + } + }; + } + public function type(): string + { + return 'foo'; + } + public function priority(): int + { + return 99; + } + }; + ServiceLoader::register(ResourceDetectorFactoryInterface::class, $factory::class); + + $detector = Loader::resourceDetector('foo'); + $this->assertInstanceOf(ResourceDetectorInterface::class, $detector); + $this->assertTrue($detector->getResource()->getAttributes()->has('foo-resource')); + $this->assertSame('foo', $detector->getResource()->getAttributes()->get('foo-resource')); } - #[DataProvider('invalidFactoryProvider')] - public function test_register_invalid_metric_exporter_factory($factory): void + public function test_get_all_resource_detectors(): void { - $this->expectException(TypeError::class); - Registry::registerMetricExporterFactory('foo', $factory, true); + $detectors = Loader::resourceDetectors(); + $this->assertNotEmpty($detectors); } - #[DataProvider('invalidFactoryProvider')] - public function test_register_invalid_log_record_exporter_factory($factory): void + public function test_missing_propagator(): void { - $this->expectException(TypeError::class); - Registry::registerLogRecordExporterFactory('foo', $factory, true); + $this->expectException(\RuntimeException::class); + Loader::textMapPropagator('missing'); } - public static function invalidFactoryProvider(): array + public function test_missing_detector(): void { - return [ - [new \stdClass()], - ['\stdClass'], - ]; + $this->expectException(\RuntimeException::class); + Loader::resourceDetector('missing'); } } diff --git a/tests/Unit/SDK/Logs/Exporter/ConsoleExporterFactoryTest.php b/tests/Unit/SDK/Logs/Exporter/ConsoleExporterFactoryTest.php index 0d8625132..fd7970948 100644 --- a/tests/Unit/SDK/Logs/Exporter/ConsoleExporterFactoryTest.php +++ b/tests/Unit/SDK/Logs/Exporter/ConsoleExporterFactoryTest.php @@ -18,4 +18,16 @@ public function test_create(): void $this->assertInstanceOf(ConsoleExporter::class, $factory->create()); } + + public function test_type(): void + { + $factory = new ConsoleExporterFactory(); + $this->assertSame('console', $factory->type()); + } + + public function test_priority(): void + { + $factory = new ConsoleExporterFactory(); + $this->assertSame(0, $factory->priority()); + } } diff --git a/tests/Unit/SDK/Logs/Exporter/InMemoryExporterFactoryTest.php b/tests/Unit/SDK/Logs/Exporter/InMemoryExporterFactoryTest.php index 6150049bb..18d8bd5cb 100644 --- a/tests/Unit/SDK/Logs/Exporter/InMemoryExporterFactoryTest.php +++ b/tests/Unit/SDK/Logs/Exporter/InMemoryExporterFactoryTest.php @@ -17,4 +17,15 @@ public function test_create(): void $factory = new InMemoryExporterFactory(); $this->assertInstanceOf(LogRecordExporterInterface::class, $factory->create()); } + public function test_type(): void + { + $factory = new InMemoryExporterFactory(); + $this->assertSame('memory', $factory->type()); + } + + public function test_priority(): void + { + $factory = new InMemoryExporterFactory(); + $this->assertSame(0, $factory->priority()); + } } diff --git a/tests/Unit/SDK/Metrics/MetricExporter/ConsoleMetricExporterFactoryTest.php b/tests/Unit/SDK/Metrics/MetricExporter/ConsoleMetricExporterFactoryTest.php index 5087e2044..4148d89f1 100644 --- a/tests/Unit/SDK/Metrics/MetricExporter/ConsoleMetricExporterFactoryTest.php +++ b/tests/Unit/SDK/Metrics/MetricExporter/ConsoleMetricExporterFactoryTest.php @@ -17,4 +17,16 @@ public function test_create(): void $exporter = (new ConsoleMetricExporterFactory())->create(); $this->assertInstanceOf(MetricExporterInterface::class, $exporter); } + + public function test_type(): void + { + $factory = new ConsoleMetricExporterFactory(); + $this->assertSame('console', $factory->type()); + } + + public function test_priority(): void + { + $factory = new ConsoleMetricExporterFactory(); + $this->assertSame(0, $factory->priority()); + } } diff --git a/tests/Unit/SDK/Metrics/MetricExporter/InMemoryExporterFactoryTest.php b/tests/Unit/SDK/Metrics/MetricExporter/InMemoryExporterFactoryTest.php index dac703047..5a64e99e3 100644 --- a/tests/Unit/SDK/Metrics/MetricExporter/InMemoryExporterFactoryTest.php +++ b/tests/Unit/SDK/Metrics/MetricExporter/InMemoryExporterFactoryTest.php @@ -17,4 +17,16 @@ public function test_create(): void $exporter = (new InMemoryExporterFactory())->create(); $this->assertInstanceOf(MetricExporterInterface::class, $exporter); } + + public function test_type(): void + { + $factory = new InMemoryExporterFactory(); + $this->assertSame('memory', $factory->type()); + } + + public function test_priority(): void + { + $factory = new InMemoryExporterFactory(); + $this->assertSame(0, $factory->priority()); + } } diff --git a/tests/Unit/SDK/Metrics/MetricExporter/NoopMetricExporterFactoryTest.php b/tests/Unit/SDK/Metrics/MetricExporter/NoopMetricExporterFactoryTest.php index d928a5887..1dd26282f 100644 --- a/tests/Unit/SDK/Metrics/MetricExporter/NoopMetricExporterFactoryTest.php +++ b/tests/Unit/SDK/Metrics/MetricExporter/NoopMetricExporterFactoryTest.php @@ -17,4 +17,16 @@ public function test_create(): void $exporter = (new NoopMetricExporterFactory())->create(); $this->assertInstanceOf(MetricExporterInterface::class, $exporter); } + + public function test_type(): void + { + $factory = new NoopMetricExporterFactory(); + $this->assertSame('none', $factory->type()); + } + + public function test_priority(): void + { + $factory = new NoopMetricExporterFactory(); + $this->assertSame(0, $factory->priority()); + } } diff --git a/tests/Unit/SDK/Resource/ResourceInfoFactoryTest.php b/tests/Unit/SDK/Resource/ResourceInfoFactoryTest.php index bb4d6a80e..2f30e2599 100644 --- a/tests/Unit/SDK/Resource/ResourceInfoFactoryTest.php +++ b/tests/Unit/SDK/Resource/ResourceInfoFactoryTest.php @@ -9,8 +9,6 @@ use OpenTelemetry\API\Behavior\Internal\Logging; use OpenTelemetry\API\Behavior\Internal\LogWriter\LogWriterInterface; use OpenTelemetry\SDK\Common\Attribute\Attributes; -use OpenTelemetry\SDK\Registry; -use OpenTelemetry\SDK\Resource\ResourceDetectorInterface; use OpenTelemetry\SDK\Resource\ResourceInfo; use OpenTelemetry\SDK\Resource\ResourceInfoFactory; use OpenTelemetry\Tests\TestState; @@ -120,36 +118,22 @@ public function test_resource_from_environment_resource_attribute_takes_preceden $this->assertEquals('foo', $resource->getAttributes()->get('service.name')); } - public function test_resource_from_registry(): void - { - $this->setEnvironmentVariable('OTEL_PHP_DETECTORS', 'foo'); - $detector = $this->createMock(ResourceDetectorInterface::class); - $detector->expects($this->once())->method('getResource')->willReturn($this->createMock(ResourceInfo::class)); - - Registry::registerResourceDetector('foo', $detector); - ResourceInfoFactory::defaultResource(); - } - - public function test_all_resources_uses_extra_resource_from_registry(): void + public function test_all_resources_uses_extra_resource_from_spi(): void { $this->setEnvironmentVariable('OTEL_PHP_DETECTORS', 'all'); - $detector = $this->createMock(ResourceDetectorInterface::class); - $resource = $this->createMock(ResourceInfo::class); - $detector->expects($this->once())->method('getResource')->willReturn($resource); - Registry::registerResourceDetector('foo', $detector); - ResourceInfoFactory::defaultResource(); + $resource = ResourceInfoFactory::defaultResource(); + $this->assertTrue($resource->getAttributes()->has('test-resource')); + $this->assertSame('test-value', $resource->getAttributes()->get('test-resource')); } - public function test_composite_default_with_extra_resource_from_registry(): void + public function test_composite_default_with_extra_resource_from_spi(): void { - $this->setEnvironmentVariable('OTEL_PHP_DETECTORS', 'foo,env'); - $resource = $this->createMock(ResourceInfo::class); - $detector = $this->createMock(ResourceDetectorInterface::class); - $detector->expects($this->once())->method('getResource')->willReturn($resource); + $this->setEnvironmentVariable('OTEL_PHP_DETECTORS', 'test,sdk'); + $resource = ResourceInfoFactory::defaultResource(); - Registry::registerResourceDetector('foo', $detector); - ResourceInfoFactory::defaultResource(); + $this->assertTrue($resource->getAttributes()->has('test-resource')); + $this->assertTrue($resource->getAttributes()->has('telemetry.sdk.language')); } public function test_default_with_all_sdk_detectors(): void diff --git a/tests/Unit/SDK/Trace/SpanExporter/ConsoleSpanExporterFactoryTest.php b/tests/Unit/SDK/Trace/SpanExporter/ConsoleSpanExporterFactoryTest.php index 280d44c1f..002e6b582 100644 --- a/tests/Unit/SDK/Trace/SpanExporter/ConsoleSpanExporterFactoryTest.php +++ b/tests/Unit/SDK/Trace/SpanExporter/ConsoleSpanExporterFactoryTest.php @@ -17,4 +17,16 @@ public function test_create(): void $exporter = (new ConsoleSpanExporterFactory())->create(); $this->assertInstanceOf(SpanExporterInterface::class, $exporter); } + + public function test_type(): void + { + $factory = new ConsoleSpanExporterFactory(); + $this->assertSame('console', $factory->type()); + } + + public function test_priority(): void + { + $factory = new ConsoleSpanExporterFactory(); + $this->assertSame(0, $factory->priority()); + } } diff --git a/tests/Unit/SDK/Trace/SpanExporter/InMemorySpanExporterFactoryTest.php b/tests/Unit/SDK/Trace/SpanExporter/InMemorySpanExporterFactoryTest.php index dec657b28..6e6cf58de 100644 --- a/tests/Unit/SDK/Trace/SpanExporter/InMemorySpanExporterFactoryTest.php +++ b/tests/Unit/SDK/Trace/SpanExporter/InMemorySpanExporterFactoryTest.php @@ -17,4 +17,16 @@ public function test_create(): void $exporter = (new InMemorySpanExporterFactory())->create(); $this->assertInstanceOf(SpanExporterInterface::class, $exporter); } + + public function test_type(): void + { + $factory = new InMemorySpanExporterFactory(); + $this->assertSame('memory', $factory->type()); + } + + public function test_priority(): void + { + $factory = new InMemorySpanExporterFactory(); + $this->assertSame(0, $factory->priority()); + } } diff --git a/tests/_register.php b/tests/_register.php new file mode 100644 index 000000000..0bc55e402 --- /dev/null +++ b/tests/_register.php @@ -0,0 +1,19 @@ +