adding LocalRootSpan (#1310)
* adding LocalRootSpan class this is based on Java's implementation, https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/v2.3.0/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/LocalRootSpan.java and adds the ability to identify and locate the "local root span" in a trace. The local root span is the top-level active span which has either an invalid or remote parent. It's tracked automatically as part of making a span active, either via `Span::activate()` or `Span::storeInContext()`, and can be retrieved in a couple of ways, but most easily via `LocalRootSpan::current()`. * remove redundant local root span check * move context key to api * internal * adding example * mark LocalRootSpan as experimental * adding an example of local root span usage * style, fix broken build
This commit is contained in:
		
							parent
							
								
									68b1b43cab
								
							
						
					
					
						commit
						10b8d9732b
					
				| 
						 | 
				
			
			@ -0,0 +1,42 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
use OpenTelemetry\API\Trace\LocalRootSpan;
 | 
			
		||||
use OpenTelemetry\API\Trace\Span;
 | 
			
		||||
use OpenTelemetry\API\Trace\SpanKind;
 | 
			
		||||
use OpenTelemetry\SDK\Common\Util\ShutdownHandler;
 | 
			
		||||
use OpenTelemetry\SDK\Propagation\PropagatorFactory;
 | 
			
		||||
use OpenTelemetry\SDK\Trace\TracerProviderFactory;
 | 
			
		||||
 | 
			
		||||
putenv('OTEL_TRACES_EXPORTER=console');
 | 
			
		||||
putenv('OTEL_PHP_DETECTORS=none');
 | 
			
		||||
 | 
			
		||||
require_once __DIR__ . '/../../../vendor/autoload.php';
 | 
			
		||||
 | 
			
		||||
$provider = (new TracerProviderFactory())->create();
 | 
			
		||||
$propagator = (new PropagatorFactory())->create();
 | 
			
		||||
ShutdownHandler::register([$provider, 'shutdown']);
 | 
			
		||||
$tracer = $provider->getTracer('example');
 | 
			
		||||
 | 
			
		||||
//start and activate a root span
 | 
			
		||||
$root = $tracer
 | 
			
		||||
    ->spanBuilder('root')
 | 
			
		||||
    ->setSpanKind(SpanKind::KIND_SERVER)
 | 
			
		||||
    ->startSpan();
 | 
			
		||||
$rootScope = $root->activate();
 | 
			
		||||
 | 
			
		||||
//start and activate a child span
 | 
			
		||||
$child = $tracer
 | 
			
		||||
    ->spanBuilder('child')
 | 
			
		||||
    ->startSpan();
 | 
			
		||||
$childScope = $child->activate();
 | 
			
		||||
 | 
			
		||||
//update the name of the root span
 | 
			
		||||
LocalRootSpan::current()->updateName('updated')->setAttribute('my-attr', true);
 | 
			
		||||
 | 
			
		||||
//end spans and detach contexts
 | 
			
		||||
$child->end();
 | 
			
		||||
$childScope->detach();
 | 
			
		||||
$root->end();
 | 
			
		||||
$rootScope->detach();
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,58 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
/**
 | 
			
		||||
 * @see https://opentelemetry.io/docs/specs/semconv/messaging/messaging-spans/#consumer-spans
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
use OpenTelemetry\API\Trace\LocalRootSpan;
 | 
			
		||||
use OpenTelemetry\API\Trace\Span;
 | 
			
		||||
use OpenTelemetry\API\Trace\SpanContext;
 | 
			
		||||
use OpenTelemetry\API\Trace\SpanKind;
 | 
			
		||||
use OpenTelemetry\Context\Context;
 | 
			
		||||
use OpenTelemetry\SDK\Common\Util\ShutdownHandler;
 | 
			
		||||
use OpenTelemetry\SDK\Propagation\PropagatorFactory;
 | 
			
		||||
use OpenTelemetry\SDK\Trace\TracerProviderFactory;
 | 
			
		||||
 | 
			
		||||
putenv('OTEL_TRACES_EXPORTER=console');
 | 
			
		||||
putenv('OTEL_PHP_DETECTORS=none');
 | 
			
		||||
 | 
			
		||||
require_once __DIR__ . '/../../../vendor/autoload.php';
 | 
			
		||||
 | 
			
		||||
$provider = (new TracerProviderFactory())->create();
 | 
			
		||||
$propagator = (new PropagatorFactory())->create();
 | 
			
		||||
ShutdownHandler::register([$provider, 'shutdown']);
 | 
			
		||||
$tracer = $provider->getTracer('example');
 | 
			
		||||
 | 
			
		||||
// “Receive” spans SHOULD be created for operations of passing messages to the application when those operations are initiated by the application code (pull-based scenarios).
 | 
			
		||||
$root = $tracer
 | 
			
		||||
    ->spanBuilder('receive')
 | 
			
		||||
    ->setSpanKind(SpanKind::KIND_CONSUMER)
 | 
			
		||||
    ->startSpan();
 | 
			
		||||
 | 
			
		||||
$rootScope = $root
 | 
			
		||||
    ->storeInContext(Context::getCurrent())
 | 
			
		||||
    ->activate();
 | 
			
		||||
assert(LocalRootSpan::fromContext(Context::getCurrent()) === $root);
 | 
			
		||||
 | 
			
		||||
$root->addLink(SpanContext::createFromRemoteParent('fabebb164f22d4afc51df50d9a3ff629', '87c6836d8610ac6d', 768));
 | 
			
		||||
 | 
			
		||||
// “Process” spans MAY be created in addition to “Receive” spans for pull-based scenarios for operations of processing messages.
 | 
			
		||||
$child = $tracer
 | 
			
		||||
    ->spanBuilder('process')
 | 
			
		||||
    ->startSpan();
 | 
			
		||||
$childScope = $child
 | 
			
		||||
    //->storeInContext($remoteContext) // preserve remote baggage etc.
 | 
			
		||||
    ->storeInContext(Context::getCurrent())
 | 
			
		||||
    ->activate();
 | 
			
		||||
$child->setAttribute('local_root', LocalRootSpan::current() === Span::getCurrent());
 | 
			
		||||
 | 
			
		||||
try {
 | 
			
		||||
    assert(LocalRootSpan::current() === $root);
 | 
			
		||||
    assert(LocalRootSpan::current() !== Span::getCurrent());
 | 
			
		||||
} finally {
 | 
			
		||||
    $root->end();
 | 
			
		||||
    $child->end();
 | 
			
		||||
    $childScope->detach();
 | 
			
		||||
    $rootScope->detach();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -20,4 +20,9 @@ class LateBindingLogger implements LoggerInterface
 | 
			
		|||
    {
 | 
			
		||||
        ($this->logger ??= ($this->factory)())->emit($logRecord);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function enabled(): bool
 | 
			
		||||
    {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,4 +20,9 @@ class LateBindingTracer implements TracerInterface
 | 
			
		|||
    {
 | 
			
		||||
        return ($this->tracer ??= ($this->factory)())->spanBuilder($spanName);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function enabled(): bool
 | 
			
		||||
    {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,63 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace OpenTelemetry\API\Trace;
 | 
			
		||||
 | 
			
		||||
use OpenTelemetry\Context\Context;
 | 
			
		||||
use OpenTelemetry\Context\ContextInterface;
 | 
			
		||||
use OpenTelemetry\Context\ContextKeyInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @experimental
 | 
			
		||||
 */
 | 
			
		||||
class LocalRootSpan
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Retrieve the local root span. This is the root-most active span which has
 | 
			
		||||
     * a remote or invalid parent.
 | 
			
		||||
     * If there is no active local root span, then an invalid span is returned.
 | 
			
		||||
     * @experimental
 | 
			
		||||
     */
 | 
			
		||||
    public static function current(): SpanInterface
 | 
			
		||||
    {
 | 
			
		||||
        return self::fromContext(Context::getCurrent());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Retrieve the local root span from a Context.
 | 
			
		||||
     * @experimental
 | 
			
		||||
     */
 | 
			
		||||
    public static function fromContext(ContextInterface $context): SpanInterface
 | 
			
		||||
    {
 | 
			
		||||
        return $context->get(self::key()) ?? Span::getInvalid();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @internal
 | 
			
		||||
     */
 | 
			
		||||
    public static function store(ContextInterface $context, SpanInterface $span): ContextInterface
 | 
			
		||||
    {
 | 
			
		||||
        return $context->with(self::key(), $span);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @internal
 | 
			
		||||
     */
 | 
			
		||||
    public static function key(): ContextKeyInterface
 | 
			
		||||
    {
 | 
			
		||||
        static $key;
 | 
			
		||||
 | 
			
		||||
        return $key ??= Context::createKey(self::class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @internal
 | 
			
		||||
     */
 | 
			
		||||
    public static function isLocalRoot(ContextInterface $parentContext): bool
 | 
			
		||||
    {
 | 
			
		||||
        $spanContext = Span::fromContext($parentContext)->getContext();
 | 
			
		||||
 | 
			
		||||
        return !$spanContext->isValid() || $spanContext->isRemote();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -54,6 +54,10 @@ abstract class Span implements SpanInterface
 | 
			
		|||
    /** @inheritDoc */
 | 
			
		||||
    final public function storeInContext(ContextInterface $context): ContextInterface
 | 
			
		||||
    {
 | 
			
		||||
        if (LocalRootSpan::isLocalRoot($context)) {
 | 
			
		||||
            $context = LocalRootSpan::store($context, $this);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $context->with(ContextKeys::span(), $this);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,49 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace OpenTelemetry\Tests\Integration\SDK;
 | 
			
		||||
 | 
			
		||||
use OpenTelemetry\API\Trace\LocalRootSpan;
 | 
			
		||||
use OpenTelemetry\API\Trace\Span;
 | 
			
		||||
use OpenTelemetry\API\Trace\SpanInterface;
 | 
			
		||||
use OpenTelemetry\Context\Context;
 | 
			
		||||
use OpenTelemetry\SDK\Trace\TracerProvider;
 | 
			
		||||
use PHPUnit\Framework\Attributes\CoversNothing;
 | 
			
		||||
use PHPUnit\Framework\TestCase;
 | 
			
		||||
 | 
			
		||||
#[CoversNothing]
 | 
			
		||||
class LocalRootSpanTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
    private SpanInterface $span;
 | 
			
		||||
 | 
			
		||||
    public function setUp(): void
 | 
			
		||||
    {
 | 
			
		||||
        $tracerProvider = new TracerProvider();
 | 
			
		||||
        $this->span = $tracerProvider->getTracer('test')->spanBuilder('my-local-root-span')->startSpan();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function test_active_root_span_is_local_root(): void
 | 
			
		||||
    {
 | 
			
		||||
        $scope = $this->span->activate();
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            $this->assertSame($this->span, LocalRootSpan::current());
 | 
			
		||||
        } finally {
 | 
			
		||||
            $scope->detach();
 | 
			
		||||
        }
 | 
			
		||||
        $this->assertSame(Span::getInvalid(), LocalRootSpan::current(), 'root span ended, a local root span does not exist');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function test_root_span_stored_in_context_is_local_root(): void
 | 
			
		||||
    {
 | 
			
		||||
        $root = Context::getRoot();
 | 
			
		||||
        Context::storage()->attach($this->span->storeInContext($root));
 | 
			
		||||
        $this->assertSame($this->span, LocalRootSpan::current());
 | 
			
		||||
        $scope = Context::storage()->scope();
 | 
			
		||||
        $this->assertNotNull($scope);
 | 
			
		||||
        $this->assertSame($this->span, Span::fromContext($scope->context()));
 | 
			
		||||
        $scope->detach();
 | 
			
		||||
        $this->assertSame(Span::getInvalid(), LocalRootSpan::current(), 'root span ended, a local root span does not exist');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,58 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace OpenTelemetry\Tests\Unit\API\Trace;
 | 
			
		||||
 | 
			
		||||
use OpenTelemetry\API\Trace\LocalRootSpan;
 | 
			
		||||
use OpenTelemetry\API\Trace\Span;
 | 
			
		||||
use OpenTelemetry\API\Trace\SpanContext;
 | 
			
		||||
use OpenTelemetry\Context\Context;
 | 
			
		||||
use PHPUnit\Framework\Attributes\CoversClass;
 | 
			
		||||
use PHPUnit\Framework\TestCase;
 | 
			
		||||
 | 
			
		||||
#[CoversClass(LocalRootSpan::class)]
 | 
			
		||||
class LocalRootSpanTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
    public function test_span_with_remote_parent_is_local_root(): void
 | 
			
		||||
    {
 | 
			
		||||
        $context = Context::getRoot()->with(
 | 
			
		||||
            LocalRootSpan::key(),
 | 
			
		||||
            Span::wrap(
 | 
			
		||||
                SpanContext::createFromRemoteParent(
 | 
			
		||||
                    '00000000000000000000000000000001',
 | 
			
		||||
                    '0000000000000002',
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
        $this->assertTrue(LocalRootSpan::isLocalRoot($context));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function test_get_local_root_span(): void
 | 
			
		||||
    {
 | 
			
		||||
        $span = Span::getInvalid();
 | 
			
		||||
        $context = LocalRootSpan::store(Context::getCurrent(), $span);
 | 
			
		||||
        $scope = $context->activate();
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            $this->assertSame($span, LocalRootSpan::fromContext($context));
 | 
			
		||||
            $this->assertSame($span, LocalRootSpan::current());
 | 
			
		||||
        } finally {
 | 
			
		||||
            $scope->detach();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function test_get_local_root_span_when_not_set(): void
 | 
			
		||||
    {
 | 
			
		||||
        $context = Context::getRoot();
 | 
			
		||||
        $scope = $context->activate();
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            $this->assertFalse(LocalRootSpan::fromContext($context)->getContext()->isValid());
 | 
			
		||||
            $this->assertFalse(LocalRootSpan::current()->getContext()->isValid());
 | 
			
		||||
            $this->assertSame(Span::getInvalid(), LocalRootSpan::current());
 | 
			
		||||
        } finally {
 | 
			
		||||
            $scope->detach();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue