Skip to content

Commit

Permalink
Add alternative instrumentation abstraction (#822)
Browse files Browse the repository at this point in the history
* Add alternative instrumentation abstraction
* Provide global access to providers
Remove logs for now, will be replaced with `LoggerProvider` in the future.
  • Loading branch information
Nevay authored Oct 16, 2022
1 parent f73a835 commit f6671dd
Show file tree
Hide file tree
Showing 5 changed files with 339 additions and 0 deletions.
81 changes: 81 additions & 0 deletions src/API/Common/Instrumentation/CachedInstrumentation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\API\Common\Instrumentation;

use ArrayAccess;
use function assert;
use function class_exists;
use OpenTelemetry\API\Metrics\MeterInterface;
use OpenTelemetry\API\Metrics\MeterProviderInterface;
use OpenTelemetry\API\Trace\TracerInterface;
use OpenTelemetry\API\Trace\TracerProviderInterface;
use const PHP_VERSION_ID;

/**
* Provides access to cached {@link TracerInterface} and {@link MeterInterface}
* instances.
*
* Autoinstrumentation should prefer using a {@link CachedInstrumentation}
* instance over repeatedly obtaining instrumentation instances from
* {@link Globals}.
*/
final class CachedInstrumentation
{
private string $name;
private ?string $version;
private ?string $schemaUrl;
private iterable $attributes;
/** @var ArrayAccess<TracerProviderInterface, TracerInterface>|null */
private ?ArrayAccess $tracers;
/** @var ArrayAccess<MeterProviderInterface, MeterInterface>|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);
}
}
80 changes: 80 additions & 0 deletions src/API/Common/Instrumentation/Configurator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\API\Common\Instrumentation;

use OpenTelemetry\API\Metrics\MeterProviderInterface;
use OpenTelemetry\API\Trace\TracerProviderInterface;
use OpenTelemetry\Context\Context;
use OpenTelemetry\Context\ContextInterface;
use OpenTelemetry\Context\ImplicitContextKeyedInterface;
use OpenTelemetry\Context\Propagation\TextMapPropagatorInterface;
use OpenTelemetry\Context\ScopeInterface;

/**
* Configures the global (context scoped) instrumentation instances.
*
* @see Configurator::activate()
*/
final class Configurator implements ImplicitContextKeyedInterface
{
private ?TracerProviderInterface $tracerProvider = null;
private ?MeterProviderInterface $meterProvider = null;
private ?TextMapPropagatorInterface $propagator = null;

private function __construct()
{
}

public static function create(): Configurator
{
return new self();
}

public function activate(): ScopeInterface
{
return $this->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;
}
}
47 changes: 47 additions & 0 deletions src/API/Common/Instrumentation/ContextKeys.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\API\Common\Instrumentation;

use OpenTelemetry\API\Metrics\MeterProviderInterface;
use OpenTelemetry\API\Trace\TracerProviderInterface;
use OpenTelemetry\Context\Context;
use OpenTelemetry\Context\ContextKeyInterface;
use OpenTelemetry\Context\Propagation\TextMapPropagatorInterface;

/**
* @internal
*/
final class ContextKeys
{
/**
* @return ContextKeyInterface<TracerProviderInterface>
*/
public static function tracerProvider(): ContextKeyInterface
{
static $instance;

return $instance ??= Context::createKey(TracerProviderInterface::class);
}

/**
* @return ContextKeyInterface<MeterProviderInterface>
*/
public static function meterProvider(): ContextKeyInterface
{
static $instance;

return $instance ??= Context::createKey(MeterProviderInterface::class);
}

/**
* @return ContextKeyInterface<TextMapPropagatorInterface>
*/
public static function propagator(): ContextKeyInterface
{
static $instance;

return $instance ??= Context::createKey(TextMapPropagatorInterface::class);
}
}
40 changes: 40 additions & 0 deletions src/API/Common/Instrumentation/Globals.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\API\Common\Instrumentation;

use OpenTelemetry\API\Metrics\MeterProviderInterface;
use OpenTelemetry\API\Metrics\Noop\NoopMeterProvider;
use OpenTelemetry\API\Trace\NoopTracerProvider;
use OpenTelemetry\API\Trace\TracerProviderInterface;
use OpenTelemetry\Context\Context;
use OpenTelemetry\Context\Propagation\NoopTextMapPropagator;
use OpenTelemetry\Context\Propagation\TextMapPropagatorInterface;

/**
* Provides access to the globally configured instrumentation instances.
*/
final class Globals
{
public static function tracerProvider(): TracerProviderInterface
{
static $noop;

return Context::getCurrent()->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();
}
}
91 changes: 91 additions & 0 deletions tests/Unit/API/Common/Instrumentation/InstrumentationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Tests\Unit\API\Common\Instrumentation;

use OpenTelemetry\API\Common\Instrumentation\CachedInstrumentation;
use OpenTelemetry\API\Common\Instrumentation\Configurator;
use OpenTelemetry\API\Common\Instrumentation\Globals;
use OpenTelemetry\API\Metrics\MeterInterface;
use OpenTelemetry\API\Metrics\MeterProviderInterface;
use OpenTelemetry\API\Metrics\Noop\NoopMeter;
use OpenTelemetry\API\Metrics\Noop\NoopMeterProvider;
use OpenTelemetry\API\Trace\NoopTracer;
use OpenTelemetry\API\Trace\NoopTracerProvider;
use OpenTelemetry\API\Trace\TracerInterface;
use OpenTelemetry\API\Trace\TracerProviderInterface;
use OpenTelemetry\Context\Propagation\NoopTextMapPropagator;
use OpenTelemetry\Context\Propagation\TextMapPropagatorInterface;
use PHPUnit\Framework\TestCase;

/**
* @covers \OpenTelemetry\API\Common\Instrumentation\Globals
* @covers \OpenTelemetry\API\Common\Instrumentation\CachedInstrumentation
* @covers \OpenTelemetry\API\Common\Instrumentation\Configurator
* @covers \OpenTelemetry\API\Common\Instrumentation\ContextKeys
*/
final class InstrumentationTest extends TestCase
{
public function test_globals_not_configured_returns_noop_instances(): void
{
$this->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();
}
}
}

0 comments on commit f6671dd

Please sign in to comment.