Skip to content

Commit 0f30849

Browse files
Added B3Propagator for B3 Single Header (#691)
* Added B3Propagator Class to handle two configurations and storing debug flag in the returned context * Updated B3Propagator extract according to the clarification in the specs; updated test cases
1 parent 2457c76 commit 0f30849

14 files changed

+1502
-325
lines changed

.gitsplit.yml

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ splits:
2020
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/sdk.git"
2121
- prefix: "src/Contrib"
2222
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/sdk-contrib.git"
23+
- prefix: "src/Extension/Propagator/B3"
24+
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/extension-propagator-b3.git"
2325

2426
# List of references to split (defined as regexp)
2527
origins:

src/API/Trace/Propagation/TraceContextPropagator.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ final class TraceContextPropagator implements TextMapPropagatorInterface
3333
public const TRACESTATE = 'tracestate';
3434
private const VERSION = '00'; // Currently, only '00' is supported
3535

36+
public const FIELDS = [
37+
self::TRACEPARENT,
38+
self::TRACESTATE,
39+
];
40+
3641
private static ?self $instance = null;
3742

3843
public static function getInstance(): self
@@ -47,7 +52,7 @@ public static function getInstance(): self
4752
/** {@inheritdoc} */
4853
public function fields(): array
4954
{
50-
return [self::TRACEPARENT, self::TRACESTATE];
55+
return self::FIELDS;
5156
}
5257

5358
/** {@inheritdoc} */
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Extension\Propagator\B3;
6+
7+
use OpenTelemetry\Context\Context;
8+
use OpenTelemetry\Context\ContextKey;
9+
10+
/**
11+
* @psalm-internal \OpenTelemetry
12+
*/
13+
final class B3DebugFlagContextKey
14+
{
15+
private const KEY_NAME = 'OpenTelemetry Context Key B3 Debug Flag';
16+
17+
private static ?ContextKey $instance = null;
18+
19+
public static function instance(): ContextKey
20+
{
21+
if (self::$instance === null) {
22+
self::$instance = Context::createKey(self::KEY_NAME);
23+
}
24+
25+
return self::$instance;
26+
}
27+
}

src/API/Trace/Propagation/B3MultiPropagator.php renamed to src/Extension/Propagator/B3/B3MultiPropagator.php

+35-24
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
declare(strict_types=1);
44

5-
namespace OpenTelemetry\API\Trace\Propagation;
5+
namespace OpenTelemetry\Extension\Propagator\B3;
66

77
use OpenTelemetry\API\Trace\AbstractSpan;
88
use OpenTelemetry\API\Trace\SpanContext;
@@ -14,9 +14,10 @@
1414
use OpenTelemetry\Context\Propagation\TextMapPropagatorInterface;
1515

1616
/**
17-
* B3Multi is a propagator that supports the specification for the header
18-
* "b3" used for trace context propagation across service boundaries.
19-
* (https://github.com/openzipkin/b3-propagation)
17+
* B3Multi is a propagator that supports the specification for multiple
18+
* "b3" http headers used for trace context propagation across service
19+
* boundaries.
20+
* (https://github.com/openzipkin/b3-propagation#multiple-headers)
2021
*/
2122
final class B3MultiPropagator implements TextMapPropagatorInterface
2223
{
@@ -27,7 +28,7 @@ final class B3MultiPropagator implements TextMapPropagatorInterface
2728
*
2829
* @see https://github.com/openzipkin/b3-propagation#traceid-1
2930
*/
30-
public const TRACE_ID = 'X-B3-TraceId';
31+
private const TRACE_ID = 'X-B3-TraceId';
3132

3233
/**
3334
* The X-B3-SpanId header is required and is encoded as 16 lower-hex characters.
@@ -36,7 +37,7 @@ final class B3MultiPropagator implements TextMapPropagatorInterface
3637
*
3738
* @see https://github.com/openzipkin/b3-propagation#spanid-1
3839
*/
39-
public const SPAN_ID = 'X-B3-SpanId';
40+
private const SPAN_ID = 'X-B3-SpanId';
4041

4142
/**
4243
* The X-B3-ParentSpanId header must be present on a child span and absent on the root span.
@@ -45,7 +46,7 @@ final class B3MultiPropagator implements TextMapPropagatorInterface
4546
*
4647
* @see https://github.com/openzipkin/b3-propagation#parentspanid-1
4748
*/
48-
public const PARENT_SPAN_ID = 'X-B3-ParentSpanId';
49+
private const PARENT_SPAN_ID = 'X-B3-ParentSpanId';
4950

5051
/**
5152
* An accept sampling decision is encoded as X-B3-Sampled: 1 and a deny as X-B3-Sampled: 0.
@@ -57,7 +58,7 @@ final class B3MultiPropagator implements TextMapPropagatorInterface
5758
*
5859
* @see https://github.com/openzipkin/b3-propagation#sampling-state-1
5960
*/
60-
public const SAMPLED = 'X-B3-Sampled';
61+
private const SAMPLED = 'X-B3-Sampled';
6162

6263
/**
6364
* Debug is encoded as X-B3-Flags: 1.
@@ -66,12 +67,14 @@ final class B3MultiPropagator implements TextMapPropagatorInterface
6667
*
6768
* @see https://github.com/openzipkin/b3-propagation#debug-flag
6869
*/
69-
public const DEBUG_FLAG = 'X-B3-Flags';
70+
private const DEBUG_FLAG = 'X-B3-Flags';
7071

7172
private const IS_SAMPLED = '1';
73+
private const VALID_SAMPLED = [self::IS_SAMPLED, 'true'];
7274
private const IS_NOT_SAMPLED = '0';
75+
private const VALID_NON_SAMPLED = [self::IS_NOT_SAMPLED, 'false'];
7376

74-
public const FIELDS = [
77+
private const FIELDS = [
7578
self::TRACE_ID,
7679
self::SPAN_ID,
7780
self::PARENT_SPAN_ID,
@@ -107,17 +110,24 @@ public function inject(&$carrier, PropagationSetterInterface $setter = null, Con
107110
return;
108111
}
109112

113+
// Inject multiple b3 headers
110114
$setter->set($carrier, self::TRACE_ID, $spanContext->getTraceId());
111115
$setter->set($carrier, self::SPAN_ID, $spanContext->getSpanId());
112-
$setter->set($carrier, self::SAMPLED, $spanContext->isSampled() ? self::IS_SAMPLED : self::IS_NOT_SAMPLED);
116+
117+
$debugValue = $context->get(B3DebugFlagContextKey::instance());
118+
if ($debugValue && $debugValue === self::IS_SAMPLED) {
119+
$setter->set($carrier, self::DEBUG_FLAG, self::IS_SAMPLED);
120+
} else {
121+
$setter->set($carrier, self::SAMPLED, $spanContext->isSampled() ? self::IS_SAMPLED : self::IS_NOT_SAMPLED);
122+
}
113123
}
114124

115125
public function extract($carrier, PropagationGetterInterface $getter = null, Context $context = null): Context
116126
{
117127
$getter ??= ArrayAccessGetterSetter::getInstance();
118128
$context ??= Context::getCurrent();
119129

120-
$spanContext = self::extractImpl($carrier, $getter);
130+
$spanContext = self::extractImpl($carrier, $getter, $context);
121131
if (!$spanContext->isValid()) {
122132
return $context;
123133
}
@@ -133,39 +143,40 @@ private static function getSampledValue($carrier, PropagationGetterInterface $ge
133143
return null;
134144
}
135145

136-
if ($value === '0' || $value === '1') {
137-
return (int) $value;
146+
if (in_array(strtolower($value), self::VALID_SAMPLED)) {
147+
return (int) self::IS_SAMPLED;
138148
}
139-
140-
if (strtolower($value) === 'true') {
141-
return 1;
142-
}
143-
if (strtolower($value) === 'false') {
144-
return 0;
149+
if (in_array(strtolower($value), self::VALID_NON_SAMPLED)) {
150+
return (int) self::IS_NOT_SAMPLED;
145151
}
146152

147153
return null;
148154
}
149155

150-
private static function extractImpl($carrier, PropagationGetterInterface $getter): SpanContextInterface
156+
private static function extractImpl($carrier, PropagationGetterInterface $getter, Context &$context): SpanContextInterface
151157
{
152158
$traceId = $getter->get($carrier, self::TRACE_ID);
153159
$spanId = $getter->get($carrier, self::SPAN_ID);
154160
$sampled = self::getSampledValue($carrier, $getter);
161+
$debug = $getter->get($carrier, self::DEBUG_FLAG);
155162

156163
if ($traceId === null || $spanId === null) {
157164
return SpanContext::getInvalid();
158165
}
159166

160-
// Validates the traceId, spanId and sampled
167+
// Validates the traceId and spanId
161168
// Returns an invalid spanContext if any of the checks fail
162169
if (!SpanContext::isValidTraceId($traceId) || !SpanContext::isValidSpanId($spanId)) {
163170
return SpanContext::getInvalid();
164171
}
165172

166-
$isSampled = ($sampled === SpanContext::SAMPLED_FLAG);
173+
if ($debug && $debug === self::IS_SAMPLED) {
174+
$context = $context->with(B3DebugFlagContextKey::instance(), self::IS_SAMPLED);
175+
$isSampled = SpanContext::SAMPLED_FLAG;
176+
} else {
177+
$isSampled = ($sampled === SpanContext::SAMPLED_FLAG);
178+
}
167179

168-
// Only traceparent header is extracted. No tracestate.
169180
return SpanContext::createFromRemoteParent(
170181
$traceId,
171182
$spanId,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Extension\Propagator\B3;
6+
7+
use OpenTelemetry\Context\Context;
8+
use OpenTelemetry\Context\Propagation\ArrayAccessGetterSetter;
9+
use OpenTelemetry\Context\Propagation\PropagationGetterInterface;
10+
use OpenTelemetry\Context\Propagation\PropagationSetterInterface;
11+
use OpenTelemetry\Context\Propagation\TextMapPropagatorInterface;
12+
13+
/**
14+
* B3 is a propagator that supports the specification for the header
15+
* "b3" used for trace context propagation across service boundaries.
16+
* (https://github.com/openzipkin/b3-propagation)
17+
*/
18+
final class B3Propagator implements TextMapPropagatorInterface
19+
{
20+
private TextMapPropagatorInterface $propagator;
21+
22+
private function __construct(TextMapPropagatorInterface $propagator)
23+
{
24+
$this->propagator = $propagator;
25+
}
26+
27+
public static function getB3SingleHeaderInstance(): self
28+
{
29+
static $instance;
30+
31+
return $instance ??= new self(B3SinglePropagator::getInstance());
32+
}
33+
public static function getB3MultiHeaderInstance(): self
34+
{
35+
static $instance;
36+
37+
return $instance ??= new self(B3MultiPropagator::getInstance());
38+
}
39+
40+
/** {@inheritdoc} */
41+
public function fields(): array
42+
{
43+
return $this->propagator->fields();
44+
}
45+
46+
/** {@inheritdoc} */
47+
public function inject(&$carrier, PropagationSetterInterface $setter = null, Context $context = null): void
48+
{
49+
$this->propagator->inject($carrier, $setter, $context);
50+
}
51+
52+
/** {@inheritdoc} */
53+
public function extract($carrier, PropagationGetterInterface $getter = null, Context $context = null): Context
54+
{
55+
$getter ??= ArrayAccessGetterSetter::getInstance();
56+
$context ??= Context::getCurrent();
57+
58+
$b3SingleHeaderContext = B3SinglePropagator::getInstance()->extract($carrier, $getter, $context);
59+
if ($b3SingleHeaderContext !== $context) {
60+
return $b3SingleHeaderContext;
61+
}
62+
63+
return B3MultiPropagator::getInstance()->extract($carrier, $getter, $context);
64+
}
65+
}

0 commit comments

Comments
 (0)