Skip to content

Commit

Permalink
adding LocalRootSpan (open-telemetry#1310)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
brettmc authored Aug 6, 2024
1 parent 68b1b43 commit 10b8d97
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 0 deletions.
42 changes: 42 additions & 0 deletions examples/traces/features/local_root_span.php
Original file line number Diff line number Diff line change
@@ -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();
58 changes: 58 additions & 0 deletions examples/traces/features/local_root_span_with_remote_parent.php
Original file line number Diff line number Diff line change
@@ -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();
}
5 changes: 5 additions & 0 deletions src/API/Logs/LateBindingLogger.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,9 @@ public function emit(LogRecord $logRecord): void
{
($this->logger ??= ($this->factory)())->emit($logRecord);
}

public function enabled(): bool
{
return true;
}
}
5 changes: 5 additions & 0 deletions src/API/Trace/LateBindingTracer.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,9 @@ public function spanBuilder(string $spanName): SpanBuilderInterface
{
return ($this->tracer ??= ($this->factory)())->spanBuilder($spanName);
}

public function enabled(): bool
{
return true;
}
}
63 changes: 63 additions & 0 deletions src/API/Trace/LocalRootSpan.php
Original file line number Diff line number Diff line change
@@ -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();
}
}
4 changes: 4 additions & 0 deletions src/API/Trace/Span.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ final public function activate(): ScopeInterface
/** @inheritDoc */
final public function storeInContext(ContextInterface $context): ContextInterface
{
if (LocalRootSpan::isLocalRoot($context)) {
$context = LocalRootSpan::store($context, $this);
}

return $context->with(ContextKeys::span(), $this);
}
}
49 changes: 49 additions & 0 deletions tests/Integration/SDK/LocalRootSpanTest.php
Original file line number Diff line number Diff line change
@@ -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');
}
}
58 changes: 58 additions & 0 deletions tests/Unit/API/Trace/LocalRootSpanTest.php
Original file line number Diff line number Diff line change
@@ -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();
}
}
}

0 comments on commit 10b8d97

Please sign in to comment.