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