Skip to content

Commit

Permalink
Add span links capabilities to the OTel API (#2451)
Browse files Browse the repository at this point in the history
* feat: Basic OTel Span Links Functionalities

* test: Add Basic Interoperability Tests

* feat: Partially handle on-the-fly addition of span links

* feat: Handle Span Links Removal (Interoperability)

* test: Duplicate Instances Addition

* fix: Prevent creation of useless instances

* Remove unused instance variable

Co-authored-by: Bob Weinand <[email protected]>

---------

Co-authored-by: Bob Weinand <[email protected]>
  • Loading branch information
PROFeNoM and bwoebi authored Jan 10, 2024
1 parent 64d9492 commit 4568c41
Show file tree
Hide file tree
Showing 5 changed files with 351 additions and 12 deletions.
17 changes: 15 additions & 2 deletions src/DDTrace/OpenTelemetry/Context.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use DDTrace\Tag;
use DDTrace\Util\ObjectKVStore;
use OpenTelemetry\API\Trace as API;
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Common\Attribute\AttributesFactory;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactory;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeInterface;
Expand Down Expand Up @@ -176,6 +177,18 @@ private static function activateParent(?SpanData $currentSpan): ContextInterface
: null;
$traceState = new API\TraceState($traceContext['tracestate'] ?? null);

// Check for span links
$links = [];
foreach ($currentSpan->links as $spanLink) {
$linkSpanContext = API\SpanContext::create(
$spanLink->traceId,
$spanLink->spanId,
API\TraceFlags::DEFAULT,
new API\TraceState($spanLink->traceState ?? null),
);
$links[] = new SDK\Link($linkSpanContext, Attributes::create($spanLink->attributes));
}

$OTelCurrentSpan = SDK\Span::startSpan(
$currentSpan,
API\SpanContext::create($currentTraceId, $currentSpanId, $traceFlags, $traceState), // $context
Expand All @@ -186,8 +199,8 @@ private static function activateParent(?SpanData $currentSpan): ContextInterface
NoopSpanProcessor::getInstance(), // $spanProcessor
ResourceInfoFactory::defaultResource(), // $resource
(new AttributesFactory())->builder(), // $attributesBuilder
[], // TODO: Handle Span Links
0, // TODO: Handle Span Links
$links, // $links
count($links), // $totalRecordedLinks
false // The span was created using the DD Api
);
ObjectKVStore::put($currentSpan, 'otel_span', $OTelCurrentSpan);
Expand Down
76 changes: 74 additions & 2 deletions src/DDTrace/OpenTelemetry/Span.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace OpenTelemetry\SDK\Trace;

use DDTrace\SpanData;
use DDTrace\SpanLink;
use DDTrace\Tag;
use DDTrace\Util\Convention;
use DDTrace\Util\ObjectKVStore;
Expand Down Expand Up @@ -35,6 +36,16 @@ final class Span extends API\Span implements ReadWriteSpanInterface
/** @readonly */
private SpanProcessorInterface $spanProcessor;

/**
* @readonly
*
* @var list<LinkInterface>
*/
private array $links;

/** @readonly */
private int $totalRecordedLinks;

/** @readonly */
private int $kind;

Expand All @@ -56,6 +67,8 @@ private function __construct(
API\SpanContextInterface $parentSpanContext,
SpanProcessorInterface $spanProcessor,
ResourceInfo $resource,
array $links = [],
int $totalRecordedLinks = 0,
bool $isRemapped = true
) {
$this->span = $span;
Expand All @@ -65,6 +78,8 @@ private function __construct(
$this->parentSpanContext = $parentSpanContext;
$this->spanProcessor = $spanProcessor;
$this->resource = $resource;
$this->links = $links;
$this->totalRecordedLinks = $totalRecordedLinks;

$this->status = StatusData::unset();

Expand All @@ -75,6 +90,27 @@ private function __construct(
// done in serializer.c:ddtrace_set_root_span_properties (as of v0.92.0)
$span->name = $this->operationNameConvention = Convention::defaultOperationName($span);
}

// Set the span links
if ($isRemapped) {
// At initialization time (now), only set the links if the span was created using the OTel API
// Otherwise, the links were already set in DD's OpenTelemetry\Context\Context
foreach ($links as $link) {
/** @var LinkInterface $link */
$linkContext = $link->getSpanContext();

$spanLink = new SpanLink();
$spanLink->traceId = $linkContext->getTraceId();
$spanLink->spanId = $linkContext->getSpanId();
$spanLink->traceState = (string)$linkContext->getTraceState(); // __toString()
$spanLink->attributes = $link->getAttributes()->toArray();
$spanLink->droppedAttributesCount = 0; // Attributes limit aren't supported/meaningful in DD

// Save the link
ObjectKVStore::put($spanLink, "link", $link);
$span->links[] = $spanLink;
}
}
}

/**
Expand Down Expand Up @@ -116,6 +152,8 @@ public static function startSpan(
$parentSpan->getContext(),
$spanProcessor,
$resource,
$links,
$totalRecordedLinks,
$isRemapped
);

Expand Down Expand Up @@ -162,10 +200,12 @@ public function toSpanData(): SpanDataInterface
{
$hasEnded = $this->hasEnded();

$this->updateSpanLinks();

return new ImmutableSpan(
$this,
$this->getName(),
[], // TODO: Handle Span Links
$this->links,
[], // TODO: Handle Span Events
Attributes::create(array_merge($this->span->meta, $this->span->metrics)),
0,
Expand Down Expand Up @@ -206,7 +246,7 @@ public function getStartEpochNanos(): int

public function getTotalRecordedLinks(): int
{
return 0;
return $this->totalRecordedLinks;
}

public function getTotalRecordedEvents(): int
Expand Down Expand Up @@ -400,4 +440,36 @@ public function getDDSpan(): SpanData
{
return $this->span;
}

private function updateSpanLinks()
{
// Important: Span Links are supposed immutable
$datadogSpanLinks = $this->span->links;

$otel = [];
foreach ($datadogSpanLinks as $datadogSpanLink) {
// Check if the link relationship exists
$link = ObjectKVStore::get($datadogSpanLink, "link");
if ($link === null) {
// Create the link
$link = new Link(
API\SpanContext::create(
$datadogSpanLink->traceId,
$datadogSpanLink->spanId,
$this->context->getTraceFlags(),
new API\TraceState($datadogSpanLink->traceState ?? null)
),
Attributes::create($datadogSpanLink->attributes ?? [])
);

// Save the link
ObjectKVStore::put($datadogSpanLink, "link", $link);
}
$otel[] = $link;
}

// Update the links
$this->links = $otel;
$this->totalRecordedLinks = count($otel);
}
}
17 changes: 15 additions & 2 deletions src/DDTrace/OpenTelemetry/SpanBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,20 @@ public function setParent($context): API\SpanBuilderInterface

public function addLink(SpanContextInterface $context, iterable $attributes = []): SpanBuilderInterface
{
// TODO: Span Links are future works
if (!$context->isValid()) {
return $this;
}

$this->totalNumberOfLinksAdded++;

$this->links[] = new Link(
$context,
$this->tracerSharedState
->getSpanLimits()
->getLinkAttributesFactory()
->builder($attributes)
->build(),
);

return $this;
}
Expand Down Expand Up @@ -186,7 +199,7 @@ public function startSpan(): SpanInterface
$this->tracerSharedState->getResource(),
$attributesBuilder,
$this->links,
0,
$this->totalNumberOfLinksAdded,
);
}

Expand Down
Loading

0 comments on commit 4568c41

Please sign in to comment.