Skip to content

Commit 499c58a

Browse files
Added B3MultiPropagater (#680)
* Added B3MultiPropagater * Added static self and removed case insensitive getter function * Added unit tests for B3MultiPropagator class * Refactored B3MultiPropagator and added tests in B3MultiPropagatorTest * Added description for B3MultiPropagator
1 parent 17264f7 commit 499c58a

File tree

2 files changed

+487
-0
lines changed

2 files changed

+487
-0
lines changed

Diff for: src/API/Trace/Propagation/B3MultiPropagator.php

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\API\Trace\Propagation;
6+
7+
use OpenTelemetry\API\Trace\AbstractSpan;
8+
use OpenTelemetry\API\Trace\SpanContext;
9+
use OpenTelemetry\API\Trace\SpanContextInterface;
10+
use OpenTelemetry\Context\Context;
11+
use OpenTelemetry\Context\Propagation\ArrayAccessGetterSetter;
12+
use OpenTelemetry\Context\Propagation\PropagationGetterInterface;
13+
use OpenTelemetry\Context\Propagation\PropagationSetterInterface;
14+
use OpenTelemetry\Context\Propagation\TextMapPropagatorInterface;
15+
16+
/**
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)
20+
*/
21+
final class B3MultiPropagator implements TextMapPropagatorInterface
22+
{
23+
/**
24+
* The X-B3-TraceId header is required and is encoded as 32 or 16 lower-hex characters.
25+
* For example, a 128-bit TraceId header might look like: X-B3-TraceId: 463ac35c9f6413ad48485a3953bb6124 .
26+
* Unless propagating only the Sampling State, the X-B3-TraceId header is required.
27+
*
28+
* @see https://github.com/openzipkin/b3-propagation#traceid-1
29+
*/
30+
public const TRACE_ID = 'X-B3-TraceId';
31+
32+
/**
33+
* The X-B3-SpanId header is required and is encoded as 16 lower-hex characters.
34+
* For example, a SpanId header might look like: X-B3-SpanId: a2fb4a1d1a96d312 .
35+
* Unless propagating only the Sampling State, the X-B3-SpanId header is required.
36+
*
37+
* @see https://github.com/openzipkin/b3-propagation#spanid-1
38+
*/
39+
public const SPAN_ID = 'X-B3-SpanId';
40+
41+
/**
42+
* The X-B3-ParentSpanId header must be present on a child span and absent on the root span.
43+
* It is encoded as 16 lower-hex characters.
44+
* For example, a ParentSpanId header might look like: X-B3-ParentSpanId: 0020000000000001
45+
*
46+
* @see https://github.com/openzipkin/b3-propagation#parentspanid-1
47+
*/
48+
public const PARENT_SPAN_ID = 'X-B3-ParentSpanId';
49+
50+
/**
51+
* An accept sampling decision is encoded as X-B3-Sampled: 1 and a deny as X-B3-Sampled: 0.
52+
* Absent means defer the decision to the receiver of this header.
53+
* For example, a Sampled header might look like: X-B3-Sampled: 1
54+
*
55+
* Note: Before this specification was written, some tracers propagated X-B3-Sampled as true or false as opposed to 1 or 0.
56+
* While you shouldn't encode X-B3-Sampled as true or false, a lenient implementation may accept them.
57+
*
58+
* @see https://github.com/openzipkin/b3-propagation#sampling-state-1
59+
*/
60+
public const SAMPLED = 'X-B3-Sampled';
61+
62+
/**
63+
* Debug is encoded as X-B3-Flags: 1.
64+
* Absent or any other value can be ignored.
65+
* Debug implies an accept decision, so don't also send the X-B3-Sampled header.
66+
*
67+
* @see https://github.com/openzipkin/b3-propagation#debug-flag
68+
*/
69+
public const DEBUG_FLAG = 'X-B3-Flags';
70+
71+
private const IS_SAMPLED = '1';
72+
private const IS_NOT_SAMPLED = '0';
73+
74+
public const FIELDS = [
75+
self::TRACE_ID,
76+
self::SPAN_ID,
77+
self::PARENT_SPAN_ID,
78+
self::SAMPLED,
79+
self::DEBUG_FLAG,
80+
];
81+
82+
private static ?self $instance = null;
83+
84+
public static function getInstance(): self
85+
{
86+
if (null === self::$instance) {
87+
self::$instance = new self();
88+
}
89+
90+
return self::$instance;
91+
}
92+
93+
/** {@inheritdoc} */
94+
public function fields(): array
95+
{
96+
return self::FIELDS;
97+
}
98+
99+
/** {@inheritdoc} */
100+
public function inject(&$carrier, PropagationSetterInterface $setter = null, Context $context = null): void
101+
{
102+
$setter = $setter ?? ArrayAccessGetterSetter::getInstance();
103+
$context = $context ?? Context::getCurrent();
104+
$spanContext = AbstractSpan::fromContext($context)->getContext();
105+
106+
if (!$spanContext->isValid()) {
107+
return;
108+
}
109+
110+
$setter->set($carrier, self::TRACE_ID, $spanContext->getTraceId());
111+
$setter->set($carrier, self::SPAN_ID, $spanContext->getSpanId());
112+
$setter->set($carrier, self::SAMPLED, $spanContext->isSampled() ? self::IS_SAMPLED : self::IS_NOT_SAMPLED);
113+
}
114+
115+
public function extract($carrier, PropagationGetterInterface $getter = null, Context $context = null): Context
116+
{
117+
$getter = $getter ?? ArrayAccessGetterSetter::getInstance();
118+
$context = $context ?? Context::getCurrent();
119+
120+
$spanContext = self::extractImpl($carrier, $getter);
121+
if (!$spanContext->isValid()) {
122+
return $context;
123+
}
124+
125+
return $context->withContextValue(AbstractSpan::wrap($spanContext));
126+
}
127+
128+
private static function getSampledValue($carrier, PropagationGetterInterface $getter): ?int
129+
{
130+
$value = $getter->get($carrier, self::SAMPLED);
131+
132+
if ($value === null) {
133+
return null;
134+
}
135+
136+
if ($value === '0' || $value === '1') {
137+
return (int) $value;
138+
}
139+
140+
if (strtolower($value) === 'true') {
141+
return 1;
142+
}
143+
if (strtolower($value) === 'false') {
144+
return 0;
145+
}
146+
147+
return null;
148+
}
149+
150+
private static function extractImpl($carrier, PropagationGetterInterface $getter): SpanContextInterface
151+
{
152+
$traceId = $getter->get($carrier, self::TRACE_ID);
153+
$spanId = $getter->get($carrier, self::SPAN_ID);
154+
$sampled = self::getSampledValue($carrier, $getter);
155+
156+
if ($traceId === null || $spanId === null) {
157+
return SpanContext::getInvalid();
158+
}
159+
160+
// Validates the traceId, spanId and sampled
161+
// Returns an invalid spanContext if any of the checks fail
162+
if (!SpanContext::isValidTraceId($traceId) || !SpanContext::isValidSpanId($spanId)) {
163+
return SpanContext::getInvalid();
164+
}
165+
166+
$isSampled = ($sampled === SpanContext::SAMPLED_FLAG);
167+
168+
// Only traceparent header is extracted. No tracestate.
169+
return SpanContext::createFromRemoteParent(
170+
$traceId,
171+
$spanId,
172+
$isSampled ? SpanContextInterface::TRACE_FLAG_SAMPLED : SpanContextInterface::TRACE_FLAG_DEFAULT
173+
);
174+
}
175+
}

0 commit comments

Comments
 (0)