Skip to content

Implement GCP X-Cloud-Trace-Context Propagator #1132

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"src/Contrib/Grpc/_register.php",
"src/Contrib/Zipkin/_register.php",
"src/Extension/Propagator/B3/_register.php",
"src/Extension/Propagator/XCloudTrace/_register.php",
"src/SDK/Logs/Exporter/_register.php",
"src/SDK/Metrics/MetricExporter/_register.php",
"src/SDK/Propagation/_register.php",
Expand Down
127 changes: 127 additions & 0 deletions src/Extension/Propagator/XCloudTrace/XCloudTraceFormatter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php
/**
* Copyright 2017 OpenCensus Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace OpenTelemetry\Extension\Propagator\XCloudTrace;

use OpenTelemetry\API\Trace\SpanContext;
use OpenTelemetry\API\Trace\SpanContextInterface;

/**
* This format using a human readable string encoding to propagate SpanContext.
* The current format of the header is `<trace-id>[/<span-id>][;o=<options>]`.
* The options are a bitmask of options. Currently the only option is the
* least significant bit which signals whether the request was traced or not
* (1 = traced, 0 = not traced).
*/
final class XCloudTraceFormatter
{
const CONTEXT_HEADER_FORMAT = '/([0-9a-fA-F]{32})(?:\/(\d+))?(?:;o=(\d+))?/';

/**
* Generate a SpanContext object from the Trace Context header
*
* @param string $header
* @return SpanContextInterface
*/
public static function deserialize(string $header) : SpanContextInterface
{
if (preg_match(self::CONTEXT_HEADER_FORMAT, $header, $matches)) {
return SpanContext::createFromRemoteParent(
strtolower($matches[1]),
array_key_exists(2, $matches) && !empty($matches[2])
? self::decToHex($matches[2])
: null,
array_key_exists(3, $matches)
? (int)($matches[3] == '1')
: null
);
}
return SpanContext::getInvalid();
}

/**
* Convert a SpanContextInterface to header string
*
* @param SpanContextInterface $context
* @return string
*/
public static function serialize(SpanContextInterface $context) : string
{
$ret = $context->getTraceId();
if ($context->getSpanId()) {
$ret .= '/' . self::hexToDec($context->getSpanId());
}
$ret .= ';o=' . ($context->isSampled() ? '1' : '0');
return $ret;
}

private static function decToHex(string $num) : string
{
$int = (int) $num;
if (self::isBigNum($int)) {
$ret = self::baseConvert($num, 10, 16);
} else {
$ret = dechex($int);
}
return str_pad($ret, 16, '0', STR_PAD_LEFT);
}

private static function hexToDec(string $num) : string
{
$dec = hexdec($num);
if (self::isBigNum($dec)) {
return self::baseConvert($num, 16, 10);
}
return strval($dec);
}

private static function isBigNum(int|float $number) : bool
{
return $number >= PHP_INT_MAX;
}

private static function baseConvert(string $num, int $fromBase, int $toBase) : string
{
$chars = "0123456789abcdefghijklmnopqrstuvwxyz";
$newstring = substr($chars, 0, $toBase);

$length = strlen($num);
$result = '';

for ($i = 0; $i < $length; $i++) {
$number[$i] = strpos($chars, $num[$i]);
}

do {
$divide = 0;
$newlen = 0;
for ($i = 0; $i < $length; $i++) {
$divide = $divide * $fromBase + $number[$i];
if ($divide >= $toBase) {
$number[$newlen++] = (int)($divide / $toBase);
$divide = $divide % $toBase;
} elseif ($newlen > 0) {
$number[$newlen++] = 0;
}
}
$length = $newlen;
$result = $newstring[$divide] . $result;
} while ($newlen != 0);

return $result;
}
}
96 changes: 96 additions & 0 deletions src/Extension/Propagator/XCloudTrace/XCloudTracePropagator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Extension\Propagator\XCloudTrace;

