Skip to content

Commit d6c4c89

Browse files
Implement GCP X-Cloud-Trace-Context Propagator (#1132)
1 parent 30b73d9 commit d6c4c89

14 files changed

+721
-0
lines changed

.gitsplit.yml

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ splits:
2424
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/exporter-zipkin.git"
2525
- prefix: "src/Extension/Propagator/B3"
2626
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/extension-propagator-b3.git"
27+
- prefix: "src/Extension/Propagator/XCloudTrace"
28+
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/extension-propagator-cloudtrace.git"
2729

2830
# List of references to split (defined as regexp)
2931
origins:

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,27 @@
1+
[![Releases](https://img.shields.io/badge/releases-purple)](https://github.com/opentelemetry-php/extension-propagator-cloudtrace/releases)
2+
[![Source](https://img.shields.io/badge/source-extension--propagator--xcloudtrace-green)](https://github.com/open-telemetry/opentelemetry-php/tree/main/src/Extension/Propagator/XCloudTrace)
3+
[![Mirror](https://img.shields.io/badge/mirror-opentelemetry--php:extension--propagator--xcloudtrace-blue)](https://github.com/opentelemetry-php/extension-propagator-cloudtrace)
4+
[![Latest Version](http://poser.pugx.org/open-telemetry/extension-propagator-cloudtrace/v/unstable)](https://packagist.org/packages/open-telemetry/extension-propagator-cloudtrace/)
5+
[![Stable](http://poser.pugx.org/open-telemetry/extension-propagator-cloudtrace/v/stable)](https://packagist.org/packages/open-telemetry/extension-propagator-cloudtrace/)
6+
7+
# OpenTelemetry Extension
8+
### XCloudTrace Propagator
9+
10+
XCloudTrace is a propagator that supports the specification for the header "x-cloud-trace-context" used for trace context propagation across
11+
service boundaries. (https://cloud.google.com/trace/docs/setup#force-trace). OpenTelemetry PHP XCloudTrace Propagator Extension provides
12+
option to use it bi-directionally or one-way. One-way does not inject the header for downstream consumption, it only processes the incoming headers
13+
and returns the correct span context. It only attaches to existing X-Cloud-Trace-Context traces and does not create downstream ones.
14+
For one-way XCloudTrace:
15+
```text
16+
XCloudTracePropagator::getOneWayInstance()
17+
```
18+
19+
For bi-directional XCloudTrace:
20+
```text
21+
XCloudTracePropagator::getInstance()
22+
```
23+
24+
## Contributing
25+
26+
This repository is a read-only git subtree split.
27+
To contribute, please see the main [OpenTelemetry PHP monorepo](https://github.com/open-telemetry/opentelemetry-php).
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Extension\Propagator\XCloudTrace;
6+
7+
/**
8+
* This class contains utilities that are used by the XCloudTracePropagator.
9+
* This class mostly contains numerical handling functions to work with
10+
* trace and span IDs.
11+
*/
12+
final class Utils
13+
{
14+
15+
/**
16+
* Pads the string with zero string characters on left hand side, to max total string size.
17+
*
18+
* @param string $str The string to pad.
19+
* @param int $amount Total String size, default is 16.
20+
* @return string The padded string
21+
*/
22+
public static function leftZeroPad(string $str, int $amount = 16) : string
23+
{
24+
return str_pad($str, $amount, '0', STR_PAD_LEFT);
25+
}
26+
27+
/**
28+
* Converts a decimal number in string format to a hex number in string format.
29+
* The returned number will not start with 0x.
30+
*
31+
* @param string $num The number to convert.
32+
* @return string The converted number.
33+
*/
34+
public static function decToHex(string $num) : string
35+
{
36+
$int = (int) $num;
37+
if (self::isBigNum($int)) {
38+
return self::baseConvert($num, 10, 16);
39+
}
40+
41+
return dechex($int);
42+
}
43+
44+
/**
45+
* Converts a hex number in string format to a decimal number in string format.
46+
* The given number does not have to start with 0x.
47+
*
48+
* @param string $num The number to convert.
49+
* @return string The converted number.
50+
*/
51+
public static function hexToDec(string $num) : string
52+
{
53+
$dec = hexdec($num);
54+
if (self::isBigNum($dec)) {
55+
return self::baseConvert($num, 16, 10);
56+
}
57+
58+
return (string) $dec;
59+
}
60+
61+
/**
62+
* Tests whether the given number is larger than the maximum integer of the installed PHP's build.
63+
* On 32-bit system it's 2147483647 and on 64-bit it's 9223372036854775807.
64+
* We are comparing with >= and no >, because this function is used in context of what method to use
65+
* to convert to some base (in our case hex to octal and vice versa).
66+
* So it's ok if we use >=, because it means that only for MAX_INT we will use the slower baseConvert
67+
* method.
68+
*
69+
* @param int|float $number The number to test.
70+
* @return bool Whether it was bigger or not than the max.
71+
*/
72+
public static function isBigNum($number) : bool
73+
{
74+
return $number >= PHP_INT_MAX;
75+
}
76+
77+
/**
78+
* Custom function to convert a number in string format from one base to another.
79+
* Built-in functions, specifically for hex, do not work well in PHP under
80+
* all versions (32/64-bit) or if the number only fits into an unsigned long.
81+
* PHP does not have unsigned longs, so this function is necessary.
82+
*
83+
* @param string $num The number to convert (in some base).
84+
* @param int $fromBase The base to convert from.
85+
* @param int $toBase The base to convert to.
86+
* @return string Converted number in the new base.
87+
*/
88+
public static function baseConvert(string $num, int $fromBase, int $toBase) : string
89+
{
90+
$num = strtolower($num);
91+
$chars = '0123456789abcdefghijklmnopqrstuvwxyz';
92+
$newstring = substr($chars, 0, $toBase);
93+
94+
$length = strlen($num);
95+
$result = '';
96+
97+
$number = [];
98+
for ($i = 0; $i < $length; $i++) {
99+
$number[$i] = strpos($chars, $num[$i]);
100+
}
101+
102+
do {
103+
$divide = 0;
104+
$newlen = 0;
105+
for ($i = 0; $i < $length; $i++) {
106+
if (!isset($number[$i]) || $number[$i] === false) {
107+
return '';
108+
}
109+
$divide = $divide * $fromBase + $number[$i];
110+
if ($divide >= $toBase) {
111+
$number[$newlen++] = (int) ($divide / $toBase);
112+
$divide %= $toBase;
113+
} elseif ($newlen > 0) {
114+
$number[$newlen++] = 0;
115+
}
116+
}
117+
$length = $newlen;
118+
$result = $newstring[$divide] . $result;
119+
} while ($newlen != 0);
120+
121+
return $result;
122+
}
123+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Extension\Propagator\XCloudTrace;
6+
7+
use OpenTelemetry\API\Trace\SpanContext;
8+
use OpenTelemetry\API\Trace\SpanContextInterface;
9+
10+
/**
11+
* This format using a human readable string encoding to propagate SpanContext.
12+
* The current format of the header is `<trace-id>[/<span-id>][;o=<options>]`.
13+
* The options are a bitmask of options. Currently the only option is the
14+
* least significant bit which signals whether the request was traced or not
15+
* (1 = traced, 0 = not traced).
16+
*/
17+
final class XCloudTraceFormatter
18+
{
19+
const CONTEXT_HEADER_FORMAT = '/([0-9a-fA-F]{32})(?:\/(\d+))?(?:;o=(\d+))?/';
20+
21+
/**
22+
* Generate a SpanContext object from the Trace Context header
23+
*
24+
* @param string $header
25+
* @return SpanContextInterface
26+
*/
27+
public static function deserialize(string $header) : SpanContextInterface
28+
{
29+
$matched = preg_match(self::CONTEXT_HEADER_FORMAT, $header, $matches);
30+
31+
if (!$matched) {
32+
return SpanContext::getInvalid();
33+
}
34+
if (!array_key_exists(2, $matches) || empty($matches[2])) {
35+
return SpanContext::getInvalid();
36+
}
37+
if (!array_key_exists(3, $matches)) {
38+
return SpanContext::getInvalid();
39+
}
40+
41+
return SpanContext::createFromRemoteParent(
42+
strtolower($matches[1]),
43+
Utils::leftZeroPad(Utils::decToHex($matches[2])),
44+
(int) ($matches[3] == '1')
45+
);
46+
}
47+
48+
/**
49+
* Convert a SpanContextInterface to header string
50+
*
51+
* @param SpanContextInterface $context
52+
* @return string
53+
*/
54+
public static function serialize(SpanContextInterface $context) : string
55+
{
56+
$ret = $context->getTraceId();
57+
if ($context->getSpanId()) {
58+
$ret .= '/' . Utils::hexToDec($context->getSpanId());
59+
}
60+
61+
return $ret . (';o=' . ($context->isSampled() ? '1' : '0'));
62+
}
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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+
/**
16+
* XCloudTracePropagator is a propagator that supports the specification for the X-Cloud-Trace-Context
17+
* header used for trace context propagation across service boundaries.
18+
* (https://cloud.google.com/trace/docs/setup#force-trace)
19+
*/
20+
final class XCloudTracePropagator implements TextMapPropagatorInterface
21+
{
22+
private static ?TextMapPropagatorInterface $oneWayInstance = null;
23+
private static ?TextMapPropagatorInterface $instance = null;
24+
25+
public static function getOneWayInstance(): TextMapPropagatorInterface
26+
{
27+
if (self::$oneWayInstance === null) {
28+
self::$oneWayInstance = new XCloudTracePropagator(true);
29+
}
30+
31+
return self::$oneWayInstance;
32+
}
33+
34+
public static function getInstance(): TextMapPropagatorInterface
35+
{
36+
if (self::$instance === null) {
37+
self::$instance = new XCloudTracePropagator(false);
38+
}
39+
40+
return self::$instance;
41+
}
42+
43+
private const XCLOUD = 'x-cloud-trace-context';
44+
45+
private const FIELDS = [
46+
self::XCLOUD,
47+
];
48+
49+
private bool $oneWay;
50+
51+
private function __construct(bool $oneWay)
52+
{
53+
$this->oneWay = $oneWay;
54+
}
55+
56+
/** {@inheritdoc} */
57+
public function fields(): array
58+
{
59+
return self::FIELDS;
60+
}
61+
62+
/** {@inheritdoc} */
63+
public function inject(&$carrier, PropagationSetterInterface $setter = null, ContextInterface $context = null): void
64+
{
65+
if ($this->oneWay) {
66+
return;
67+
}
68+
69+
$setter ??= ArrayAccessGetterSetter::getInstance();
70+
$context ??= Context::getCurrent();
71+
$spanContext = Span::fromContext($context)->getContext();
72+
73+
if (!$spanContext->isValid()) {
74+
return;
75+
}
76+
77+
$headerValue = XCloudTraceFormatter::serialize($spanContext);
78+
$setter->set($carrier, self::XCLOUD, $headerValue);
79+
}
80+
81+
/** {@inheritdoc} */
82+
public function extract($carrier, PropagationGetterInterface $getter = null, ContextInterface $context = null): ContextInterface
83+
{
84+
$getter ??= ArrayAccessGetterSetter::getInstance();
85+
$context ??= Context::getCurrent();
86+
87+
$headerValue = $getter->get($carrier, self::XCLOUD);
88+
if ($headerValue === null) {
89+
return $context;
90+
}
91+
92+
$spanContext = XCloudTraceFormatter::deserialize($headerValue);
93+
if (!$spanContext->isValid()) {
94+
return $context;
95+
}
96+
97+
return $context->withContextValue(Span::wrap($spanContext));
98+
}
99+
}
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

+2
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';
@@ -105,6 +106,7 @@ interface KnownValues
105106
self::VALUE_BAGGAGE, // W3C Baggage
106107
self::VALUE_B3, // B3 Single
107108
self::VALUE_B3_MULTI, // B3 Multi
109+
self::VALUE_XCLOUD_TRACE, // GCP XCloudTraceContext
108110
self::VALUE_XRAY, // AWS X-Ray (third party)
109111
self::VALUE_OTTRACE, // OT Trace (third party)
110112
self::VALUE_NONE, // No automatically configured propagator.

0 commit comments

Comments
 (0)