From f6671ddd796570d737e7d6e5d362a58c2faec9e0 Mon Sep 17 00:00:00 2001 From: Tobias Bachert Date: Sun, 16 Oct 2022 23:57:57 +0200 Subject: [PATCH] Add alternative instrumentation abstraction (#822) * Add alternative instrumentation abstraction * Provide global access to providers Remove logs for now, will be replaced with `LoggerProvider` in the future. --- .../Instrumentation/CachedInstrumentation.php | 81 +++++++++++++++++ .../Common/Instrumentation/Configurator.php | 80 ++++++++++++++++ .../Common/Instrumentation/ContextKeys.php | 47 ++++++++++ src/API/Common/Instrumentation/Globals.php | 40 ++++++++ .../Instrumentation/InstrumentationTest.php | 91 +++++++++++++++++++ 5 files changed, 339 insertions(+) create mode 100644 src/API/Common/Instrumentation/CachedInstrumentation.php create mode 100644 src/API/Common/Instrumentation/Configurator.php create mode 100644 src/API/Common/Instrumentation/ContextKeys.php create mode 100644 src/API/Common/Instrumentation/Globals.php create mode 100644 tests/Unit/API/Common/Instrumentation/InstrumentationTest.php diff --git a/src/API/Common/Instrumentation/CachedInstrumentation.php b/src/API/Common/Instrumentation/CachedInstrumentation.php new file mode 100644 index 000000000..8bd0de592 --- /dev/null +++ b/src/API/Common/Instrumentation/CachedInstrumentation.php @@ -0,0 +1,81 @@ +|null */ + private ?ArrayAccess $tracers; + /** @var ArrayAccess|null */ + private ?ArrayAccess $meters; + + public function __construct(string $name, ?string $version = null, ?string $schemaUrl = null, iterable $attributes = []) + { + $this->name = $name; + $this->version = $version; + $this->schemaUrl = $schemaUrl; + $this->attributes = $attributes; + $this->tracers = self::createWeakMap(); + $this->meters = self::createWeakMap(); + } + + private static function createWeakMap(): ?ArrayAccess + { + if (PHP_VERSION_ID < 80000) { + return null; + } + + /** @phan-suppress-next-line PhanUndeclaredClassReference */ + assert(class_exists(\WeakMap::class, false)); + /** @phan-suppress-next-line PhanUndeclaredClassMethod */ + $map = new \WeakMap(); + assert($map instanceof ArrayAccess); + + return $map; + } + + public function tracer(): TracerInterface + { + $tracerProvider = Globals::tracerProvider(); + + if ($this->tracers === null) { + return $tracerProvider->getTracer($this->name, $this->version, $this->schemaUrl, $this->attributes); + } + + return $this->tracers[$tracerProvider] ??= $tracerProvider->getTracer($this->name, $this->version, $this->schemaUrl, $this->attributes); + } + + public function meter(): MeterInterface + { + $meterProvider = Globals::meterProvider(); + + if ($this->meters === null) { + return $meterProvider->getMeter($this->name, $this->version, $this->schemaUrl, $this->attributes); + } + + return $this->meters[$meterProvider] ??= $meterProvider->getMeter($this->name, $this->version, $this->schemaUrl, $this->attributes); + } +} diff --git a/src/API/Common/Instrumentation/Configurator.php b/src/API/Common/Instrumentation/Configurator.php new file mode 100644 index 000000000..e34cd5eea --- /dev/null +++ b/src/API/Common/Instrumentation/Configurator.php @@ -0,0 +1,80 @@ +storeInContext()->activate(); + } + + public function storeInContext(?ContextInterface $context = null): ContextInterface + { + $context ??= Context::getCurrent(); + + if ($this->tracerProvider !== null) { + $context = $context->with(ContextKeys::tracerProvider(), $this->tracerProvider); + } + if ($this->meterProvider !== null) { + $context = $context->with(ContextKeys::meterProvider(), $this->meterProvider); + } + if ($this->propagator !== null) { + $context = $context->with(ContextKeys::propagator(), $this->propagator); + } + + return $context; + } + + public function withTracerProvider(?TracerProviderInterface $tracerProvider): Configurator + { + $self = clone $this; + $self->tracerProvider = $tracerProvider; + + return $self; + } + + public function withMeterProvider(?MeterProviderInterface $meterProvider): Configurator + { + $self = clone $this; + $self->meterProvider = $meterProvider; + + return $self; + } + + public function withPropagator(?TextMapPropagatorInterface $propagator): Configurator + { + $self = clone $this; + $self->propagator = $propagator; + + return $self; + } +} diff --git a/src/API/Common/Instrumentation/ContextKeys.php b/src/API/Common/Instrumentation/ContextKeys.php new file mode 100644 index 000000000..590a75d41 --- /dev/null +++ b/src/API/Common/Instrumentation/ContextKeys.php @@ -0,0 +1,47 @@ + + */ + public static function tracerProvider(): ContextKeyInterface + { + static $instance; + + return $instance ??= Context::createKey(TracerProviderInterface::class); + } + + /** + * @return ContextKeyInterface + */ + public static function meterProvider(): ContextKeyInterface + { + static $instance; + + return $instance ??= Context::createKey(MeterProviderInterface::class); + } + + /** + * @return ContextKeyInterface + */ + public static function propagator(): ContextKeyInterface + { + static $instance; + + return $instance ??= Context::createKey(TextMapPropagatorInterface::class); + } +} diff --git a/src/API/Common/Instrumentation/Globals.php b/src/API/Common/Instrumentation/Globals.php new file mode 100644 index 000000000..6c35133e7 --- /dev/null +++ b/src/API/Common/Instrumentation/Globals.php @@ -0,0 +1,40 @@ +get(ContextKeys::tracerProvider()) ?? $noop ??= new NoopTracerProvider(); + } + + public static function meterProvider(): MeterProviderInterface + { + static $noop; + + return Context::getCurrent()->get(ContextKeys::meterProvider()) ?? $noop ??= new NoopMeterProvider(); + } + + public static function propagator(): TextMapPropagatorInterface + { + static $noop; + + return Context::getCurrent()->get(ContextKeys::propagator()) ?? $noop ??= new NoopTextMapPropagator(); + } +} diff --git a/tests/Unit/API/Common/Instrumentation/InstrumentationTest.php b/tests/Unit/API/Common/Instrumentation/InstrumentationTest.php new file mode 100644 index 000000000..b4d192701 --- /dev/null +++ b/tests/Unit/API/Common/Instrumentation/InstrumentationTest.php @@ -0,0 +1,91 @@ +assertInstanceOf(NoopTracerProvider::class, Globals::tracerProvider()); + $this->assertInstanceOf(NoopMeterProvider::class, Globals::meterProvider()); + $this->assertInstanceOf(NoopTextMapPropagator::class, Globals::propagator()); + } + + public function test_globals_returns_configured_instances(): void + { + $tracerProvider = $this->createMock(TracerProviderInterface::class); + $meterProvider = $this->createMock(MeterProviderInterface::class); + $propagator = $this->createMock(TextMapPropagatorInterface::class); + + $scope = Configurator::create() + ->withTracerProvider($tracerProvider) + ->withMeterProvider($meterProvider) + ->withPropagator($propagator) + ->activate(); + + try { + $this->assertSame($tracerProvider, Globals::tracerProvider()); + $this->assertSame($meterProvider, Globals::meterProvider()); + $this->assertSame($propagator, Globals::propagator()); + } finally { + $scope->detach(); + } + } + + public function test_instrumentation_not_configured_returns_noop_instances(): void + { + $instrumentation = new CachedInstrumentation('', null, null, []); + + $this->assertInstanceOf(NoopTracer::class, $instrumentation->tracer()); + $this->assertInstanceOf(NoopMeter::class, $instrumentation->meter()); + } + + public function test_instrumentation_returns_configured_instances(): void + { + $instrumentation = new CachedInstrumentation('', null, null, []); + + $tracer = $this->createMock(TracerInterface::class); + $tracerProvider = $this->createMock(TracerProviderInterface::class); + $tracerProvider->method('getTracer')->willReturn($tracer); + $meter = $this->createMock(MeterInterface::class); + $meterProvider = $this->createMock(MeterProviderInterface::class); + $meterProvider->method('getMeter')->willReturn($meter); + $propagator = $this->createMock(TextMapPropagatorInterface::class); + + $scope = Configurator::create() + ->withTracerProvider($tracerProvider) + ->withMeterProvider($meterProvider) + ->withPropagator($propagator) + ->activate(); + + try { + $this->assertSame($tracer, $instrumentation->tracer()); + $this->assertSame($meter, $instrumentation->meter()); + } finally { + $scope->detach(); + } + } +}