use OpenTelemetry\API\Trace\Span;
use OpenTelemetry\Context\Context;
use OpenTelemetry\Context\ContextInterface;
use OpenTelemetry\Context\Propagation\ArrayAccessGetterSetter;
use OpenTelemetry\Context\Propagation\PropagationGetterInterface;
use OpenTelemetry\Context\Propagation\PropagationSetterInterface;
use OpenTelemetry\Context\Propagation\TextMapPropagatorInterface;

final class XCloudTracePropagator implements TextMapPropagatorInterface
{

private static ?TextMapPropagatorInterface $oneWayInstance = null;
private static ?TextMapPropagatorInterface $instance = null;


public static function getOneWayInstance(): TextMapPropagatorInterface
{
if (self::$oneWayInstance === null) {
self::$oneWayInstance = new XCloudTracePropagator(true);
}

return self::$oneWayInstance;
}

public static function getInstance(): TextMapPropagatorInterface
{
if (self::$instance === null) {
self::$instance = new XCloudTracePropagator(false);
}

return self::$instance;
}

private const XCLOUD = 'x-cloud-trace-context';

private const FIELDS = [
self::XCLOUD,
];

private bool $oneWay;

private function __construct(bool $oneWay) {
$this->oneWay = $oneWay;
}

/** {@inheritdoc} */
public function fields(): array
{
return self::FIELDS;
}

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

$setter ??= ArrayAccessGetterSetter::getInstance();
$context ??= Context::getCurrent();
$spanContext = Span::fromContext($context)->getContext();

if (!$spanContext->isValid()) {
return;
}

$headerValue = XCloudTraceFormatter::serialize($spanContext);
$setter->set($carrier, self::XCLOUD, $headerValue);
}

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

$headerValue = $getter->get($carrier, self::XCLOUD);
if ($headerValue === null) {
return $context;
}

$spanContext = XCloudTraceFormatter::deserialize($headerValue);
if (!$spanContext->isValid()) {
return $context;
}

return $context->withContextValue(Span::wrap($spanContext));
}

}
12 changes: 12 additions & 0 deletions src/Extension/Propagator/XCloudTrace/_register.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

use OpenTelemetry\Extension\Propagator\XCloudTrace\XCloudTracePropagator;
use OpenTelemetry\SDK\Common\Configuration\KnownValues;
use OpenTelemetry\SDK\Registry;

Registry::registerTextMapPropagator(
KnownValues::VALUE_XCLOUD_TRACE,
XCloudTracePropagator::getInstance()
);
37 changes: 37 additions & 0 deletions src/Extension/Propagator/XCloudTrace/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "open-telemetry/extension-propagator-xcloudtrace",
"description": "XCloudTraceContext propagator extension for OpenTelemetry PHP.",
"keywords": ["opentelemetry", "otel", "tracing", "apm", "extension", "propagator", "xcloudtrace"],
"type": "library",
"support": {
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
"source": "https://github.com/open-telemetry/opentelemetry-php",
"docs": "https://opentelemetry.io/docs/php",
"chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V"
},
"license": "Apache-2.0",
"authors": [
{
"name": "opentelemetry-php contributors",
"homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors"
}
],
"require": {
"php": "^7.4 || ^8.0",
"open-telemetry/api": "^1.0",
"open-telemetry/context": "^1.0"
},
"autoload": {
"psr-4": {
"OpenTelemetry\\Extension\\Propagator\\XCloudTrace\\": "."
},
"files": [
"_register.php"
]
},
"extra": {
"branch-alias": {
"dev-main": "1.0.x-dev"
}
}
}
1 change: 1 addition & 0 deletions src/SDK/Common/Configuration/KnownValues.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface KnownValues
public const VALUE_BAGGAGE = 'baggage';
public const VALUE_B3 = 'b3';
public const VALUE_B3_MULTI = 'b3multi';
public const VALUE_XCLOUD_TRACE = 'xcloudtrace';
public const VALUE_XRAY = 'xray';
public const VALUE_OTTRACE = 'ottrace';
public const VALUE_ALWAYS_ON = 'always_on';
Expand Down
Loading