Skip to content

Commit 919d55c

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 03260e6 commit 919d55c

32 files changed

+1045
-6
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Example;
6+
7+
use OpenTelemetry\API\Trace\SpanKind;
8+
use OpenTelemetry\API\Trace\SpanSuppression\SemanticConvention;
9+
10+
final class SemanticConventionResolver implements \OpenTelemetry\API\Trace\SpanSuppression\SemanticConventionResolver
11+
{
12+
#[\Override]
13+
public function resolveSemanticConventions(string $name, ?string $version, ?string $schemaUrl): array
14+
{
15+
if ($schemaUrl === null || !\str_starts_with($schemaUrl, 'https://opentelemetry.io/schemas/')) {
16+
return [];
17+
}
18+
19+
return [
20+
new SemanticConvention('span.azure.cosmosdb.client', SpanKind::KIND_CLIENT, ['db.operation.name'], ['azure.client.id', 'azure.cosmosdb.request.body.size', 'error.type', 'azure.cosmosdb.consistency.level', 'azure.cosmosdb.response.sub_status_code', 'az.namespace', 'azure.cosmosdb.connection.mode', 'azure.cosmosdb.operation.contacted_regions', 'azure.cosmosdb.operation.request_charge', 'user_agent.original', 'db.query.text', 'db.operation.batch.size', 'db.stored_procedure.name', 'server.address', 'db.query.parameter', 'db.namespace', 'db.collection.name', 'db.response.returned_rows', 'db.response.status_code', 'server.port', 'code.*']),
21+
new SemanticConvention('span.cicd.pipeline.task.internal', SpanKind::KIND_INTERNAL, ['cicd.pipeline.task.name', 'cicd.pipeline.task.run.id', 'cicd.pipeline.task.run.url.full'], ['error.type', 'code.*']),
22+
new SemanticConvention('span.db.client', SpanKind::KIND_CLIENT, ['db.system.name'], ['error.type', 'network.peer.address', 'network.peer.port', 'db.operation.batch.size', 'db.response.status_code', 'db.stored_procedure.name', 'db.operation.name', 'server.address', 'server.port', 'db.query.parameter', 'db.query.summary', 'db.query.text', 'db.collection.name', 'db.namespace', 'db.response.returned_rows', 'code.*']),
23+
new SemanticConvention('span.db.elasticsearch.client', SpanKind::KIND_CLIENT, ['http.request.method', 'url.full', 'db.operation.name'], ['error.type', 'elasticsearch.node.name', 'db.operation.batch.size', 'server.address', 'server.port', 'db.collection.name', 'db.namespace', 'db.operation.parameter', 'db.query.text', 'db.response.status_code', 'code.*']),
24+
new SemanticConvention('span.db.hbase.client', SpanKind::KIND_CLIENT, ['db.operation.name'], ['error.type', 'db.operation.batch.size', 'server.address', 'server.port', 'db.collection.name', 'db.namespace', 'db.response.status_code', 'code.*']),
25+
new SemanticConvention('span.db.mongodb.client', SpanKind::KIND_CLIENT, ['db.collection.name', 'db.operation.name'], ['error.type', 'db.operation.batch.size', 'server.address', 'server.port', 'db.namespace', 'db.response.status_code', 'code.*']),
26+
new SemanticConvention('span.db.redis.client', SpanKind::KIND_CLIENT, ['db.operation.name'], ['error.type', 'network.peer.port', 'network.peer.address', 'db.operation.batch.size', 'server.address', 'server.port', 'db.namespace', 'db.query.text', 'db.response.status_code', 'db.stored_procedure.name', 'code.*']),
27+
new SemanticConvention('span.http.client', SpanKind::KIND_CLIENT, ['http.request.method', 'server.address', 'server.port', 'url.full'], ['network.peer.address', 'network.peer.port', 'error.type', 'http.request.body.size', 'http.request.header.*', 'http.request.method_original', 'http.request.resend_count', 'http.request.size', 'http.response.body.size', 'http.response.header.*', 'http.response.size', 'http.response.status_code', 'network.protocol.name', 'network.protocol.version', 'network.transport', 'user_agent.original', 'user_agent.synthetic.type', 'url.scheme', 'url.template', 'code.*']),
28+
new SemanticConvention('span.http.server', SpanKind::KIND_SERVER, ['http.request.method', 'url.path', 'url.scheme'], ['network.peer.address', 'network.peer.port', 'error.type', 'http.request.body.size', 'http.request.method_original', 'http.request.size', 'http.response.body.size', 'http.response.header.*', 'http.response.size', 'http.response.status_code', 'network.protocol.name', 'network.protocol.version', 'network.transport', 'user_agent.synthetic.type', 'client.address', 'client.port', 'http.request.header.*', 'http.route', 'network.local.address', 'network.local.port', 'user_agent.original', 'server.address', 'server.port', 'url.query', 'code.*']),
29+
];
30+
}
31+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Example;
6+
7+
use OpenTelemetry\API\Common\Time\Clock;
8+
use OpenTelemetry\API\Trace\SpanKind;
9+
use OpenTelemetry\Contrib\Otlp\SpanExporter;
10+
use OpenTelemetry\SDK\Common\Export\Stream\StreamTransportFactory;
11+
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
12+
use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;
13+
use OpenTelemetry\SDK\Trace\SpanSuppression\SemanticConventionSuppressionStrategy\SemanticConventionSuppressionStrategy;
14+
use OpenTelemetry\SDK\Trace\TracerProviderBuilder;
15+
16+
require_once __DIR__ . '/../../vendor/autoload.php';
17+
require_once __DIR__ . '/SemanticConventionResolver.php';
18+
19+
// Two nested http client instrumentations
20+
21+
$tp = (new TracerProviderBuilder())
22+
->setResource(ResourceInfoFactory::emptyResource())
23+
->addSpanProcessor(new BatchSpanProcessor(new SpanExporter((new StreamTransportFactory())->create('php://stdout', 'application/x-ndjson')), Clock::getDefault()))
24+
->setSpanSuppressionStrategy(new SemanticConventionSuppressionStrategy([
25+
new SemanticConventionResolver(),
26+
]))
27+
->build()
28+
;
29+
30+
$t = $tp->getTracer('test');
31+
$c1 = $tp
32+
->getTracer('instrumentation-1', schemaUrl: 'https://opentelemetry.io/schemas/1.33.0')
33+
->spanBuilder('GET')
34+
->setSpanKind(SpanKind::KIND_CLIENT)
35+
->setAttributes([
36+
'http.request.method' => 'GET',
37+
'server.address' => 'http://example.com',
38+
'server.port' => '80',
39+
'url.full' => 'http://example.com',
40+
])
41+
->startSpan();
42+
$s1 = $c1->activate();
43+
44+
try {
45+
$c2 = $tp
46+
->getTracer('instrumentation-2', schemaUrl: 'https://opentelemetry.io/schemas/1.31.0')
47+
->spanBuilder('GET')
48+
->setSpanKind(SpanKind::KIND_CLIENT)
49+
->setAttributes([
50+
'http.request.method' => 'GET',
51+
'server.address' => 'http://example.com',
52+
'server.port' => '80',
53+
'url.full' => 'http://example.com',
54+
])
55+
->startSpan();
56+
$s2 = $c2->activate();
57+
58+
try {
59+
// ...
60+
} finally {
61+
$s2->detach();
62+
$c2->end();
63+
}
64+
} finally {
65+
$s1->detach();
66+
$c1->end();
67+
}
68+
69+
$tp->shutdown();
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Example;
6+
7+
use OpenTelemetry\API\Common\Time\Clock;
8+
use OpenTelemetry\API\Trace\SpanKind;
9+
use OpenTelemetry\Contrib\Otlp\SpanExporter;
10+
use OpenTelemetry\SDK\Common\Export\Stream\StreamTransportFactory;
11+
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
12+
use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;
13+
use OpenTelemetry\SDK\Trace\SpanSuppression\SemanticConventionSuppressionStrategy\SemanticConventionSuppressionStrategy;
14+
use OpenTelemetry\SDK\Trace\TracerProviderBuilder;
15+
16+
require_once __DIR__ . '/../../vendor/autoload.php';
17+
require_once __DIR__ . '/SemanticConventionResolver.php';
18+
19+
// Elasticsearch client instrumentation that uses an instrumented http client
20+
21+
$tp = (new TracerProviderBuilder())
22+
->setResource(ResourceInfoFactory::emptyResource())
23+
->addSpanProcessor(new BatchSpanProcessor(new SpanExporter((new StreamTransportFactory())->create('php://stdout', 'application/x-ndjson')), Clock::getDefault()))
24+
->setSpanSuppressionStrategy(new SemanticConventionSuppressionStrategy([
25+
new SemanticConventionResolver(),
26+
]))
27+
->build()
28+
;
29+
30+
$t = $tp->getTracer('test');
31+
32+
$c1 = $tp
33+
->getTracer('elasticsearch-instrumentation', schemaUrl: 'https://opentelemetry.io/schemas/1.33.0')
34+
->spanBuilder('SELECT')
35+
->setSpanKind(SpanKind::KIND_CLIENT)
36+
->setAttributes([
37+
'http.request.method' => 'GET',
38+
'server.address' => 'http://example.com',
39+
'server.port' => '80',
40+
'url.full' => 'http://example.com',
41+
'db.operation.name' => 'SELECT',
42+
])
43+
->startSpan();
44+
$s1 = $c1->activate();
45+
46+
try {
47+
$c2 = $tp
48+
->getTracer('httpclient-instrumentation', schemaUrl: 'https://opentelemetry.io/schemas/1.33.0')
49+
->spanBuilder('GET')
50+
->setSpanKind(SpanKind::KIND_CLIENT)
51+
->setAttributes([
52+
'http.request.method' => 'GET',
53+
'server.address' => 'http://example.com',
54+
'server.port' => '80',
55+
'url.full' => 'http://example.com',
56+
])
57+
->startSpan();
58+
$s2 = $c2->activate();
59+
60+
try {
61+
// ...
62+
} finally {
63+
$s2->detach();
64+
$c2->end();
65+
}
66+
} finally {
67+
$s1->detach();
68+
$c1->end();
69+
}
70+
71+
$tp->shutdown();
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Example;
6+
7+
use OpenTelemetry\API\Common\Time\Clock;
8+
use OpenTelemetry\API\Trace\SpanKind;
9+
use OpenTelemetry\API\Trace\SpanSuppression\SemanticConvention;
10+
use OpenTelemetry\Contrib\Otlp\SpanExporter;
11+
use OpenTelemetry\SDK\Common\Export\Stream\StreamTransportFactory;
12+
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
13+
use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;
14+
use OpenTelemetry\SDK\Trace\SpanSuppression\SemanticConventionSuppressionStrategy\SemanticConventionSuppressionStrategy;
15+
use OpenTelemetry\SDK\Trace\TracerProviderBuilder;
16+
17+
require_once __DIR__ . '/../../vendor/autoload.php';
18+
19+
// Nested Twig template rendering
20+
21+
$tp = (new TracerProviderBuilder())
22+
->setResource(ResourceInfoFactory::emptyResource())
23+
->addSpanProcessor(new BatchSpanProcessor(new SpanExporter((new StreamTransportFactory())->create('php://stdout', 'application/x-ndjson')), Clock::getDefault()))
24+
->setSpanSuppressionStrategy(new SemanticConventionSuppressionStrategy([
25+
new class() implements \OpenTelemetry\API\Trace\SpanSuppression\SemanticConventionResolver {
26+
#[\Override]
27+
public function resolveSemanticConventions(string $name, ?string $version, ?string $schemaUrl): array
28+
{
29+
if ($name !== 'io.open-telemetry.php.twig') {
30+
return [];
31+
}
32+
33+
return [
34+
new SemanticConvention('io.open-telemetry.php.twig.template.render', SpanKind::KIND_INTERNAL, ['twig.template.name'], []),
35+
];
36+
}
37+
},
38+
]))
39+
->build()
40+
;
41+
42+
$t = $tp->getTracer('io.open-telemetry.php.twig');
43+
$c1 = $t->spanBuilder('render index.html.twig')->setAttribute('twig.template.name', 'index.html.twig')->startSpan();
44+
$s1 = $c1->activate();
45+
46+
try {
47+
$c2 = $t->spanBuilder('render header.html.twig')->setAttribute('twig.template.name', 'header.html.twig')->startSpan();
48+
$s2 = $c2->activate();
49+
50+
try {
51+
for ($i = 0; $i < 5; $i++) {
52+
$t->spanBuilder('render meta.html.twig')->setAttribute('twig.template.name', 'meta.html.twig')->startSpan()->end();
53+
}
54+
// ...
55+
} finally {
56+
$s2->detach();
57+
$c2->end();
58+
}
59+
} finally {
60+
$s1->detach();
61+
$c1->end();
62+
}
63+
64+
$tp->shutdown();
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Example;
6+
7+
use OpenTelemetry\API\Common\Time\Clock;
8+
use OpenTelemetry\API\Trace\SpanKind;
9+
use OpenTelemetry\Contrib\Otlp\SpanExporter;
10+
use OpenTelemetry\SDK\Common\Export\Stream\StreamTransportFactory;
11+
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
12+
use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;
13+
use OpenTelemetry\SDK\Trace\SpanSuppression\SpanKindSuppressionStrategy\SpanKindSuppressionStrategy;
14+
use OpenTelemetry\SDK\Trace\TracerProviderBuilder;
15+
16+
require_once __DIR__ . '/../../vendor/autoload.php';
17+
18+
$tp = (new TracerProviderBuilder())
19+
->setResource(ResourceInfoFactory::emptyResource())
20+
->addSpanProcessor(new BatchSpanProcessor(new SpanExporter((new StreamTransportFactory())->create('php://stdout', 'application/x-ndjson')), Clock::getDefault()))
21+
->setSpanSuppressionStrategy(new SpanKindSuppressionStrategy())
22+
->build()
23+
;
24+
25+
$t = $tp->getTracer('test');
26+
$c1 = $t->spanBuilder('client-1')->setSpanKind(SpanKind::KIND_CLIENT)->startSpan();
27+
$s1 = $c1->activate();
28+
29+
try {
30+
$c2 = $t->spanBuilder('client-2')->setSpanKind(SpanKind::KIND_CLIENT)->startSpan();
31+
$s2 = $c2->activate();
32+
33+
try {
34+
// ...
35+
} finally {
36+
$s2->detach();
37+
$c2->end();
38+
}
39+
} finally {
40+
$s1->detach();
41+
$c1->end();
42+
}
43+
44+
$tp->shutdown();

src/API/Trace/Span.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ final public function activate(): ScopeInterface
5858

5959
/** @inheritDoc */
6060
#[\Override]
61-
final public function storeInContext(ContextInterface $context): ContextInterface
61+
public function storeInContext(ContextInterface $context): ContextInterface
6262
{
6363
if (LocalRootSpan::isLocalRoot($context)) {
6464
$context = LocalRootSpan::store($context, $this);
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\API\Trace\SpanSuppression;
6+
7+
/**
8+
* @experimental
9+
*/
10+
final class SemanticConvention
11+
{
12+
/**
13+
* @param list<string> $samplingAttributes
14+
*/
15+
public function __construct(
16+
public readonly string $name,
17+
public readonly int $spanKind,
18+
public readonly array $samplingAttributes,
19+
public readonly array $attributes,
20+
) {
21+
}
22+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\API\Trace\SpanSuppression;
6+
7+
/**
8+
* @experimental
9+
*/
10+
interface SemanticConventionResolver
11+
{
12+
/**
13+
* @return list<SemanticConvention>
14+
*/
15+
public function resolveSemanticConventions(string $name, ?string $version, ?string $schemaUrl): array;
16+
}

src/API/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
},
3636
"extra": {
3737
"branch-alias": {
38-
"dev-main": "1.4.x-dev"
38+
"dev-main": "1.7.x-dev"
3939
},
4040
"spi": {
4141
"OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [

src/SDK/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

0 commit comments

Comments
 (0)