From 068086347b41c6d31c545cf9b37e3a9a6c1ff59f Mon Sep 17 00:00:00 2001 From: Brett McBride Date: Sat, 7 Sep 2024 08:51:46 +1000 Subject: [PATCH 1/9] fix withspan handler nullable + example (#1377) - class is nullable for pre hooks - add phpt tests for withspan and its interaction with auto root span --- src/API/Instrumentation/WithSpanHandler.php | 2 +- ...auto_root_span_withspan_localrootspan.phpt | 108 ++++++++++++++ .../Trace/test_withspan_instrumentation.phpt | 132 ++++++++++++++++++ 3 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 tests/Integration/SDK/Trace/test_auto_root_span_withspan_localrootspan.phpt create mode 100644 tests/Integration/SDK/Trace/test_withspan_instrumentation.phpt diff --git a/src/API/Instrumentation/WithSpanHandler.php b/src/API/Instrumentation/WithSpanHandler.php index f0f0dc928..979ebcd73 100644 --- a/src/API/Instrumentation/WithSpanHandler.php +++ b/src/API/Instrumentation/WithSpanHandler.php @@ -18,7 +18,7 @@ class WithSpanHandler /** * @psalm-suppress ArgumentTypeCoercion */ - public static function pre(mixed $target, array $params, string $class, string $function, ?string $filename, ?int $lineno, ?array $span_args = [], ?array $attributes = []): void + public static function pre(mixed $target, array $params, ?string $class, string $function, ?string $filename, ?int $lineno, ?array $span_args = [], ?array $attributes = []): void { static $instrumentation; $instrumentation ??= new CachedInstrumentation(name: 'io.opentelemetry.php.with-span', schemaUrl: 'https://opentelemetry.io/schemas/1.25.0'); diff --git a/tests/Integration/SDK/Trace/test_auto_root_span_withspan_localrootspan.phpt b/tests/Integration/SDK/Trace/test_auto_root_span_withspan_localrootspan.phpt new file mode 100644 index 000000000..4e4c492f2 --- /dev/null +++ b/tests/Integration/SDK/Trace/test_auto_root_span_withspan_localrootspan.phpt @@ -0,0 +1,108 @@ +--TEST-- +Tests that auto root span, withspan auto-instrumentation and local root span all work together +--SKIPIF-- += 1.1.0'); ?> +--INI-- +opentelemetry.attr_hooks_enabled = On +opentelemetry.attr_pre_handler_function = OpenTelemetry\API\Instrumentation\WithSpanHandler::pre +opentelemetry.attr_post_handler_function = OpenTelemetry\API\Instrumentation\WithSpanHandler::post +--ENV-- +OTEL_PHP_AUTOLOAD_ENABLED=true +OTEL_PHP_EXPERIMENTAL_AUTO_ROOT_SPAN=true +OTEL_TRACES_EXPORTER=console +OTEL_METRICS_EXPORTER=none +OTEL_LOGS_EXPORTER=none +OTEL_PHP_DETECTORS=none +REQUEST_METHOD=GET +REQUEST_URI=/foo?bar=baz +REQUEST_SCHEME=https +SERVER_NAME=example.com +SERVER_PORT=8080 +HTTP_HOST=example.com:8080 +HTTP_USER_AGENT=my-user-agent/1.0 +REQUEST_TIME_FLOAT=1721706151.242976 +HTTP_TRACEPARENT=00-ff000000000000000000000000000041-ff00000000000041-01 +--FILE-- +updateName('GET updated-name'); + +#[WithSpan] +function foo( + #[SpanAttribute] string $word +): void +{ + //do nothing +} +//"word" -> "bar" should appear as a span attribute +foo('bar'); +?> +--EXPECTF-- +%A +[ + { + "name": "foo", + "context": { + "trace_id": "ff000000000000000000000000000041", + "span_id": "%s", + "trace_state": "", + "trace_flags": 1 + }, + "resource": [], + "parent_span_id": "%s", + "kind": "KIND_INTERNAL", + "start": %d, + "end": %d, + "attributes": { + "code.function": "foo", + "code.filepath": "%s", + "code.lineno": %d, + "word": "bar" + }, + "status": { + "code": "Unset", + "description": "" + }, + "events": [], + "links": [], + "schema_url": "%s" + } +] +[ + { + "name": "GET updated-name", + "context": { + "trace_id": "ff000000000000000000000000000041", + "span_id": "%s", + "trace_state": "", + "trace_flags": 1 + }, + "resource": [], + "parent_span_id": "ff00000000000041", + "kind": "KIND_SERVER", + "start": %d, + "end": %d, + "attributes": { + "url.full": "%s", + "http.request.method": "GET", + "http.request.body.size": "", + "user_agent.original": "my-user-agent\/1.0", + "server.address": "%S", + "server.port": %d, + "url.scheme": "https", + "url.path": "\/foo" + }, + "status": { + "code": "Unset", + "description": "" + }, + "events": [], + "links": [], + "schema_url": "%s" + } +] diff --git a/tests/Integration/SDK/Trace/test_withspan_instrumentation.phpt b/tests/Integration/SDK/Trace/test_withspan_instrumentation.phpt new file mode 100644 index 000000000..b7a11ee2f --- /dev/null +++ b/tests/Integration/SDK/Trace/test_withspan_instrumentation.phpt @@ -0,0 +1,132 @@ +--TEST-- +Auto root span creation +--SKIPIF-- += 1.1.0'); ?> +--INI-- +opentelemetry.attr_hooks_enabled = On +--ENV-- +OTEL_PHP_AUTOLOAD_ENABLED=true +OTEL_TRACES_EXPORTER=console +OTEL_METRICS_EXPORTER=none +OTEL_LOGS_EXPORTER=none +OTEL_PHP_DETECTORS=none +--FILE-- + +--EXPECTF-- +%A +string(10) "foo::start" +string(8) "foo::end" +[ + { + "name": "TestClass::baz", + "context": { + "trace_id": "%s", + "span_id": "%s", + "trace_state": "", + "trace_flags": 1 + }, + "resource": [], + "parent_span_id": "%s", + "kind": "KIND_INTERNAL", + "start": %d, + "end": %d, + "attributes": { + "code.function": "baz", + "code.namespace": "TestClass", + "code.filepath": "Standard input code", + "code.lineno": %d + }, + "status": { + "code": "Unset", + "description": "" + }, + "events": [], + "links": [], + "schema_url": "https:\/\/opentelemetry.io\/schemas\/%d.%d.%d" + } +] +[ + { + "name": "TestClass::bar", + "context": { + "trace_id": "%s", + "span_id": "%s", + "trace_state": "", + "trace_flags": 1 + }, + "resource": [], + "parent_span_id": "%s", + "kind": "KIND_INTERNAL", + "start": %d, + "end": %d, + "attributes": { + "code.function": "bar", + "code.namespace": "TestClass", + "code.filepath": "Standard input code", + "code.lineno": %d + }, + "status": { + "code": "Unset", + "description": "" + }, + "events": [], + "links": [], + "schema_url": "https:\/\/opentelemetry.io\/schemas\/%d.%d.%d" + } +] +[ + { + "name": "foo", + "context": { + "trace_id": "%s", + "span_id": "%s", + "trace_state": "", + "trace_flags": 1 + }, + "resource": [], + "parent_span_id": "", + "kind": "KIND_INTERNAL", + "start": %d, + "end": %d, + "attributes": { + "code.function": "foo", + "code.filepath": "Standard input code", + "code.lineno": %d + }, + "status": { + "code": "Unset", + "description": "" + }, + "events": [], + "links": [], + "schema_url": "https:\/\/opentelemetry.io\/schemas\/%d.%d.%d" + } +] From 3be58cff57f7ed7d5b7e989b58b503c7e0802eaf Mon Sep 17 00:00:00 2001 From: Alex Boten <223565+codeboten@users.noreply.github.com> Date: Sun, 22 Sep 2024 00:42:55 -0700 Subject: [PATCH 2/9] fix: update references to logging exporter (#1383) This exporter has been replaced by the debug exporter and will be removed soon. Related to https://github.com/open-telemetry/opentelemetry-collector/pull/11037 Signed-off-by: Alex Boten <223565+codeboten@users.noreply.github.com> --- examples/traces/demo/collector/config.yaml | 6 +++--- files/collector/otel-collector-config.yml | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/traces/demo/collector/config.yaml b/examples/traces/demo/collector/config.yaml index 5bd8acec6..c54c3d85b 100644 --- a/examples/traces/demo/collector/config.yaml +++ b/examples/traces/demo/collector/config.yaml @@ -4,8 +4,8 @@ receivers: grpc: exporters: - logging: - logLevel: debug + debug: + verbosity: detailed zipkin: endpoint: "http://zipkin:9411/api/v2/spans" jaeger: @@ -19,6 +19,6 @@ service: receivers: - otlp exporters: - - logging + - debug - zipkin - jaeger diff --git a/files/collector/otel-collector-config.yml b/files/collector/otel-collector-config.yml index db6d243c1..9d815ee3a 100644 --- a/files/collector/otel-collector-config.yml +++ b/files/collector/otel-collector-config.yml @@ -8,7 +8,7 @@ receivers: exporters: zipkin: endpoint: "http://zipkin:9411/api/v2/spans" - logging: + debug: verbosity: detailed processors: @@ -27,12 +27,12 @@ service: pipelines: traces: receivers: [otlp, zipkin] - exporters: [logging] + exporters: [debug] processors: [batch] metrics: receivers: [otlp] - exporters: [logging] + exporters: [debug] logs: receivers: [ otlp ] processors: [ batch ] - exporters: [ logging ] + exporters: [ debug ] From 8ed62b1e4355162d7dc3a82ab61696fd58b8c016 Mon Sep 17 00:00:00 2001 From: Jamie Danielson Date: Mon, 23 Sep 2024 18:35:18 -0400 Subject: [PATCH 3/9] fix: Update collector tag in example to latest (#1384) Follow-up from #1383. Debug exporter isn't available until v0.86.0, so this new configuration throws an error when trying to run the collector. `Error: cannot unmarshal the configuration: unknown exporters type "debug" for "debug"` Instead of setting to v0.86.0, I noticed the other docker files omitted the tag (so latest is pulled). I just updated this to match. --- examples/traces/demo/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/traces/demo/docker-compose.yml b/examples/traces/demo/docker-compose.yml index a0d8a0b37..aac546baf 100644 --- a/examples/traces/demo/docker-compose.yml +++ b/examples/traces/demo/docker-compose.yml @@ -41,7 +41,7 @@ services: OTEL_SERVICE_NAME: service-three collector: - image: otel/opentelemetry-collector-contrib:0.39.0 + image: otel/opentelemetry-collector-contrib volumes: - './collector:/etc/otel' ports: From 6a2242d46a98e1420f8610bacbddf74b8b8c8b37 Mon Sep 17 00:00:00 2001 From: Tobias Bachert Date: Wed, 25 Sep 2024 01:58:09 +0200 Subject: [PATCH 4/9] Remove `MeterInterface::isEnabled()` and fix meter config re-enabling (#1387) --- src/API/Metrics/LateBindingMeter.php | 5 - src/API/Metrics/MeterInterface.php | 2 - src/API/Metrics/Noop/NoopMeter.php | 5 - src/SDK/Metrics/Instrument.php | 8 - src/SDK/Metrics/Meter.php | 140 ++++++++++++------ src/SDK/Metrics/MeterInstruments.php | 4 +- src/SDK/Metrics/MeterProvider.php | 2 +- .../MetricFactory/StreamMetricSource.php | 5 - .../Metrics/MetricReader/ExportingReader.php | 47 +++--- .../Metrics/MetricRegistry/MetricRegistry.php | 36 ++--- .../MetricRegistryInterface.php | 2 +- src/SDK/Metrics/MetricSourceInterface.php | 1 - ...etricSourceRegistryUnregisterInterface.php | 18 +++ src/SDK/Metrics/ObservableInstrumentTrait.php | 6 - src/SDK/Metrics/RegisteredInstrument.php | 17 +++ .../Metrics/SynchronousInstrumentTrait.php | 9 +- .../SDK/Metrics/MeterConfigTest.php | 16 +- tests/Unit/SDK/Metrics/InstrumentTest.php | 43 ++---- tests/Unit/SDK/Metrics/MeterProviderTest.php | 4 +- tests/Unit/SDK/Metrics/MeterTest.php | 37 ++--- .../MetricReader/ExportingReaderTest.php | 2 - .../Metrics/View/SelectionCriteriaTest.php | 13 +- 22 files changed, 209 insertions(+), 213 deletions(-) create mode 100644 src/SDK/Metrics/MetricSourceRegistryUnregisterInterface.php create mode 100644 src/SDK/Metrics/RegisteredInstrument.php diff --git a/src/API/Metrics/LateBindingMeter.php b/src/API/Metrics/LateBindingMeter.php index 18dd1112d..b146bfbbb 100644 --- a/src/API/Metrics/LateBindingMeter.php +++ b/src/API/Metrics/LateBindingMeter.php @@ -58,9 +58,4 @@ public function createObservableUpDownCounter(string $name, ?string $unit = null { return ($this->meter ??= ($this->factory)())->createObservableUpDownCounter($name, $unit, $description, $advisory, $callbacks); } - - public function isEnabled(): bool - { - return true; - } } diff --git a/src/API/Metrics/MeterInterface.php b/src/API/Metrics/MeterInterface.php index f8f0b9c69..fa12e5814 100644 --- a/src/API/Metrics/MeterInterface.php +++ b/src/API/Metrics/MeterInterface.php @@ -178,6 +178,4 @@ public function createObservableUpDownCounter( array|callable $advisory = [], callable ...$callbacks, ): ObservableUpDownCounterInterface; - - public function isEnabled(): bool; } diff --git a/src/API/Metrics/Noop/NoopMeter.php b/src/API/Metrics/Noop/NoopMeter.php index 63671c800..25ae9f864 100644 --- a/src/API/Metrics/Noop/NoopMeter.php +++ b/src/API/Metrics/Noop/NoopMeter.php @@ -56,9 +56,4 @@ public function createObservableUpDownCounter(string $name, ?string $unit = null { return new NoopObservableUpDownCounter(); } - - public function isEnabled(): bool - { - return false; - } } diff --git a/src/SDK/Metrics/Instrument.php b/src/SDK/Metrics/Instrument.php index efacb8614..8d9a5b363 100644 --- a/src/SDK/Metrics/Instrument.php +++ b/src/SDK/Metrics/Instrument.php @@ -4,8 +4,6 @@ namespace OpenTelemetry\SDK\Metrics; -use OpenTelemetry\API\Metrics\MeterInterface; - final class Instrument { public function __construct( @@ -14,12 +12,6 @@ public function __construct( public readonly ?string $unit, public readonly ?string $description, public readonly array $advisory = [], - public readonly ?MeterInterface $meter = null, ) { } - - public function isEnabled(): bool - { - return $this->meter?->isEnabled() ?? true; - } } diff --git a/src/SDK/Metrics/Meter.php b/src/SDK/Metrics/Meter.php index 40eb129ae..0f4ccebc0 100644 --- a/src/SDK/Metrics/Meter.php +++ b/src/SDK/Metrics/Meter.php @@ -61,9 +61,9 @@ public function __construct( private readonly MetricRegistryInterface $registry, private readonly MetricWriterInterface $writer, private readonly ArrayAccess $destructors, - private ?Configurator $configurator = null, + ?Configurator $configurator = null, ) { - $this->config = $this->configurator?->resolve($this->instrumentationScope) ?? MeterConfig::default(); + $this->config = $configurator?->resolve($this->instrumentationScope) ?? MeterConfig::default(); } private static function dummyInstrument(): Instrument @@ -78,8 +78,44 @@ private static function dummyInstrument(): Instrument */ public function updateConfigurator(Configurator $configurator): void { - $this->configurator = $configurator; $this->config = $configurator->resolve($this->instrumentationScope); + + $startTimestamp = $this->clock->now(); + foreach ($this->instruments->observers[self::instrumentationScopeId($this->instrumentationScope)] as [$instrument, $stalenessHandler, $r]) { + if ($this->config->isEnabled() && $r->dormant) { + $this->metricFactory->createAsynchronousObserver( + $this->registry, + $this->resource, + $this->instrumentationScope, + $instrument, + $startTimestamp, + $this->viewRegistrationRequests($instrument, $stalenessHandler), + ); + $r->dormant = false; + } + if (!$this->config->isEnabled() && !$r->dormant) { + $this->releaseStreams($instrument); + $r->dormant = true; + } + } + foreach ($this->instruments->writers[self::instrumentationScopeId($this->instrumentationScope)] as [$instrument, $stalenessHandler, $r]) { + if ($this->config->isEnabled() && $r->dormant) { + $this->metricFactory->createSynchronousWriter( + $this->registry, + $this->resource, + $this->instrumentationScope, + $instrument, + $startTimestamp, + $this->viewRegistrationRequests($instrument, $stalenessHandler), + $this->exemplarFilter, + ); + $r->dormant = false; + } + if (!$this->config->isEnabled() && !$r->dormant) { + $this->releaseStreams($instrument); + $r->dormant = true; + } + } } public function batchObserve(callable $callback, AsynchronousInstrument $instrument, AsynchronousInstrument ...$instruments): ObservableCallbackInterface @@ -131,7 +167,7 @@ public function createCounter(string $name, ?string $unit = null, ?string $descr $advisory, ); - return new Counter($this->writer, $instrument, $referenceCounter, $this); + return new Counter($this->writer, $instrument, $referenceCounter); } public function createObservableCounter(string $name, ?string $unit = null, ?string $description = null, $advisory = [], callable ...$callbacks): ObservableCounterInterface @@ -153,7 +189,7 @@ public function createObservableCounter(string $name, ?string $unit = null, ?str $referenceCounter->acquire(true); } - return new ObservableCounter($this->writer, $instrument, $referenceCounter, $this->destructors, $this); + return new ObservableCounter($this->writer, $instrument, $referenceCounter, $this->destructors); } public function createHistogram(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): HistogramInterface @@ -166,7 +202,7 @@ public function createHistogram(string $name, ?string $unit = null, ?string $des $advisory, ); - return new Histogram($this->writer, $instrument, $referenceCounter, $this); + return new Histogram($this->writer, $instrument, $referenceCounter); } public function createGauge(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): GaugeInterface @@ -179,7 +215,7 @@ public function createGauge(string $name, ?string $unit = null, ?string $descrip $advisory, ); - return new Gauge($this->writer, $instrument, $referenceCounter, $this); + return new Gauge($this->writer, $instrument, $referenceCounter); } public function createObservableGauge(string $name, ?string $unit = null, ?string $description = null, $advisory = [], callable ...$callbacks): ObservableGaugeInterface @@ -201,7 +237,7 @@ public function createObservableGauge(string $name, ?string $unit = null, ?strin $referenceCounter->acquire(true); } - return new ObservableGauge($this->writer, $instrument, $referenceCounter, $this->destructors, $this); + return new ObservableGauge($this->writer, $instrument, $referenceCounter, $this->destructors); } public function createUpDownCounter(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): UpDownCounterInterface @@ -214,7 +250,7 @@ public function createUpDownCounter(string $name, ?string $unit = null, ?string $advisory, ); - return new UpDownCounter($this->writer, $instrument, $referenceCounter, $this); + return new UpDownCounter($this->writer, $instrument, $referenceCounter); } public function createObservableUpDownCounter(string $name, ?string $unit = null, ?string $description = null, $advisory = [], callable ...$callbacks): ObservableUpDownCounterInterface @@ -236,16 +272,11 @@ public function createObservableUpDownCounter(string $name, ?string $unit = null $referenceCounter->acquire(true); } - return new ObservableUpDownCounter($this->writer, $instrument, $referenceCounter, $this->destructors, $this); - } - - public function isEnabled(): bool - { - return $this->config->isEnabled(); + return new ObservableUpDownCounter($this->writer, $instrument, $referenceCounter, $this->destructors); } /** - * @return array{Instrument, ReferenceCounterInterface}|null + * @return array{Instrument, ReferenceCounterInterface, RegisteredInstrument}|null */ private function getAsynchronousInstrument(Instrument $instrument, InstrumentationScopeInterface $instrumentationScope): ?array { @@ -261,11 +292,11 @@ private function getAsynchronousInstrument(Instrument $instrument, Instrumentati } /** - * @return array{Instrument, ReferenceCounterInterface} + * @return array{Instrument, ReferenceCounterInterface, RegisteredInstrument} */ private function createSynchronousWriter(string|InstrumentType $instrumentType, string $name, ?string $unit, ?string $description, array $advisory = []): array { - $instrument = new Instrument($instrumentType, $name, $unit, $description, $advisory, $this); + $instrument = new Instrument($instrumentType, $name, $unit, $description, $advisory); $instrumentationScopeId = $this->instrumentationScopeId($this->instrumentationScope); $instrumentId = $this->instrumentId($instrument); @@ -276,26 +307,25 @@ private function createSynchronousWriter(string|InstrumentType $instrumentType, } $stalenessHandler = $this->stalenessHandlerFactory->create(); - $instruments->startTimestamp ??= $this->clock->now(); - $streamIds = $this->metricFactory->createSynchronousWriter( - $this->registry, - $this->resource, - $this->instrumentationScope, - $instrument, - $instruments->startTimestamp, - $this->viewRegistrationRequests($instrument, $stalenessHandler), - $this->exemplarFilter, - ); + if ($this->config->isEnabled()) { + $instruments->startTimestamp ??= $this->clock->now(); + $this->metricFactory->createSynchronousWriter( + $this->registry, + $this->resource, + $this->instrumentationScope, + $instrument, + $instruments->startTimestamp, + $this->viewRegistrationRequests($instrument, $stalenessHandler), + $this->exemplarFilter, + ); + } - $registry = $this->registry; - $stalenessHandler->onStale(static function () use ($instruments, $instrumentationScopeId, $instrumentId, $registry, $streamIds): void { + $stalenessHandler->onStale(fn () => $this->releaseStreams($instrument)); + $stalenessHandler->onStale(static function () use ($instruments, $instrumentationScopeId, $instrumentId): void { unset($instruments->writers[$instrumentationScopeId][$instrumentId]); if (!$instruments->writers[$instrumentationScopeId]) { unset($instruments->writers[$instrumentationScopeId]); } - foreach ($streamIds as $streamId) { - $registry->unregisterStream($streamId); - } $instruments->startTimestamp = null; }); @@ -303,15 +333,16 @@ private function createSynchronousWriter(string|InstrumentType $instrumentType, return $instruments->writers[$instrumentationScopeId][$instrumentId] = [ $instrument, $stalenessHandler, + new RegisteredInstrument(!$this->config->isEnabled(), $this), ]; } /** - * @return array{Instrument, ReferenceCounterInterface} + * @return array{Instrument, ReferenceCounterInterface, RegisteredInstrument} */ private function createAsynchronousObserver(string|InstrumentType $instrumentType, string $name, ?string $unit, ?string $description, array $advisory): array { - $instrument = new Instrument($instrumentType, $name, $unit, $description, $advisory, $this); + $instrument = new Instrument($instrumentType, $name, $unit, $description, $advisory); $instrumentationScopeId = $this->instrumentationScopeId($this->instrumentationScope); $instrumentId = $this->instrumentId($instrument); @@ -322,25 +353,24 @@ private function createAsynchronousObserver(string|InstrumentType $instrumentTyp } $stalenessHandler = $this->stalenessHandlerFactory->create(); - $instruments->startTimestamp ??= $this->clock->now(); - $streamIds = $this->metricFactory->createAsynchronousObserver( - $this->registry, - $this->resource, - $this->instrumentationScope, - $instrument, - $instruments->startTimestamp, - $this->viewRegistrationRequests($instrument, $stalenessHandler), - ); + if ($this->config->isEnabled()) { + $instruments->startTimestamp ??= $this->clock->now(); + $this->metricFactory->createAsynchronousObserver( + $this->registry, + $this->resource, + $this->instrumentationScope, + $instrument, + $instruments->startTimestamp, + $this->viewRegistrationRequests($instrument, $stalenessHandler), + ); + } - $registry = $this->registry; - $stalenessHandler->onStale(static function () use ($instruments, $instrumentationScopeId, $instrumentId, $registry, $streamIds): void { + $stalenessHandler->onStale(fn () => $this->releaseStreams($instrument)); + $stalenessHandler->onStale(static function () use ($instruments, $instrumentationScopeId, $instrumentId): void { unset($instruments->observers[$instrumentationScopeId][$instrumentId]); if (!$instruments->observers[$instrumentationScopeId]) { unset($instruments->observers[$instrumentationScopeId]); } - foreach ($streamIds as $streamId) { - $registry->unregisterStream($streamId); - } $instruments->startTimestamp = null; }); @@ -348,9 +378,21 @@ private function createAsynchronousObserver(string|InstrumentType $instrumentTyp return $instruments->observers[$instrumentationScopeId][$instrumentId] = [ $instrument, $stalenessHandler, + new RegisteredInstrument(!$this->config->isEnabled(), $this), ]; } + private function releaseStreams(Instrument $instrument): void + { + foreach ($this->registry->unregisterStreams($instrument) as $streamId) { + foreach ($this->metricRegistries as $metricRegistry) { + if ($metricRegistry instanceof MetricSourceRegistryUnregisterInterface) { + $metricRegistry->unregisterStream($this->registry, $streamId); + } + } + } + } + /** * @return iterable */ diff --git a/src/SDK/Metrics/MeterInstruments.php b/src/SDK/Metrics/MeterInstruments.php index 2070f0860..8d97809c9 100644 --- a/src/SDK/Metrics/MeterInstruments.php +++ b/src/SDK/Metrics/MeterInstruments.php @@ -11,11 +11,11 @@ final class MeterInstruments { public ?int $startTimestamp = null; /** - * @var array> + * @var array> */ public array $observers = []; /** - * @var array> + * @var array> */ public array $writers = []; } diff --git a/src/SDK/Metrics/MeterProvider.php b/src/SDK/Metrics/MeterProvider.php index 543601e7f..efd2b4b7b 100644 --- a/src/SDK/Metrics/MeterProvider.php +++ b/src/SDK/Metrics/MeterProvider.php @@ -128,7 +128,7 @@ public static function builder(): MeterProviderBuilder /** * Update the {@link Configurator} for a {@link MeterProvider}, which will reconfigure * all meters created from the provider. - * @todo enabling a previous-disabled meter does not drop/recreate the underlying metric streams, so previously collected synchronous metrics will still be exported. + * * @experimental */ public function updateConfigurator(Configurator $configurator): void diff --git a/src/SDK/Metrics/MetricFactory/StreamMetricSource.php b/src/SDK/Metrics/MetricFactory/StreamMetricSource.php index a96c0e7e4..5b105a5a5 100644 --- a/src/SDK/Metrics/MetricFactory/StreamMetricSource.php +++ b/src/SDK/Metrics/MetricFactory/StreamMetricSource.php @@ -39,9 +39,4 @@ public function __destruct() { $this->provider->stream->unregister($this->reader); } - - public function isEnabled(): bool - { - return $this->provider->instrument->isEnabled(); - } } diff --git a/src/SDK/Metrics/MetricReader/ExportingReader.php b/src/SDK/Metrics/MetricReader/ExportingReader.php index 45585689b..b1230d714 100644 --- a/src/SDK/Metrics/MetricReader/ExportingReader.php +++ b/src/SDK/Metrics/MetricReader/ExportingReader.php @@ -17,11 +17,12 @@ use OpenTelemetry\SDK\Metrics\MetricSourceInterface; use OpenTelemetry\SDK\Metrics\MetricSourceProviderInterface; use OpenTelemetry\SDK\Metrics\MetricSourceRegistryInterface; +use OpenTelemetry\SDK\Metrics\MetricSourceRegistryUnregisterInterface; use OpenTelemetry\SDK\Metrics\PushMetricExporterInterface; use OpenTelemetry\SDK\Metrics\StalenessHandlerInterface; use function spl_object_id; -final class ExportingReader implements MetricReaderInterface, MetricSourceRegistryInterface, DefaultAggregationProviderInterface +final class ExportingReader implements MetricReaderInterface, MetricSourceRegistryInterface, MetricSourceRegistryUnregisterInterface, DefaultAggregationProviderInterface { use DefaultAggregationProviderTrait { defaultAggregation as private _defaultAggregation; } /** @var array */ @@ -29,7 +30,7 @@ final class ExportingReader implements MetricReaderInterface, MetricSourceRegist /** @var array */ private array $registries = []; - /** @var array> */ + /** @var array>> */ private array $streamIds = []; private bool $closed = false; @@ -64,11 +65,11 @@ public function add(MetricSourceProviderInterface $provider, MetricMetadataInter $sourceId = spl_object_id($source); $this->sources[$sourceId] = $source; - $stalenessHandler->onStale(function () use ($sourceId): void { - unset($this->sources[$sourceId]); - }); - if (!$provider instanceof StreamMetricSourceProvider) { + $stalenessHandler->onStale(function () use ($sourceId): void { + unset($this->sources[$sourceId]); + }); + return; } @@ -77,20 +78,22 @@ public function add(MetricSourceProviderInterface $provider, MetricMetadataInter $registryId = spl_object_id($registry); $this->registries[$registryId] = $registry; - $this->streamIds[$registryId][$streamId] ??= 0; - $this->streamIds[$registryId][$streamId]++; - - $stalenessHandler->onStale(function () use ($streamId, $registryId): void { - if (!--$this->streamIds[$registryId][$streamId]) { - unset($this->streamIds[$registryId][$streamId]); - if (!$this->streamIds[$registryId]) { - unset( - $this->registries[$registryId], - $this->streamIds[$registryId], - ); - } - } - }); + $this->streamIds[$registryId][$streamId][] = $sourceId; + } + + public function unregisterStream(MetricCollectorInterface $collector, int $streamId): void + { + $registryId = spl_object_id($collector); + foreach ($this->streamIds[$registryId][$streamId] ?? [] as $sourceId) { + unset($this->sources[$sourceId]); + } + unset($this->streamIds[$registryId][$streamId]); + if (!$this->streamIds[$registryId]) { + unset( + $this->registries[$registryId], + $this->streamIds[$registryId], + ); + } } private function doCollect(): bool @@ -102,9 +105,7 @@ private function doCollect(): bool $metrics = []; foreach ($this->sources as $source) { - if ($source->isEnabled()) { - $metrics[] = $source->collect(); - } + $metrics[] = $source->collect(); } if ($metrics === []) { diff --git a/src/SDK/Metrics/MetricRegistry/MetricRegistry.php b/src/SDK/Metrics/MetricRegistry/MetricRegistry.php index 2c553419e..ea68865a3 100644 --- a/src/SDK/Metrics/MetricRegistry/MetricRegistry.php +++ b/src/SDK/Metrics/MetricRegistry/MetricRegistry.php @@ -38,8 +38,6 @@ final class MetricRegistry implements MetricRegistryInterface, MetricWriterInter private array $asynchronousCallbacks = []; /** @var array> */ private array $asynchronousCallbackArguments = []; - /** @var array */ - private array $instruments = []; public function __construct( private readonly ?ContextStorageInterface $contextStorage, @@ -57,7 +55,6 @@ public function registerSynchronousStream(Instrument $instrument, MetricStreamIn $this->synchronousAggregators[$streamId] = $aggregator; $this->instrumentToStreams[$instrumentId][$streamId] = $streamId; $this->streamToInstrument[$streamId] = $instrumentId; - $this->instruments[$instrumentId] = $instrument; return $streamId; } @@ -71,25 +68,26 @@ public function registerAsynchronousStream(Instrument $instrument, MetricStreamI $this->asynchronousAggregatorFactories[$streamId] = $aggregatorFactory; $this->instrumentToStreams[$instrumentId][$streamId] = $streamId; $this->streamToInstrument[$streamId] = $instrumentId; - $this->instruments[$instrumentId] = $instrument; return $streamId; } - public function unregisterStream(int $streamId): void + public function unregisterStreams(Instrument $instrument): array { - $instrumentId = $this->streamToInstrument[$streamId]; - unset( - $this->streams[$streamId], - $this->synchronousAggregators[$streamId], - $this->asynchronousAggregatorFactories[$streamId], - $this->instrumentToStreams[$instrumentId][$streamId], - $this->streamToInstrument[$streamId], - $this->instruments[$instrumentId], - ); - if (!$this->instrumentToStreams[$instrumentId]) { - unset($this->instrumentToStreams[$instrumentId]); + $instrumentId = spl_object_id($instrument); + $streamIds = $this->instrumentToStreams[$instrumentId] ?? []; + + foreach ($streamIds as $streamId) { + unset( + $this->streams[$streamId], + $this->synchronousAggregators[$streamId], + $this->asynchronousAggregatorFactories[$streamId], + $this->streamToInstrument[$streamId], + ); } + unset($this->instrumentToStreams[$instrumentId]); + + return $streamIds; } public function record(Instrument $instrument, $value, iterable $attributes = [], $context = null): void @@ -145,12 +143,6 @@ public function collectAndPush(iterable $streamIds): void $callbackIds = []; foreach ($streamIds as $streamId) { $instrumentId = $this->streamToInstrument[$streamId]; - if ( - array_key_exists($instrumentId, $this->instruments) - && $this->instruments[$instrumentId]->meter?->isEnabled() === false - ) { - continue; - } if (!$aggregator = $this->synchronousAggregators[$streamId] ?? null) { $aggregator = $this->asynchronousAggregatorFactories[$streamId]->create(); diff --git a/src/SDK/Metrics/MetricRegistry/MetricRegistryInterface.php b/src/SDK/Metrics/MetricRegistry/MetricRegistryInterface.php index e86731138..21b0887f7 100644 --- a/src/SDK/Metrics/MetricRegistry/MetricRegistryInterface.php +++ b/src/SDK/Metrics/MetricRegistry/MetricRegistryInterface.php @@ -18,5 +18,5 @@ public function registerSynchronousStream(Instrument $instrument, MetricStreamIn public function registerAsynchronousStream(Instrument $instrument, MetricStreamInterface $stream, MetricAggregatorFactoryInterface $aggregatorFactory): int; - public function unregisterStream(int $streamId): void; + public function unregisterStreams(Instrument $instrument): array; } diff --git a/src/SDK/Metrics/MetricSourceInterface.php b/src/SDK/Metrics/MetricSourceInterface.php index ba91e776f..5f00a0717 100644 --- a/src/SDK/Metrics/MetricSourceInterface.php +++ b/src/SDK/Metrics/MetricSourceInterface.php @@ -21,5 +21,4 @@ public function collectionTimestamp(): int; * @return Metric collected metric */ public function collect(): Metric; - public function isEnabled(): bool; } diff --git a/src/SDK/Metrics/MetricSourceRegistryUnregisterInterface.php b/src/SDK/Metrics/MetricSourceRegistryUnregisterInterface.php new file mode 100644 index 000000000..543939752 --- /dev/null +++ b/src/SDK/Metrics/MetricSourceRegistryUnregisterInterface.php @@ -0,0 +1,18 @@ +meter->isEnabled()) { - return false; - } - return $this->writer->enabled($this->instrument); } } diff --git a/src/SDK/Metrics/RegisteredInstrument.php b/src/SDK/Metrics/RegisteredInstrument.php new file mode 100644 index 000000000..efd4c00ff --- /dev/null +++ b/src/SDK/Metrics/RegisteredInstrument.php @@ -0,0 +1,17 @@ +writer = $writer; $this->instrument = $instrument; $this->referenceCounter = $referenceCounter; - $this->meter = $meter; $this->referenceCounter->acquire(); } @@ -49,10 +46,6 @@ public function write($amount, iterable $attributes = [], $context = null): void public function isEnabled(): bool { - if (!$this->meter->isEnabled()) { - return false; - } - return $this->writer->enabled($this->instrument); } } diff --git a/tests/Integration/SDK/Metrics/MeterConfigTest.php b/tests/Integration/SDK/Metrics/MeterConfigTest.php index 63ca4e408..439b9fea7 100644 --- a/tests/Integration/SDK/Metrics/MeterConfigTest.php +++ b/tests/Integration/SDK/Metrics/MeterConfigTest.php @@ -7,6 +7,7 @@ use OpenTelemetry\API\Common\Time\TestClock; use OpenTelemetry\API\Metrics\ObserverInterface; use OpenTelemetry\SDK\Common\InstrumentationScope\Configurator; +use OpenTelemetry\SDK\Metrics\Data\Sum; use OpenTelemetry\SDK\Metrics\Data\Temporality; use OpenTelemetry\SDK\Metrics\MeterConfig; use OpenTelemetry\SDK\Metrics\MeterProvider; @@ -52,13 +53,13 @@ public function test_disable_scopes(): void $this->assertFalse($instrument->isEnabled(), sprintf('instrument %s is disabled', $id)); } - $this->assertTrue($meter_one->isEnabled()); - $this->assertFalse($meter_two->isEnabled()); - $this->assertTrue($meter_three->isEnabled()); + $this->assertTrue($meter_one->createCounter('test')->isEnabled()); + $this->assertFalse($meter_two->createCounter('test')->isEnabled()); + $this->assertTrue($meter_three->createCounter('test')->isEnabled()); $meterProvider->updateConfigurator(Configurator::meter()); - $this->assertTrue($meter_two->isEnabled()); + $this->assertTrue($meter_two->createCounter('test')->isEnabled()); foreach ($instruments as $instrument) { $this->assertTrue($instrument->isEnabled()); @@ -81,7 +82,7 @@ public function test_metrics_not_exported_when_disabled(): void ) ->build(); $meter = $meterProvider->getMeter('test'); - $this->assertFalse($meter->isEnabled()); + $this->assertFalse($meter->createCounter('test')->isEnabled()); $counter = $meter->createCounter('a'); $async_counter = $meter->createObservableCounter('b', callbacks: function (ObserverInterface $o) { $this->fail('observer from disabled meter should not have been called'); @@ -101,10 +102,9 @@ public function test_metrics_not_exported_when_disabled(): void */ public function test_streams_recreated_on_enable(): void { - $this->markTestSkipped('TODO implement drop/create streams'); // @phpstan-ignore-next-line $clock = new TestClock(self::T0); $disabledConfigurator = Configurator::meter() - ->with(static fn (MeterConfig $config) => $config->setDisabled(false), name: '*'); + ->with(static fn (MeterConfig $config) => $config->setDisabled(true), name: '*'); $exporter = new InMemoryExporter(Temporality::CUMULATIVE); $reader = new ExportingReader($exporter); $meterProvider = MeterProvider::builder() @@ -132,6 +132,8 @@ public function test_streams_recreated_on_enable(): void $this->assertCount(1, $metrics); $metric = $metrics[0]; + $this->assertInstanceOf(Sum::class, $metric->data); + $this->assertIsArray($metric->data->dataPoints); $this->assertCount(1, $metric->data->dataPoints); $dataPoint = $metric->data->dataPoints[0]; diff --git a/tests/Unit/SDK/Metrics/InstrumentTest.php b/tests/Unit/SDK/Metrics/InstrumentTest.php index a5620fd27..3f1a5acb1 100644 --- a/tests/Unit/SDK/Metrics/InstrumentTest.php +++ b/tests/Unit/SDK/Metrics/InstrumentTest.php @@ -5,7 +5,6 @@ namespace OpenTelemetry\Tests\Unit\SDK\Metrics; use OpenTelemetry\API\Common\Time\TestClock; -use OpenTelemetry\API\Metrics\MeterInterface; use OpenTelemetry\API\Metrics\ObserverInterface; use OpenTelemetry\SDK\Common\Attribute\Attributes; use OpenTelemetry\SDK\Metrics\Aggregation\ExplicitBucketHistogramAggregation; @@ -41,24 +40,16 @@ #[CoversClass(ObservableInstrumentTrait::class)] final class InstrumentTest extends TestCase { - private MeterInterface $meter; - - public function setUp(): void - { - $this->meter = $this->createMock(MeterInterface::class); - $this->meter->method('isEnabled')->willReturn(true); - } - public function test_counter(): void { $a = new MetricAggregator(null, new SumAggregation(true)); $s = new SynchronousMetricStream(new SumAggregation(true), 0); $w = new MetricRegistry(null, Attributes::factory(), new TestClock(1)); - $i = new Instrument(InstrumentType::COUNTER, 'test', null, null, meter: $this->meter); + $i = new Instrument(InstrumentType::COUNTER, 'test', null, null); $n = $w->registerSynchronousStream($i, $s, $a); $r = $s->register(Temporality::DELTA); - $c = new Counter($w, $i, new NoopStalenessHandler(), $this->meter); + $c = new Counter($w, $i, new NoopStalenessHandler()); $c->add(5); $c->add(7); $c->add(3); @@ -83,11 +74,11 @@ public function test_asynchronous_counter(): void $a = new MetricAggregatorFactory(null, new SumAggregation(true)); $s = new SynchronousMetricStream(new SumAggregation(true), 0); $w = new MetricRegistry(null, Attributes::factory(), new TestClock(1)); - $i = new Instrument(InstrumentType::ASYNCHRONOUS_COUNTER, 'test', null, null, meter: $this->meter); + $i = new Instrument(InstrumentType::ASYNCHRONOUS_COUNTER, 'test', null, null); $n = $w->registerAsynchronousStream($i, $s, $a); $r = $s->register(Temporality::CUMULATIVE); - $c = new ObservableCounter($w, $i, new NoopStalenessHandler(), new WeakMap(), $this->meter); + $c = new ObservableCounter($w, $i, new NoopStalenessHandler(), new WeakMap()); $c->observe(static function (ObserverInterface $observer): void { $observer->observe(5); }); @@ -112,7 +103,7 @@ public function test_asynchronous_counter_weaken(): void $a = new MetricAggregatorFactory(null, new SumAggregation(true)); $s = new SynchronousMetricStream(new SumAggregation(true), 0); $w = new MetricRegistry(null, Attributes::factory(), new TestClock(1)); - $i = new Instrument(InstrumentType::ASYNCHRONOUS_COUNTER, 'test', null, null, meter: $this->meter); + $i = new Instrument(InstrumentType::ASYNCHRONOUS_COUNTER, 'test', null, null); $n = $w->registerAsynchronousStream($i, $s, $a); $r = $s->register(Temporality::CUMULATIVE); @@ -123,7 +114,7 @@ public function __invoke(ObserverInterface $observer) } }; - $c = new ObservableCounter($w, $i, new NoopStalenessHandler(), new WeakMap(), $this->meter); + $c = new ObservableCounter($w, $i, new NoopStalenessHandler(), new WeakMap()); $c->observe($instance); $instance = null; @@ -140,11 +131,11 @@ public function test_up_down_counter(): void $a = new MetricAggregator(null, new SumAggregation(false)); $s = new SynchronousMetricStream(new SumAggregation(false), 0); $w = new MetricRegistry(null, Attributes::factory(), new TestClock(1)); - $i = new Instrument(InstrumentType::UP_DOWN_COUNTER, 'test', null, null, meter: $this->meter); + $i = new Instrument(InstrumentType::UP_DOWN_COUNTER, 'test', null, null); $n = $w->registerSynchronousStream($i, $s, $a); $r = $s->register(Temporality::DELTA); - $c = new UpDownCounter($w, $i, new NoopStalenessHandler(), $this->meter); + $c = new UpDownCounter($w, $i, new NoopStalenessHandler()); $c->add(5); $c->add(7); $c->add(-8); @@ -169,11 +160,11 @@ public function test_histogram(): void $a = new MetricAggregator(null, new ExplicitBucketHistogramAggregation([3, 6, 9])); $s = new SynchronousMetricStream(new ExplicitBucketHistogramAggregation([3, 6, 9]), 0); $w = new MetricRegistry(null, Attributes::factory(), new TestClock(1)); - $i = new Instrument(InstrumentType::HISTOGRAM, 'test', null, null, meter: $this->meter); + $i = new Instrument(InstrumentType::HISTOGRAM, 'test', null, null); $n = $w->registerSynchronousStream($i, $s, $a); $r = $s->register(Temporality::DELTA); - $h = new Histogram($w, $i, new NoopStalenessHandler(), $this->meter); + $h = new Histogram($w, $i, new NoopStalenessHandler()); $h->record(1); $h->record(7); $h->record(9); @@ -254,10 +245,9 @@ public function test_synchronous_disabled_if_meter_disabled(): void { $w = $this->createMock(MetricWriterInterface::class); $c = $this->createMock(ReferenceCounterInterface::class); - $i = new Instrument(InstrumentType::UP_DOWN_COUNTER, 'test', null, null, meter: $this->meter); - $meter = $this->createMock(MeterInterface::class); - $meter->expects($this->once())->method('isEnabled')->willReturn(false); - $counter = new Counter($w, $i, $c, $meter); + $i = new Instrument(InstrumentType::UP_DOWN_COUNTER, 'test', null, null); + $w->expects($this->once())->method('enabled')->with($i)->willReturn(false); + $counter = new Counter($w, $i, $c); $this->assertFalse($counter->isEnabled()); } @@ -266,10 +256,9 @@ public function test_asynchronous_disabled_if_meter_disabled(): void { $w = $this->createMock(MetricWriterInterface::class); $c = $this->createMock(ReferenceCounterInterface::class); - $i = new Instrument(InstrumentType::UP_DOWN_COUNTER, 'test', null, null, meter: $this->meter); - $meter = $this->createMock(MeterInterface::class); - $meter->expects($this->once())->method('isEnabled')->willReturn(false); - $counter = new ObservableCounter($w, $i, $c, new WeakMap(), $meter); + $i = new Instrument(InstrumentType::UP_DOWN_COUNTER, 'test', null, null); + $w->expects($this->once())->method('enabled')->with($i)->willReturn(false); + $counter = new ObservableCounter($w, $i, $c, new WeakMap()); $this->assertFalse($counter->isEnabled()); } diff --git a/tests/Unit/SDK/Metrics/MeterProviderTest.php b/tests/Unit/SDK/Metrics/MeterProviderTest.php index 7df3c7802..c72e0d1e4 100644 --- a/tests/Unit/SDK/Metrics/MeterProviderTest.php +++ b/tests/Unit/SDK/Metrics/MeterProviderTest.php @@ -114,11 +114,11 @@ public function test_disable(): void $meterProvider = MeterProvider::builder()->addReader(new ExportingReader(new InMemoryExporter()))->build(); $this->assertInstanceOf(MeterProvider::class, $meterProvider); $meter = $meterProvider->getMeter('one'); - $this->assertTrue($meter->isEnabled()); + $this->assertTrue($meter->createCounter('test')->isEnabled()); $counter = $meter->createCounter('A'); $this->assertTrue($counter->isEnabled()); $meterProvider->updateConfigurator(Configurator::meter()->with(static fn (MeterConfig $config) => $config->setDisabled(true), name: 'one')); - $this->assertFalse($meter->isEnabled()); + $this->assertFalse($meter->createCounter('test')->isEnabled()); $this->assertFalse($counter->isEnabled()); } } diff --git a/tests/Unit/SDK/Metrics/MeterTest.php b/tests/Unit/SDK/Metrics/MeterTest.php index 1558e7ca1..fa6fd83a7 100644 --- a/tests/Unit/SDK/Metrics/MeterTest.php +++ b/tests/Unit/SDK/Metrics/MeterTest.php @@ -9,13 +9,11 @@ use OpenTelemetry\SDK\Common\Attribute\Attributes; use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScope; use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactory; -use OpenTelemetry\SDK\Common\InstrumentationScope\Configurator; use OpenTelemetry\SDK\Metrics\AggregationInterface; use OpenTelemetry\SDK\Metrics\DefaultAggregationProviderInterface; use OpenTelemetry\SDK\Metrics\Instrument; use OpenTelemetry\SDK\Metrics\InstrumentType; use OpenTelemetry\SDK\Metrics\Meter; -use OpenTelemetry\SDK\Metrics\MeterConfig; use OpenTelemetry\SDK\Metrics\MeterProvider; use OpenTelemetry\SDK\Metrics\MetricFactoryInterface; use OpenTelemetry\SDK\Metrics\MetricReaderInterface; @@ -41,7 +39,7 @@ public function test_create_counter(): void $this->anything(), $this->anything(), new InstrumentationScope('test', null, null, Attributes::create([])), - new Instrument(InstrumentType::COUNTER, 'name', 'unit', 'description', meter: $meter), + new Instrument(InstrumentType::COUNTER, 'name', 'unit', 'description'), $this->anything(), $this->anything(), $this->anything(), @@ -61,7 +59,7 @@ public function test_create_histogram(): void $this->anything(), $this->anything(), new InstrumentationScope('test', null, null, Attributes::create([])), - new Instrument(InstrumentType::HISTOGRAM, 'name', 'unit', 'description', meter: $meter), + new Instrument(InstrumentType::HISTOGRAM, 'name', 'unit', 'description'), $this->anything(), $this->anything(), $this->anything(), @@ -87,7 +85,6 @@ public function test_create_histogram_advisory(): void 's', 'Measures the duration of inbound HTTP requests.', ['ExplicitBucketBoundaries' => [0, 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10]], - $meter, ), $this->anything(), $this->anything(), @@ -113,7 +110,7 @@ public function test_create_gauge(): void $this->anything(), $this->anything(), new InstrumentationScope('test', null, null, Attributes::create([])), - new Instrument(InstrumentType::GAUGE, 'name', 'unit', 'description', meter: $meter), + new Instrument(InstrumentType::GAUGE, 'name', 'unit', 'description'), $this->anything(), $this->anything(), $this->anything(), @@ -133,7 +130,7 @@ public function test_create_up_down_counter(): void $this->anything(), $this->anything(), new InstrumentationScope('test', null, null, Attributes::create([])), - new Instrument(InstrumentType::UP_DOWN_COUNTER, 'name', 'unit', 'description', meter: $meter), + new Instrument(InstrumentType::UP_DOWN_COUNTER, 'name', 'unit', 'description'), $this->anything(), $this->anything(), $this->anything(), @@ -153,7 +150,7 @@ public function test_create_observable_counter(): void $this->anything(), $this->anything(), new InstrumentationScope('test', null, null, Attributes::create([])), - new Instrument(InstrumentType::ASYNCHRONOUS_COUNTER, 'name', 'unit', 'description', meter: $meter), + new Instrument(InstrumentType::ASYNCHRONOUS_COUNTER, 'name', 'unit', 'description'), $this->anything(), $this->anything(), ) @@ -172,7 +169,7 @@ public function test_create_observable_gauge(): void $this->anything(), $this->anything(), new InstrumentationScope('test', null, null, Attributes::create([])), - new Instrument(InstrumentType::ASYNCHRONOUS_GAUGE, 'name', 'unit', 'description', meter: $meter), + new Instrument(InstrumentType::ASYNCHRONOUS_GAUGE, 'name', 'unit', 'description'), $this->anything(), $this->anything(), ) @@ -191,7 +188,7 @@ public function test_create_observable_up_down_counter(): void $this->anything(), $this->anything(), new InstrumentationScope('test', null, null, Attributes::create([])), - new Instrument(InstrumentType::ASYNCHRONOUS_UP_DOWN_COUNTER, 'name', 'unit', 'description', meter: $meter), + new Instrument(InstrumentType::ASYNCHRONOUS_UP_DOWN_COUNTER, 'name', 'unit', 'description'), $this->anything(), $this->anything(), ) @@ -211,7 +208,7 @@ public function test_reuses_writer_when_not_stale(): void $this->anything(), $this->anything(), new InstrumentationScope('test', null, null, Attributes::create([])), - new Instrument(InstrumentType::COUNTER, 'name', 'unit', 'description', meter: $meter), + new Instrument(InstrumentType::COUNTER, 'name', 'unit', 'description'), $this->anything(), $this->anything(), $this->anything(), @@ -232,7 +229,7 @@ public function test_releases_writer_on_stale(): void $this->anything(), $this->anything(), new InstrumentationScope('test', null, null, Attributes::create([])), - new Instrument(InstrumentType::COUNTER, 'name', 'unit', 'description', meter: $meter), + new Instrument(InstrumentType::COUNTER, 'name', 'unit', 'description'), $this->anything(), $this->anything(), $this->anything(), @@ -254,7 +251,7 @@ public function test_reuses_observer_when_not_stale(): void $this->anything(), $this->anything(), new InstrumentationScope('test', null, null, Attributes::create([])), - new Instrument(InstrumentType::ASYNCHRONOUS_COUNTER, 'name', 'unit', 'description', meter: $meter), + new Instrument(InstrumentType::ASYNCHRONOUS_COUNTER, 'name', 'unit', 'description'), $this->anything(), $this->anything(), ) @@ -274,7 +271,7 @@ public function test_releases_observer_on_stale(): void $this->anything(), $this->anything(), new InstrumentationScope('test', null, null, Attributes::create([])), - new Instrument(InstrumentType::ASYNCHRONOUS_COUNTER, 'name', 'unit', 'description', meter: $meter), + new Instrument(InstrumentType::ASYNCHRONOUS_COUNTER, 'name', 'unit', 'description'), $this->anything(), $this->anything(), ) @@ -376,18 +373,6 @@ public function test_uses_default_view_if_null_views_returned(): void $meter->createCounter('name'); } - public function test_update_configurator(): void - { - $metricFactory = $this->createMock(MetricFactoryInterface::class); - $metricFactory->method('createSynchronousWriter')->willReturn([]); - - $meterProvider = $this->createMeterProviderForMetricFactory($metricFactory); - $meter = $meterProvider->getMeter('test'); - $this->assertTrue($meter->isEnabled()); - $meterProvider->updateConfigurator(Configurator::meter()->with(static fn (MeterConfig $config) => $config->setDisabled(true), name: 'test')); - $this->assertFalse($meter->isEnabled()); - } - /** * @param iterable $metricReaders */ diff --git a/tests/Unit/SDK/Metrics/MetricReader/ExportingReaderTest.php b/tests/Unit/SDK/Metrics/MetricReader/ExportingReaderTest.php index 2150de98c..d9bb3e05a 100644 --- a/tests/Unit/SDK/Metrics/MetricReader/ExportingReaderTest.php +++ b/tests/Unit/SDK/Metrics/MetricReader/ExportingReaderTest.php @@ -148,7 +148,6 @@ public function test_collect_collects_sources_with_current_timestamp(): void $source = $this->createMock(MetricSourceInterface::class); $source->expects($this->once())->method('collect')->willReturn($metric); - $source->method('isEnabled')->willReturn(true); $provider = $this->createMock(MetricSourceProviderInterface::class); $provider->expects($this->once())->method('create')->willReturn($source); $metricMetadata = $this->createMock(MetricMetadataInterface::class); @@ -184,7 +183,6 @@ public function test_shutdown_exports_metrics(): void $provider = $this->createMock(MetricSourceProviderInterface::class); $source = $this->createMock(MetricSourceInterface::class); $source->method('collect')->willReturn($this->createMock(Metric::class)); - $source->method('isEnabled')->willReturn(true); $provider->method('create')->willReturn($source); $exporter->method('temporality')->willReturn('foo'); $exporter->expects($this->once())->method('export')->willReturn(true); diff --git a/tests/Unit/SDK/Metrics/View/SelectionCriteriaTest.php b/tests/Unit/SDK/Metrics/View/SelectionCriteriaTest.php index f1400fd71..3038483c3 100644 --- a/tests/Unit/SDK/Metrics/View/SelectionCriteriaTest.php +++ b/tests/Unit/SDK/Metrics/View/SelectionCriteriaTest.php @@ -4,7 +4,6 @@ namespace OpenTelemetry\Tests\Unit\SDK\Metrics\View; -use OpenTelemetry\API\Metrics\MeterInterface; use OpenTelemetry\SDK\Common\Attribute\Attributes; use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScope; use OpenTelemetry\SDK\Metrics\Instrument; @@ -31,22 +30,14 @@ final class SelectionCriteriaTest extends TestCase { use ProphecyTrait; - private MeterInterface $meter; - - public function setUp(): void - { - $this->meter = $this->createMock(MeterInterface::class); - $this->meter->method('isEnabled')->willReturn(true); - } - public function test_instrument_scope_name_criteria(): void { $this->assertTrue((new InstrumentationScopeNameCriteria('scopeName'))->accepts( - new Instrument(InstrumentType::COUNTER, 'name', null, null, meter: $this->meter), + new Instrument(InstrumentType::COUNTER, 'name', null, null), new InstrumentationScope('scopeName', null, null, Attributes::create([])), )); $this->assertFalse((new InstrumentationScopeNameCriteria('scopeName'))->accepts( - new Instrument(InstrumentType::COUNTER, 'name', null, null, meter: $this->meter), + new Instrument(InstrumentType::COUNTER, 'name', null, null), new InstrumentationScope('scope-name', null, null, Attributes::create([])), )); } From 439447b2861114f01509f69df4c2947dab58132e Mon Sep 17 00:00:00 2001 From: Chris Lightfoot-Wild Date: Wed, 25 Sep 2024 12:54:21 +0100 Subject: [PATCH 5/9] Update SPI dependency to v1. (#1388) --- composer.json | 2 +- src/Config/SDK/composer.json | 2 +- src/SDK/composer.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index b30c221f0..e127ff8fc 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "symfony/config": "^5.4 || ^6.4 || ^7.0", "symfony/polyfill-mbstring": "^1.23", "symfony/polyfill-php82": "^1.26", - "tbachert/spi": ">= 0.2.1" + "tbachert/spi": "^1.0.1" }, "config": { "sort-packages": true, diff --git a/src/Config/SDK/composer.json b/src/Config/SDK/composer.json index 8cb1259ae..ebb5c691d 100644 --- a/src/Config/SDK/composer.json +++ b/src/Config/SDK/composer.json @@ -21,7 +21,7 @@ "open-telemetry/context": "^1.0", "open-telemetry/sdk": "^1.0", "symfony/config": "^5.4 || ^6.4 || ^7.0", - "tbachert/spi": ">= 0.2.1" + "tbachert/spi": "^1.0.1" }, "autoload": { "psr-4": { diff --git a/src/SDK/composer.json b/src/SDK/composer.json index cad688d5a..1fe3d71d0 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": ">= 0.2.1" + "tbachert/spi": "^1.0.1" }, "autoload": { "psr-4": { From 7d744e0086d5ddc7459b972fe5ed9d71fd09e31b Mon Sep 17 00:00:00 2001 From: Chris Lightfoot-Wild Date: Wed, 25 Sep 2024 12:59:48 +0100 Subject: [PATCH 6/9] Fix README badges. (#1389) --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6777a0cfb..23ebb89da 100644 --- a/README.md +++ b/README.md @@ -14,14 +14,14 @@ Please read the official documentation: https://opentelemetry.io/docs/instrument | Package | Latest | |----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| API | [![Latest Stable Version](http://poser.pugx.org/open-telemetry/api/v/stable)](https://packagist.org/packages/open-telemetry/api/) [![Latest Unstable Version](http://poser.pugx.org/open-telemetry/api/v/unstable)](https://packagist.org/packages/open-telemetry/api/) | -| SDK | [![Latest Stable Version](http://poser.pugx.org/open-telemetry/sdk/v/stable)](https://packagist.org/packages/open-telemetry/sdk/) [![Latest Unstable Version](http://poser.pugx.org/open-telemetry/sdk/v/unstable)](https://packagist.org/packages/open-telemetry/sdk/) | -| Context | [![Latest Stable Version](http://poser.pugx.org/open-telemetry/context/v/stable)](https://packagist.org/packages/open-telemetry/context/) [![Latest Unstable Version](http://poser.pugx.org/open-telemetry/context/v/unstable)](https://packagist.org/packages/open-telemetry/context/) | -| Semantic Conventions | [![Latest Stable Version](http://poser.pugx.org/open-telemetry/sem-conv/v/stable)](https://packagist.org/packages/open-telemetry/sem-conv/) [![Latest Unstable Version](http://poser.pugx.org/open-telemetry/sem-conv/v/unstable)](https://packagist.org/packages/open-telemetry/sem-conv/) | -| OTLP Exporter | [![Latest Stable Version](http://poser.pugx.org/open-telemetry/exporter-otlp/v/stable)](https://packagist.org/packages/open-telemetry/exporter-otlp/) [![Latest Unstable Version](http://poser.pugx.org/open-telemetry/exporter-otlp/v/unstable)](https://packagist.org/packages/open-telemetry/exporter-otlp/) | -| gRPC Transport | [![Latest Stable Version](http://poser.pugx.org/open-telemetry/transport-grpc/v/stable)](https://packagist.org/packages/open-telemetry/transport-grpc/) [![Latest Unstable Version](http://poser.pugx.org/open-telemetry/transport-grpc/v/unstable)](https://packagist.org/packages/open-telemetry/transport-grpc/) | -| OTLP Protobuf Files | [![Latest Stable Version](http://poser.pugx.org/open-telemetry/gen-otlp-protobuf/v/stable)](https://packagist.org/packages/open-telemetry/gen-otlp-protobuf/) [![Latest Unstable Version](http://poser.pugx.org/open-telemetry/gen-otlp-protobuf/v/unstable)](https://packagist.org/packages/open-telemetry/gen-otlp-protobuf/) | -| B3 Propagator | [![Latest Stable Version](http://poser.pugx.org/open-telemetry/extension-propagator-b3/v/stable)](https://packagist.org/packages/open-telemetry/extension-propagator-b3/) [![Latest Unstable Version](http://poser.pugx.org/open-telemetry/extension-propagator-b3/v/unstable)](https://packagist.org/packages/open-telemetry/extension-propagator-b3/) | +| API | [![Latest Stable Version](https://poser.pugx.org/open-telemetry/api/v/stable)](https://packagist.org/packages/open-telemetry/api/) [![Latest Unstable Version](https://poser.pugx.org/open-telemetry/api/v/unstable)](https://packagist.org/packages/open-telemetry/api/) | +| SDK | [![Latest Stable Version](https://poser.pugx.org/open-telemetry/sdk/v/stable)](https://packagist.org/packages/open-telemetry/sdk/) [![Latest Unstable Version](https://poser.pugx.org/open-telemetry/sdk/v/unstable)](https://packagist.org/packages/open-telemetry/sdk/) | +| Context | [![Latest Stable Version](https://poser.pugx.org/open-telemetry/context/v/stable)](https://packagist.org/packages/open-telemetry/context/) [![Latest Unstable Version](https://poser.pugx.org/open-telemetry/context/v/unstable)](https://packagist.org/packages/open-telemetry/context/) | +| Semantic Conventions | [![Latest Stable Version](https://poser.pugx.org/open-telemetry/sem-conv/v/stable)](https://packagist.org/packages/open-telemetry/sem-conv/) [![Latest Unstable Version](https://poser.pugx.org/open-telemetry/sem-conv/v/unstable)](https://packagist.org/packages/open-telemetry/sem-conv/) | +| OTLP Exporter | [![Latest Stable Version](https://poser.pugx.org/open-telemetry/exporter-otlp/v/stable)](https://packagist.org/packages/open-telemetry/exporter-otlp/) [![Latest Unstable Version](https://poser.pugx.org/open-telemetry/exporter-otlp/v/unstable)](https://packagist.org/packages/open-telemetry/exporter-otlp/) | +| gRPC Transport | [![Latest Stable Version](https://poser.pugx.org/open-telemetry/transport-grpc/v/stable)](https://packagist.org/packages/open-telemetry/transport-grpc/) [![Latest Unstable Version](https://poser.pugx.org/open-telemetry/transport-grpc/v/unstable)](https://packagist.org/packages/open-telemetry/transport-grpc/) | +| OTLP Protobuf Files | [![Latest Stable Version](https://poser.pugx.org/open-telemetry/gen-otlp-protobuf/v/stable)](https://packagist.org/packages/open-telemetry/gen-otlp-protobuf/) [![Latest Unstable Version](https://poser.pugx.org/open-telemetry/gen-otlp-protobuf/v/unstable)](https://packagist.org/packages/open-telemetry/gen-otlp-protobuf/) | +| B3 Propagator | [![Latest Stable Version](https://poser.pugx.org/open-telemetry/extension-propagator-b3/v/stable)](https://packagist.org/packages/open-telemetry/extension-propagator-b3/) [![Latest Unstable Version](https://poser.pugx.org/open-telemetry/extension-propagator-b3/v/unstable)](https://packagist.org/packages/open-telemetry/extension-propagator-b3/) | Releases for both this repository and [contrib](https://github.com/open-telemetry/opentelemetry-php-contrib) are based on read-only [git subtree splits](https://github.com/splitsh/lite) from our monorepo. You should refer to From 91f4735b3a3f0c85b032cc864469f208a9f1f851 Mon Sep 17 00:00:00 2001 From: Severin Neumann Date: Wed, 25 Sep 2024 20:39:36 +0200 Subject: [PATCH 7/9] add phpDocumentor instructions to Makefile (#1385) * add phpDocumentor instructions to Makefile Signed-off-by: svrnm * update DEVELOPMENT.md Signed-off-by: svrnm --------- Signed-off-by: svrnm --- .gitignore | 6 ++++++ DEVELOPMENT.md | 18 ++++++++++++++++++ Makefile | 4 ++++ docker-compose.phpDocumentor.yaml | 11 +++++++++++ phpdoc.dist.xml | 11 +++++++++++ 5 files changed, 50 insertions(+) create mode 100644 docker-compose.phpDocumentor.yaml create mode 100644 phpdoc.dist.xml diff --git a/.gitignore b/.gitignore index 39b37c4dd..b95376955 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,9 @@ tests/TraceContext/W3CTestService/trace-context # deptrac cache /.deptrac.cache + +# output from phpdoc +docs/build + +# cache from phpdoc +.phpdoc diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 87d6edd4e..69f0b9019 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -236,3 +236,21 @@ make phpmetrics This will generate a HTML PhpMetrics report in the `var/metrics` directory. Make sure to run `make test` before to create the test log-file, used by the metrics report. + +## API Documentation + +We use [phpDocumentor](https://phpdoc.org/) to automatically generate API documentation from DocBlocks in the code. + +To generate a recent version of the API documentation, you can run: + +```bash +make phpdoc +``` + +To preview the documentation and changes you might expect, you can run: + +```bash +make phpdoc-preview +``` + +This will start a HTTP server running at serving the updated documentation files. \ No newline at end of file diff --git a/Makefile b/Makefile index 8538ca84d..ed7685c5c 100644 --- a/Makefile +++ b/Makefile @@ -41,6 +41,10 @@ psalm: ## Run psalm $(DC_RUN_PHP) env XDEBUG_MODE=off vendor-bin/psalm/vendor/bin/psalm --threads=1 --no-cache psalm-info: ## Run psalm and show info $(DC_RUN_PHP) env XDEBUG_MODE=off vendor-bin/psalm/vendor/bin/psalm --show-info=true --threads=1 +phpdoc: ## Run phpdoc + $(DOCKER_COMPOSE) -f docker-compose.phpDocumentor.yaml run --rm phpdoc +phpdoc-preview: + $(DOCKER_COMPOSE) -f docker-compose.phpDocumentor.yaml run --service-ports --rm preview phpstan: ## Run phpstan $(DC_RUN_PHP) env XDEBUG_MODE=off vendor/bin/phpstan analyse --memory-limit=256M infection: ## Run infection (mutation testing) diff --git a/docker-compose.phpDocumentor.yaml b/docker-compose.phpDocumentor.yaml new file mode 100644 index 000000000..5cfe5e643 --- /dev/null +++ b/docker-compose.phpDocumentor.yaml @@ -0,0 +1,11 @@ +services: + phpdoc: + image: phpdoc/phpdoc:3 + volumes: + - ./:/data + preview: + image: nginx:alpine + ports: + - 8080:80 + volumes: + - ./docs/build:/usr/share/nginx/html \ No newline at end of file diff --git a/phpdoc.dist.xml b/phpdoc.dist.xml new file mode 100644 index 000000000..58bab8faa --- /dev/null +++ b/phpdoc.dist.xml @@ -0,0 +1,11 @@ + + + OpenTelemetry PHP + + docs/build + + \ No newline at end of file From 20bf3cff3e6fdd3deac23aed1e62042b7dd24ff5 Mon Sep 17 00:00:00 2001 From: Oleg <142805497+devactivity-team@users.noreply.github.com> Date: Wed, 25 Sep 2024 21:40:03 +0300 Subject: [PATCH 8/9] Update README.md (#1344) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 23ebb89da..2944e391b 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,8 @@ You can also look at the read-only repositories, which live in the ## Contributing +[![GitHub repo Good Issues for newbies](https://img.shields.io/github/issues/open-telemetry/opentelemetry-php/good%20first%20issue?style=flat&logo=github&logoColor=green&label=Good%20First%20issues)](https://github.com/open-telemetry/opentelemetry-php/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) [![GitHub Help Wanted issues](https://img.shields.io/github/issues/open-telemetry/opentelemetry-php/help%20wanted?style=flat&logo=github&logoColor=b545d1&label=%22Help%20Wanted%22%20issues)](https://github.com/open-telemetry/opentelemetry-php/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) [![GitHub Help Wanted PRs](https://img.shields.io/github/issues-pr/open-telemetry/opentelemetry-php/help%20wanted?style=flat&logo=github&logoColor=b545d1&label=%22Help%20Wanted%22%20PRs)](https://github.com/open-telemetry/opentelemetry-php/pulls?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) [![GitHub repo Issues](https://img.shields.io/github/issues/open-telemetry/opentelemetry-php?style=flat&logo=github&logoColor=red&label=Issues)](https://github.com/open-telemetry/opentelemetry-php/issues?q=is%3Aopen) + We would love to have you on board, please see our [Development README](./DEVELOPMENT.md) and [Contributing README](./CONTRIBUTING.md). ## Specification conformance From fd24607c10083c3963aad5dea577b27835854bef Mon Sep 17 00:00:00 2001 From: Tobias Bachert Date: Sun, 29 Sep 2024 14:12:53 +0200 Subject: [PATCH 9/9] Fix `IncompatibleReturnValueException` in `MessageFactoryTest` (#1392) PSR7 `RequestInterface::getUri()` must return `UriInterface`, not `string`; explicit typehint was added in `2.0`. --- .../SDK/Common/Http/Psr/Message/UsesRequestFactoryTrait.php | 3 ++- .../Common/Http/Psr/Message/UsesServerRequestFactoryTrait.php | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/Unit/SDK/Common/Http/Psr/Message/UsesRequestFactoryTrait.php b/tests/Unit/SDK/Common/Http/Psr/Message/UsesRequestFactoryTrait.php index e816e6f6b..dcd49802f 100644 --- a/tests/Unit/SDK/Common/Http/Psr/Message/UsesRequestFactoryTrait.php +++ b/tests/Unit/SDK/Common/Http/Psr/Message/UsesRequestFactoryTrait.php @@ -4,6 +4,7 @@ namespace OpenTelemetry\Tests\Unit\SDK\Common\Http\Psr\Message; +use Nyholm\Psr7\Uri; use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Message\RequestInterface; @@ -19,7 +20,7 @@ private function createRequestFactoryMock(?string $requestMethod = 'GET', ?strin $request->method('getMethod') ->willReturn((string) $requestMethod); $request->method('getUri') - ->willReturn((string) $requestUri); + ->willReturn(new Uri((string) $requestUri)); $factory = $this->createMock(RequestFactoryInterface::class); $factory->method('createRequest') ->willReturn($request); diff --git a/tests/Unit/SDK/Common/Http/Psr/Message/UsesServerRequestFactoryTrait.php b/tests/Unit/SDK/Common/Http/Psr/Message/UsesServerRequestFactoryTrait.php index 2b7007ad8..1e8db7725 100644 --- a/tests/Unit/SDK/Common/Http/Psr/Message/UsesServerRequestFactoryTrait.php +++ b/tests/Unit/SDK/Common/Http/Psr/Message/UsesServerRequestFactoryTrait.php @@ -4,6 +4,7 @@ namespace OpenTelemetry\Tests\Unit\SDK\Common\Http\Psr\Message; +use Nyholm\Psr7\Uri; use Psr\Http\Message\ServerRequestFactoryInterface; use Psr\Http\Message\ServerRequestInterface; @@ -19,7 +20,7 @@ private function createServerRequestFactoryMock(?string $requestMethod = 'GET', $request->method('getMethod') ->willReturn((string) $requestMethod); $request->method('getUri') - ->willReturn((string) $requestUri); + ->willReturn(new Uri((string) $requestUri)); $factory = $this->createMock(ServerRequestFactoryInterface::class); $factory->method('createServerRequest') ->willReturn($request);