Skip to content

Commit

Permalink
Added B3Propagator for B3 Single Header (#691)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
kishannsangani committed Aug 24, 2022
1 parent 2457c76 commit 0f30849
Show file tree
Hide file tree
Showing 14 changed files with 1,502 additions and 325 deletions.
2 changes: 2 additions & 0 deletions .gitsplit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ splits:
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/sdk.git"
- prefix: "src/Contrib"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/sdk-contrib.git"
- prefix: "src/Extension/Propagator/B3"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/extension-propagator-b3.git"

# List of references to split (defined as regexp)
origins:
Expand Down
7 changes: 6 additions & 1 deletion src/API/Trace/Propagation/TraceContextPropagator.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ final class TraceContextPropagator implements TextMapPropagatorInterface
public const TRACESTATE = 'tracestate';
private const VERSION = '00'; // Currently, only '00' is supported

public const FIELDS = [
self::TRACEPARENT,
self::TRACESTATE,
];

private static ?self $instance = null;

public static function getInstance(): self
Expand All @@ -47,7 +52,7 @@ public static function getInstance(): self
/** {@inheritdoc} */
public function fields(): array
{
return [self::TRACEPARENT, self::TRACESTATE];
return self::FIELDS;
}

/** {@inheritdoc} */
Expand Down
27 changes: 27 additions & 0 deletions src/Extension/Propagator/B3/B3DebugFlagContextKey.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Extension\Propagator\B3;

use OpenTelemetry\Context\Context;
use OpenTelemetry\Context\ContextKey;

/**
* @psalm-internal \OpenTelemetry
*/
final class B3DebugFlagContextKey
{
private const KEY_NAME = 'OpenTelemetry Context Key B3 Debug Flag';

private static ?ContextKey $instance = null;

public static function instance(): ContextKey
{
if (self::$instance === null) {
self::$instance = Context::createKey(self::KEY_NAME);
}

return self::$instance;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace OpenTelemetry\API\Trace\Propagation;
namespace OpenTelemetry\Extension\Propagator\B3;

use OpenTelemetry\API\Trace\AbstractSpan;
use OpenTelemetry\API\Trace\SpanContext;
Expand All @@ -14,9 +14,10 @@
use OpenTelemetry\Context\Propagation\TextMapPropagatorInterface;

/**
* B3Multi is a propagator that supports the specification for the header
* "b3" used for trace context propagation across service boundaries.
* (https://github.com/openzipkin/b3-propagation)
* B3Multi is a propagator that supports the specification for multiple
* "b3" http headers used for trace context propagation across service
* boundaries.
* (https://github.com/openzipkin/b3-propagation#multiple-headers)
*/
final class B3MultiPropagator implements TextMapPropagatorInterface
{
Expand All @@ -27,7 +28,7 @@ final class B3MultiPropagator implements TextMapPropagatorInterface
*
* @see https://github.com/openzipkin/b3-propagation#traceid-1
*/
public const TRACE_ID = 'X-B3-TraceId';
private const TRACE_ID = 'X-B3-TraceId';

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

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

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

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

private const IS_SAMPLED = '1';
private const VALID_SAMPLED = [self::IS_SAMPLED, 'true'];
private const IS_NOT_SAMPLED = '0';
private const VALID_NON_SAMPLED = [self::IS_NOT_SAMPLED, 'false'];

public const FIELDS = [
private const FIELDS = [
self::TRACE_ID,
self::SPAN_ID,
self::PARENT_SPAN_ID,
Expand Down Expand Up @@ -107,17 +110,24 @@ public function inject(&$carrier, PropagationSetterInterface $setter = null, Con
return;
}

// Inject multiple b3 headers
$setter->set($carrier, self::TRACE_ID, $spanContext->getTraceId());
$setter->set($carrier, self::SPAN_ID, $spanContext->getSpanId());
$setter->set($carrier, self::SAMPLED, $spanContext->isSampled() ? self::IS_SAMPLED : self::IS_NOT_SAMPLED);

$debugValue = $context->get(B3DebugFlagContextKey::instance());
if ($debugValue && $debugValue === self::IS_SAMPLED) {
$setter->set($carrier, self::DEBUG_FLAG, self::IS_SAMPLED);
} else {
$setter->set($carrier, self::SAMPLED, $spanContext->isSampled() ? self::IS_SAMPLED : self::IS_NOT_SAMPLED);
}
}

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

$spanContext = self::extractImpl($carrier, $getter);
$spanContext = self::extractImpl($carrier, $getter, $context);
if (!$spanContext->isValid()) {
return $context;
}
Expand All @@ -133,39 +143,40 @@ private static function getSampledValue($carrier, PropagationGetterInterface $ge
return null;
}

if ($value === '0' || $value === '1') {
return (int) $value;
if (in_array(strtolower($value), self::VALID_SAMPLED)) {
return (int) self::IS_SAMPLED;
}

if (strtolower($value) === 'true') {
return 1;
}
if (strtolower($value) === 'false') {
return 0;
if (in_array(strtolower($value), self::VALID_NON_SAMPLED)) {
return (int) self::IS_NOT_SAMPLED;
}

return null;
}

private static function extractImpl($carrier, PropagationGetterInterface $getter): SpanContextInterface
private static function extractImpl($carrier, PropagationGetterInterface $getter, Context &$context): SpanContextInterface
{
$traceId = $getter->get($carrier, self::TRACE_ID);
$spanId = $getter->get($carrier, self::SPAN_ID);
$sampled = self::getSampledValue($carrier, $getter);
$debug = $getter->get($carrier, self::DEBUG_FLAG);

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

// Validates the traceId, spanId and sampled
// Validates the traceId and spanId
// Returns an invalid spanContext if any of the checks fail
if (!SpanContext::isValidTraceId($traceId) || !SpanContext::isValidSpanId($spanId)) {
return SpanContext::getInvalid();
}

$isSampled = ($sampled === SpanContext::SAMPLED_FLAG);
if ($debug && $debug === self::IS_SAMPLED) {
$context = $context->with(B3DebugFlagContextKey::instance(), self::IS_SAMPLED);
$isSampled = SpanContext::SAMPLED_FLAG;
} else {
$isSampled = ($sampled === SpanContext::SAMPLED_FLAG);
}

// Only traceparent header is extracted. No tracestate.
return SpanContext::createFromRemoteParent(
$traceId,
$spanId,
Expand Down
65 changes: 65 additions & 0 deletions src/Extension/Propagator/B3/B3Propagator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Extension\Propagator\B3;

use OpenTelemetry\Context\Context;
use OpenTelemetry\Context\Propagation\ArrayAccessGetterSetter;
use OpenTelemetry\Context\Propagation\PropagationGetterInterface;
use OpenTelemetry\Context\Propagation\PropagationSetterInterface;
use OpenTelemetry\Context\Propagation\TextMapPropagatorInterface;

/**
* B3 is a propagator that supports the specification for the header
* "b3" used for trace context propagation across service boundaries.
* (https://github.com/openzipkin/b3-propagation)
*/
final class B3Propagator implements TextMapPropagatorInterface
{
private TextMapPropagatorInterface $propagator;

private function __construct(TextMapPropagatorInterface $propagator)
{
$this->propagator = $propagator;
}

public static function getB3SingleHeaderInstance(): self
{
static $instance;

return $instance ??= new self(B3SinglePropagator::getInstance());
}
public static function getB3MultiHeaderInstance(): self
{
static $instance;

return $instance ??= new self(B3MultiPropagator::getInstance());
}

/** {@inheritdoc} */
public function fields(): array
{
return $this->propagator->fields();
}

/** {@inheritdoc} */
public function inject(&$carrier, PropagationSetterInterface $setter = null, Context $context = null): void
{
$this->propagator->inject($carrier, $setter, $context);
}

/** {@inheritdoc} */
public function extract($carrier, PropagationGetterInterface $getter = null, Context $context = null): Context
{
$getter ??= ArrayAccessGetterSetter::getInstance();
$context ??= Context::getCurrent();

$b3SingleHeaderContext = B3SinglePropagator::getInstance()->extract($carrier, $getter, $context);
if ($b3SingleHeaderContext !== $context) {
return $b3SingleHeaderContext;
}

return B3MultiPropagator::getInstance()->extract($carrier, $getter, $context);
}
}

0 comments on commit 0f30849

Please sign in to comment.