Skip to content

Commit 2c3c170

Browse files
committed
Implement GCP X-Cloud-Trace-Context Propagator
1 parent 42a8b95 commit 2c3c170

File tree

9 files changed

+451
-0
lines changed

9 files changed

+451
-0
lines changed

composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"src/Contrib/Grpc/_register.php",
5757
"src/Contrib/Zipkin/_register.php",
5858
"src/Extension/Propagator/B3/_register.php",
59+
"src/Extension/Propagator/XCloudTrace/_register.php",
5960
"src/SDK/Logs/Exporter/_register.php",
6061
"src/SDK/Metrics/MetricExporter/_register.php",
6162
"src/SDK/Propagation/_register.php",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?php
2+
/**
3+
* Copyright 2017 OpenCensus Authors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
namespace OpenTelemetry\Extension\Propagator\XCloudTrace;
19+
20+
use OpenTelemetry\API\Trace\SpanContext;
21+
use OpenTelemetry\API\Trace\SpanContextInterface;
22+
23+
/**
24+
* This format using a human readable string encoding to propagate SpanContext.
25+
* The current format of the header is `<trace-id>[/<span-id>][;o=<options>]`.
26+
* The options are a bitmask of options. Currently the only option is the
27+
* least significant bit which signals whether the request was traced or not
28+
* (1 = traced, 0 = not traced).
29+
*/
30+
final class XCloudTraceFormatter
31+
{
32+
const CONTEXT_HEADER_FORMAT = '/([0-9a-fA-F]{32})(?:\/(\d+))?(?:;o=(\d+))?/';
33+
34+
/**
35+
* Generate a SpanContext object from the Trace Context header
36+
*
37+
* @param string $header
38+
* @return SpanContextInterface
39+
*/
40+
public static function deserialize(string $header) : SpanContextInterface
41+
{
42+
if (preg_match(self::CONTEXT_HEADER_FORMAT, $header, $matches)) {
43+
return SpanContext::createFromRemoteParent(
44+
strtolower($matches[1]),
45+
array_key_exists(2, $matches) && !empty($matches[2])
46+
? self::decToHex($matches[2])
47+
: null,
48+
array_key_exists(3, $matches)
49+
? (int)($matches[3] == '1')
50+
: null
51+
);
52+
}
53+
return SpanContext::getInvalid();
54+
}
55+
56+
/**
57+
* Convert a SpanContextInterface to header string
58+
*
59+
* @param SpanContextInterface $context
60+
* @return string
61+
*/
62+
public static function serialize(SpanContextInterface $context) : string
63+
{
64+
$ret = $context->getTraceId();
65+
if ($context->getSpanId()) {
66+
$ret .= '/' . self::hexToDec($context->getSpanId());
67+
}
68+
$ret .= ';o=' . ($context->isSampled() ? '1' : '0');
69+
return $ret;
70+
}
71+
72+
private static function decToHex(string $num) : string
73+
{
74+
$int = (int) $num;
75+
if (self::isBigNum($int)) {
76+
$ret = self::baseConvert($num, 10, 16);
77+
} else {
78+
$ret = dechex($int);
79+
}
80+
return str_pad($ret, 16, '0', STR_PAD_LEFT);
81+
}
82+
83+
private static function hexToDec(string $num) : string
84+
{
85+
$dec = hexdec($num);
86+
if (self::isBigNum($dec)) {
87+
return self::baseConvert($num, 16, 10);
88+
}
89+
return strval($dec);
90+
}
91+
92+
private static function isBigNum(int|float $number) : bool
93+
{
94+
return $number >= PHP_INT_MAX;
95+
}
96+
97+
private static function baseConvert(string $num, int $fromBase, int $toBase) : string
98+
{
99+
$chars = "0123456789abcdefghijklmnopqrstuvwxyz";
100+
$newstring = substr($chars, 0, $toBase);
101+
102+
$length = strlen($num);
103+
$result = '';
104+
105+
for ($i = 0; $i < $length; $i++) {
106+
$number[$i] = strpos($chars, $num[$i]);
107+
}
108+
109+
do {
110+
$divide = 0;
111+
$newlen = 0;
112+
for ($i = 0; $i < $length; $i++) {
113+
$divide = $divide * $fromBase + $number[$i];
114+
if ($divide >= $toBase) {
115+
$number[$newlen++] = (int)($divide / $toBase);
116+
$divide = $divide % $toBase;
117+
} elseif ($newlen > 0) {
118+
$number[$newlen++] = 0;
119+
}
120+
}
121+
$length = $newlen;
122+
$result = $newstring[$divide] . $result;
123+
} while ($newlen != 0);
124+
125+
return $result;
126+
}
127+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Extension\Propagator\XCloudTrace;
6+
7+
use OpenTelemetry\API\Trace\Span;
8+
use OpenTelemetry\Context\Context;
9+
use OpenTelemetry\Context\ContextInterface;
10+
use OpenTelemetry\Context\Propagation\ArrayAccessGetterSetter;
11+
use OpenTelemetry\Context\Propagation\PropagationGetterInterface;
12+
use OpenTelemetry\Context\Propagation\PropagationSetterInterface;
13+
use OpenTelemetry\Context\Propagation\TextMapPropagatorInterface;
14+
15+
final class XCloudTracePropagator implements TextMapPropagatorInterface
16+
{
17+
18+
private static ?TextMapPropagatorInterface $oneWayInstance = null;
19+
private static ?TextMapPropagatorInterface $instance = null;
20+
21+
22+
public static function getOneWayInstance(): TextMapPropagatorInterface
23+
{
24+
if (self::$oneWayInstance === null) {
25+
self::$oneWayInstance = new XCloudTracePropagator(true);
26+
}
27+
28+
return self::$oneWayInstance;
29+
}
30+
31+
public static function getInstance(): TextMapPropagatorInterface
32+
{
33+
if (self::$instance === null) {
34+
self::$instance = new XCloudTracePropagator(false);
35+
}
36+
37+
return self::$instance;
38+
}
39+
40+
private const XCLOUD = 'x-cloud-trace-context';
41+
42+
private const FIELDS = [
43+
self::XCLOUD,
44+
];
45+
46+
private bool $oneWay;
47+
48+
private function __construct(bool $oneWay) {
49+
$this->oneWay = $oneWay;
50+
}
51+
52+
/** {@inheritdoc} */
53+
public function fields(): array
54+
{
55+
return self::FIELDS;
56+
}
57+
58+
/** {@inheritdoc} */
59+
public function inject(&$carrier, PropagationSetterInterface $setter = null, ContextInterface $context = null): void
60+
{
61+
if($this->oneWay) {
62+
return;
63+
}
64+
65+
$setter ??= ArrayAccessGetterSetter::getInstance();
66+
$context ??= Context::getCurrent();
67+
$spanContext = Span::fromContext($context)->getContext();
68+
69+
if (!$spanContext->isValid()) {
70+
return;
71+
}
72+
73+
$headerValue = XCloudTraceFormatter::serialize($spanContext);
74+
$setter->set($carrier, self::XCLOUD, $headerValue);
75+
}
76+
77+
/** {@inheritdoc} */
78+
public function extract($carrier, PropagationGetterInterface $getter = null, ContextInterface $context = null): ContextInterface
79+
{
80+
$getter ??= ArrayAccessGetterSetter::getInstance();
81+
$context ??= Context::getCurrent();
82+
83+
$headerValue = $getter->get($carrier, self::XCLOUD);
84+
if ($headerValue === null) {
85+
return $context;
86+
}
87+
88+
$spanContext = XCloudTraceFormatter::deserialize($headerValue);
89+
if (!$spanContext->isValid()) {
90+
return $context;
91+
}
92+
93+
return $context->withContextValue(Span::wrap($spanContext));
94+
}
95+
96+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use OpenTelemetry\Extension\Propagator\XCloudTrace\XCloudTracePropagator;
6+
use OpenTelemetry\SDK\Common\Configuration\KnownValues;
7+
use OpenTelemetry\SDK\Registry;
8+
9+
Registry::registerTextMapPropagator(
10+
KnownValues::VALUE_XCLOUD_TRACE,
11+
XCloudTracePropagator::getInstance()
12+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"name": "open-telemetry/extension-propagator-xcloudtrace",
3+
"description": "XCloudTraceContext propagator extension for OpenTelemetry PHP.",
4+
"keywords": ["opentelemetry", "otel", "tracing", "apm", "extension", "propagator", "xcloudtrace"],
5+
"type": "library",
6+
"support": {
7+
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
8+
"source": "https://github.com/open-telemetry/opentelemetry-php",
9+
"docs": "https://opentelemetry.io/docs/php",
10+
"chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V"
11+
},
12+
"license": "Apache-2.0",
13+
"authors": [
14+
{
15+
"name": "opentelemetry-php contributors",
16+
"homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors"
17+
}
18+
],
19+
"require": {
20+
"php": "^7.4 || ^8.0",
21+
"open-telemetry/api": "^1.0",
22+
"open-telemetry/context": "^1.0"
23+
},
24+
"autoload": {
25+
"psr-4": {
26+
"OpenTelemetry\\Extension\\Propagator\\XCloudTrace\\": "."
27+
},
28+
"files": [
29+
"_register.php"
30+
]
31+
},
32+
"extra": {
33+
"branch-alias": {
34+
"dev-main": "1.0.x-dev"
35+
}
36+
}
37+
}

src/SDK/Common/Configuration/KnownValues.php

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ interface KnownValues
2525
public const VALUE_BAGGAGE = 'baggage';
2626
public const VALUE_B3 = 'b3';
2727
public const VALUE_B3_MULTI = 'b3multi';
28+
public const VALUE_XCLOUD_TRACE = 'xcloudtrace';
2829
public const VALUE_XRAY = 'xray';
2930
public const VALUE_OTTRACE = 'ottrace';
3031
public const VALUE_ALWAYS_ON = 'always_on';

0 commit comments

Comments
 (0)