Skip to content

Commit 8986bcb

Browse files
authored
Implement span suppression strategies (#1599)
* Implement span suppression strategies Noop: does not suppress anything SpanKind: suppresses nested spans with the same span kind (except for internal) SemConv: attempts to guess and suppress the semantic convention that is represented by the span * Fix style * Add `experimental`/`internal` annotation * Move `SpanSuppressionStrategy` and related classes to SDK * Update examples to avoid usage of internal classes * Move `SpanSuppression` namespace to `API\Trace` * Add override attributes * Prefilter semantic conventions based on sampling relevant attributes * Add basic span suppression test * Only inspect sampling relevant attributes * Inverse attribute masks * Ignore extra attributes if otherwise no semconv is matched * Return singletons where possible * Bump API dependency * Fix span kinds without semantic conventions
1 parent 89b6892 commit 8986bcb

22 files changed

+545
-4
lines changed

Trace/Span.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
use OpenTelemetry\SDK\Common\Exception\StackTraceFormatter;
1515
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeInterface;
1616
use OpenTelemetry\SDK\Resource\ResourceInfo;
17+
use OpenTelemetry\SDK\Trace\SpanSuppression\NoopSuppressionStrategy\NoopSuppression;
18+
use OpenTelemetry\SDK\Trace\SpanSuppression\SpanSuppression;
1719
use Throwable;
1820

1921
final class Span extends API\Span implements ReadWriteSpanInterface
@@ -44,6 +46,7 @@ private function __construct(
4446
private array $links,
4547
private int $totalRecordedLinks,
4648
private readonly int $startEpochNanos,
49+
private readonly SpanSuppression $spanSuppression,
4750
) {
4851
$this->status = StatusData::unset();
4952
}
@@ -73,6 +76,7 @@ public static function startSpan(
7376
array $links,
7477
int $totalRecordedLinks,
7578
int $startEpochNanos,
79+
SpanSuppression $spanSuppression = new NoopSuppression(),
7680
): self {
7781
$span = new self(
7882
$name,
@@ -86,7 +90,8 @@ public static function startSpan(
8690
$attributesBuilder,
8791
$links,
8892
$totalRecordedLinks,
89-
$startEpochNanos !== 0 ? $startEpochNanos : Clock::getDefault()->now()
93+
$startEpochNanos !== 0 ? $startEpochNanos : Clock::getDefault()->now(),
94+
$spanSuppression,
9095
);
9196

9297
// Call onStart here to ensure the span is fully initialized.
@@ -111,6 +116,12 @@ public static function formatStackTrace(Throwable $e, ?array &$seen = null): str
111116
return StackTraceFormatter::format($e);
112117
}
113118

119+
#[\Override]
120+
public function storeInContext(ContextInterface $context): ContextInterface
121+
{
122+
return $this->spanSuppression->suppress(parent::storeInContext($context));
123+
}
124+
114125
/** @inheritDoc */
115126
#[\Override]
116127
public function getContext(): API\SpanContextInterface

Trace/SpanBuilder.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
use OpenTelemetry\Context\ContextInterface;
1111
use OpenTelemetry\SDK\Common\Attribute\AttributesBuilderInterface;
1212
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeInterface;
13+
use OpenTelemetry\SDK\Trace\SpanSuppression\NoopSuppressionStrategy\NoopSuppressor;
14+
use OpenTelemetry\SDK\Trace\SpanSuppression\SpanSuppressor;
1315

1416
final class SpanBuilder implements API\SpanBuilderInterface
1517
{
@@ -32,6 +34,7 @@ public function __construct(
3234
private readonly string $spanName,
3335
private readonly InstrumentationScopeInterface $instrumentationScope,
3436
private readonly TracerSharedState $tracerSharedState,
37+
private readonly SpanSuppressor $spanSuppressor = new NoopSuppressor(),
3538
) {
3639
$this->attributesBuilder = $this->tracerSharedState->getSpanLimits()->getAttributesFactory()->builder();
3740
}
@@ -125,6 +128,11 @@ public function startSpan(): API\SpanInterface
125128
$parentSpan = Span::fromContext($parentContext);
126129
$parentSpanContext = $parentSpan->getContext();
127130

131+
$spanSuppression = $this->spanSuppressor->resolveSuppression($this->spanKind, $this->attributesBuilder->build()->toArray());
132+
if ($spanSuppression->isSuppressed($parentContext)) {
133+
return Span::wrap($parentSpanContext);
134+
}
135+
128136
$spanId = $this->tracerSharedState->getIdGenerator()->generateSpanId();
129137

130138
if (!$parentSpanContext->isValid()) {
@@ -155,6 +163,7 @@ public function startSpan(): API\SpanInterface
155163
);
156164

157165
if (!in_array($samplingDecision, [SamplingResult::RECORD_AND_SAMPLE, SamplingResult::RECORD_ONLY], true)) {
166+
// TODO must suppress no-op spans too
158167
return Span::wrap($spanContext);
159168
}
160169

@@ -176,7 +185,8 @@ public function startSpan(): API\SpanInterface
176185
$attributesBuilder,
177186
$this->links,
178187
$this->totalNumberOfLinksAdded,
179-
$this->startEpochNanos
188+
$this->startEpochNanos,
189+
$spanSuppression,
180190
);
181191
}
182192
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\SDK\Trace\SpanSuppression\NoopSuppressionStrategy;
6+
7+
use OpenTelemetry\Context\ContextInterface;
8+
use OpenTelemetry\SDK\Trace\SpanSuppression\SpanSuppression;
9+
10+
/**
11+
* @internal
12+
*/
13+
final class NoopSuppression implements SpanSuppression
14+
{
15+
#[\Override]
16+
public function isSuppressed(ContextInterface $context): bool
17+
{
18+
return false;
19+
}
20+
21+
#[\Override]
22+
public function suppress(ContextInterface $context): ContextInterface
23+
{
24+
return $context;
25+
}
26+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\SDK\Trace\SpanSuppression\NoopSuppressionStrategy;
6+
7+
use OpenTelemetry\SDK\Trace\SpanSuppression\SpanSuppressionStrategy;
8+
use OpenTelemetry\SDK\Trace\SpanSuppression\SpanSuppressor;
9+
10+
/**
11+
* @experimental
12+
*/
13+
final class NoopSuppressionStrategy implements SpanSuppressionStrategy
14+
{
15+
#[\Override]
16+
public function getSuppressor(string $name, ?string $version, ?string $schemaUrl): SpanSuppressor
17+
{
18+
static $suppressor = new NoopSuppressor();
19+
20+
return $suppressor;
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\SDK\Trace\SpanSuppression\NoopSuppressionStrategy;
6+
7+
use OpenTelemetry\SDK\Trace\SpanSuppression\SpanSuppression;
8+
use OpenTelemetry\SDK\Trace\SpanSuppression\SpanSuppressor;
9+
10+
/**
11+
* @internal
12+
*/
13+
final class NoopSuppressor implements SpanSuppressor
14+
{
15+
#[\Override]
16+
public function resolveSuppression(int $spanKind, array $attributes): SpanSuppression
17+
{
18+
static $suppression = new NoopSuppression();
19+
20+
return $suppression;
21+
}
22+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\SDK\Trace\SpanSuppression\SemanticConventionSuppressionStrategy;
6+
7+
/**
8+
* @internal
9+
*/
10+
final class CompiledSemanticConventionAttribute
11+
{
12+
public function __construct(
13+
public readonly string $name,
14+
public readonly int $notSamplingRelevantIn,
15+
public readonly int $includedIn,
16+
) {
17+
}
18+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\SDK\Trace\SpanSuppression\SemanticConventionSuppressionStrategy;
6+
7+
use OpenTelemetry\Context\ContextInterface;
8+
use OpenTelemetry\Context\ContextKeyInterface;
9+
use OpenTelemetry\SDK\Trace\SpanSuppression\SpanSuppression;
10+
11+
/**
12+
* @internal
13+
*/
14+
final class SemanticConventionSuppression implements SpanSuppression
15+
{
16+
public function __construct(
17+
private readonly ContextKeyInterface $contextKey,
18+
private readonly array $semanticConventions,
19+
) {
20+
}
21+
22+
#[\Override]
23+
public function isSuppressed(ContextInterface $context): bool
24+
{
25+
$suppressedConventions = $context->get($this->contextKey);
26+
if ($suppressedConventions === null) {
27+
return false;
28+
}
29+
30+
foreach ($this->semanticConventions as $semanticConvention) {
31+
if (!isset($suppressedConventions[$semanticConvention])) {
32+
return false;
33+
}
34+
}
35+
36+
return true;
37+
}
38+
39+
#[\Override]
40+
public function suppress(ContextInterface $context): ContextInterface
41+
{
42+
$suppressedConventions = $context->get($this->contextKey) ?? [];
43+
foreach ($this->semanticConventions as $semanticConvention) {
44+
$suppressedConventions[$semanticConvention] ??= true;
45+
}
46+
47+
return $context->with($this->contextKey, $suppressedConventions);
48+
}
49+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\SDK\Trace\SpanSuppression\SemanticConventionSuppressionStrategy;
6+
7+
use OpenTelemetry\Context\ContextKeyInterface;
8+
9+
/**
10+
* @implements ContextKeyInterface<array<string, true>>
11+
*
12+
* @internal
13+
*/
14+
enum SemanticConventionSuppressionContextKey implements ContextKeyInterface
15+
{
16+
case Internal;
17+
case Client;
18+
case Server;
19+
case Producer;
20+
case Consumer;
21+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\SDK\Trace\SpanSuppression\SemanticConventionSuppressionStrategy;
6+
7+
use function array_key_last;
8+
use function array_merge;
9+
use function assert;
10+
use OpenTelemetry\API\Trace\SpanSuppression\SemanticConventionResolver;
11+
use OpenTelemetry\SDK\Trace\SpanSuppression\SpanSuppressionStrategy;
12+
use OpenTelemetry\SDK\Trace\SpanSuppression\SpanSuppressor;
13+
use function strcspn;
14+
use function strlen;
15+
16+
/**
17+
* @experimental
18+
*/
19+
final class SemanticConventionSuppressionStrategy implements SpanSuppressionStrategy
20+
{
21+
/**
22+
* @param iterable<SemanticConventionResolver> $resolvers
23+
*/
24+
public function __construct(
25+
private readonly iterable $resolvers,
26+
) {
27+
}
28+
29+
#[\Override]
30+
public function getSuppressor(string $name, ?string $version, ?string $schemaUrl): SpanSuppressor
31+
{
32+
$semanticConventions = [];
33+
foreach ($this->resolvers as $resolver) {
34+
$semanticConventions[] = $resolver->resolveSemanticConventions($name, $version, $schemaUrl);
35+
}
36+
$semanticConventions = array_merge(...$semanticConventions);
37+
38+
$lookup = [];
39+
foreach ($semanticConventions as $semanticConvention) {
40+
foreach ($semanticConvention->samplingAttributes as $attribute) {
41+
assert(strcspn($attribute, '*?') === strlen($attribute));
42+
$lookup[$semanticConvention->spanKind][$attribute] ??= [0, 0];
43+
}
44+
}
45+
46+
$compiledSemanticConventions = [];
47+
foreach ($semanticConventions as $semanticConvention) {
48+
$attributes = new WildcardPattern();
49+
foreach ($semanticConvention->samplingAttributes as $attribute) {
50+
$attributes->add($attribute);
51+
}
52+
foreach ($semanticConvention->attributes as $attribute) {
53+
$attributes->add($attribute);
54+
}
55+
56+
$compiledSemanticConventions[$semanticConvention->spanKind][] = $semanticConvention->name;
57+
$i = array_key_last($compiledSemanticConventions[$semanticConvention->spanKind]);
58+
59+
foreach ($semanticConvention->samplingAttributes as $attribute) {
60+
$lookup[$semanticConvention->spanKind][$attribute][0] |= 1 << $i;
61+
}
62+
foreach ($lookup[$semanticConvention->spanKind] as $attribute => $_) {
63+
if (!$attributes->matches($attribute)) {
64+
$lookup[$semanticConvention->spanKind][$attribute][1] |= 1 << $i;
65+
}
66+
}
67+
}
68+
69+
$compiledLookupAttributes = [];
70+
foreach ($lookup as $spanKind => $attributes) {
71+
foreach ($attributes as $attribute => $masks) {
72+
$compiledLookupAttributes[$spanKind][] = new CompiledSemanticConventionAttribute($attribute, ~$masks[0], ~$masks[1]);
73+
}
74+
}
75+
76+
return new SemanticConventionSuppressor($compiledSemanticConventions, $compiledLookupAttributes);
77+
}
78+
}

0 commit comments

Comments
 (0)