Skip to content

Commit c88deba

Browse files
authored
Add Jaeger Propagator (#1188)
* jaeger propagator * adjust readme * add baggage and debug flag * add units tests * code review fixs and baggage separator * update readme and register * improve more tests * fix php ci * fix knowvalues jaeger baggage
0 parents  commit c88deba

File tree

6 files changed

+337
-0
lines changed

6 files changed

+337
-0
lines changed

JaegerBaggagePropagator.php

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Extension\Propagator\Jaeger;
6+
7+
use OpenTelemetry\API\Baggage\Baggage;
8+
use OpenTelemetry\API\Baggage\Entry; /** @phan-suppress-current-line PhanUnreferencedUseNormal */
9+
use OpenTelemetry\Context\Context;
10+
use OpenTelemetry\Context\ContextInterface;
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+
* JaegerBaggagePropagator is a baggage propagator that supports the specification for the header
18+
* "uberctx" used for baggage propagation.
19+
* (https://www.jaegertracing.io/docs/1.52/client-libraries/#baggage)
20+
*/
21+
class JaegerBaggagePropagator implements TextMapPropagatorInterface
22+
{
23+
private const UBER_BAGGAGE_HEADER_PREFIX = 'uberctx-';
24+
25+
private static ?TextMapPropagatorInterface $instance = null;
26+
27+
public static function getInstance(): TextMapPropagatorInterface
28+
{
29+
if (self::$instance === null) {
30+
self::$instance = new JaegerBaggagePropagator();
31+
}
32+
33+
return self::$instance;
34+
}
35+
36+
public function fields(): array
37+
{
38+
return [];
39+
}
40+
41+
/** {@inheritdoc} */
42+
public function inject(&$carrier, PropagationSetterInterface $setter = null, ContextInterface $context = null): void
43+
{
44+
$setter ??= ArrayAccessGetterSetter::getInstance();
45+
$context ??= Context::getCurrent();
46+
47+
$baggage = Baggage::fromContext($context);
48+
49+
if ($baggage->isEmpty()) {
50+
return;
51+
}
52+
53+
/** @var Entry $entry */
54+
foreach ($baggage->getAll() as $key => $entry) {
55+
$key = self::UBER_BAGGAGE_HEADER_PREFIX . $key;
56+
$value = rawurlencode((string) $entry->getValue());
57+
$setter->set($carrier, $key, $value);
58+
}
59+
}
60+
61+
/** {@inheritdoc} */
62+
public function extract($carrier, PropagationGetterInterface $getter = null, ContextInterface $context = null): ContextInterface
63+
{
64+
$getter ??= ArrayAccessGetterSetter::getInstance();
65+
$context ??= Context::getCurrent();
66+
67+
$baggageKeys = $getter->keys($carrier);
68+
69+
if ($baggageKeys === []) {
70+
return $context;
71+
}
72+
73+
$baggageBuilder = Baggage::getBuilder();
74+
75+
foreach ($baggageKeys as $key) {
76+
if (strpos($key, self::UBER_BAGGAGE_HEADER_PREFIX) === 0) {
77+
$baggageKey = substr($key, strlen(self::UBER_BAGGAGE_HEADER_PREFIX));
78+
$value = $getter->get($carrier, $key) ?? '';
79+
$baggageBuilder->set($baggageKey, rawurldecode($value));
80+
}
81+
}
82+
83+
return $context->withContextValue($baggageBuilder->build());
84+
}
85+
}

JaegerDebugFlagContextKey.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Extension\Propagator\Jaeger;
6+
7+
use OpenTelemetry\Context\Context;
8+
use OpenTelemetry\Context\ContextKeyInterface;
9+
10+
/**
11+
* @psalm-internal \OpenTelemetry
12+
*/
13+
final class JaegerDebugFlagContextKey
14+
{
15+
private const KEY_NAME = 'jaeger-debug-key';
16+
17+
private static ?ContextKeyInterface $instance = null;
18+
19+
public static function instance(): ContextKeyInterface
20+
{
21+
if (self::$instance === null) {
22+
self::$instance = Context::createKey(self::KEY_NAME);
23+
}
24+
25+
return self::$instance;
26+
}
27+
}

JaegerPropagator.php

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Extension\Propagator\Jaeger;
6+
7+
use OpenTelemetry\API\Trace\Span;
8+
use OpenTelemetry\API\Trace\SpanContext;
9+
use OpenTelemetry\API\Trace\SpanContextInterface;
10+
use OpenTelemetry\API\Trace\SpanContextValidator;
11+
use OpenTelemetry\API\Trace\TraceFlags;
12+
use OpenTelemetry\Context\Context;
13+
use OpenTelemetry\Context\ContextInterface;
14+
use OpenTelemetry\Context\Propagation\ArrayAccessGetterSetter;
15+
use OpenTelemetry\Context\Propagation\PropagationGetterInterface;
16+
use OpenTelemetry\Context\Propagation\PropagationSetterInterface;
17+
use OpenTelemetry\Context\Propagation\TextMapPropagatorInterface;
18+
19+
/**
20+
* JaegerPropagator is a propagator that supports the specification for the header
21+
* "uber-trace-id" used for trace context propagation across service boundaries.
22+
* (https://www.jaegertracing.io/docs/1.52/client-libraries/#propagation-format)
23+
*/
24+
class JaegerPropagator implements TextMapPropagatorInterface
25+
{
26+
private const UBER_TRACE_ID_HEADER = 'uber-trace-id';
27+
28+
private const IS_NOT_SAMPLED = 0;
29+
private const IS_SAMPLED = 1;
30+
private const IS_DEBUG = 2;
31+
private const DEFAULT_PARENT_SPAN_ID = 0;
32+
33+
private const FIELDS = [
34+
self::UBER_TRACE_ID_HEADER,
35+
];
36+
37+
private static ?TextMapPropagatorInterface $instance = null;
38+
39+
public static function getInstance(): TextMapPropagatorInterface
40+
{
41+
if (self::$instance === null) {
42+
self::$instance = new JaegerPropagator();
43+
}
44+
45+
return self::$instance;
46+
}
47+
48+
public function fields(): array
49+
{
50+
return self::FIELDS;
51+
}
52+
53+
/** {@inheritdoc} */
54+
public function inject(&$carrier, PropagationSetterInterface $setter = null, ContextInterface $context = null): void
55+
{
56+
$setter ??= ArrayAccessGetterSetter::getInstance();
57+
$context ??= Context::getCurrent();
58+
$spanContext = Span::fromContext($context)->getContext();
59+
60+
if (!$spanContext->isValid()) {
61+
return;
62+
}
63+
64+
$flag = $this->getFlag($spanContext, $context);
65+
66+
$uberTraceId = sprintf(
67+
'%s:%s:%d:%d',
68+
$spanContext->getTraceId(),
69+
$spanContext->getSpanId(),
70+
self::DEFAULT_PARENT_SPAN_ID,
71+
$flag
72+
);
73+
74+
$setter->set($carrier, self::UBER_TRACE_ID_HEADER, $uberTraceId);
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+
$spanContext = self::extractImpl($carrier, $getter, $context);
84+
if (!$spanContext->isValid()) {
85+
return $context;
86+
}
87+
88+
return $context->withContextValue(Span::wrap($spanContext));
89+
}
90+
91+
private function getFlag(SpanContextInterface $spanContext, ContextInterface $context): int
92+
{
93+
if ($spanContext->isSampled()) {
94+
if ($context->get(JaegerDebugFlagContextKey::instance())) {
95+
return self::IS_DEBUG | self::IS_SAMPLED;
96+
}
97+
98+
return self::IS_SAMPLED;
99+
}
100+
101+
return self::IS_NOT_SAMPLED;
102+
}
103+
104+
private static function extractImpl($carrier, PropagationGetterInterface $getter, ContextInterface &$context): SpanContextInterface
105+
{
106+
$headerValue = $getter->get($carrier, self::UBER_TRACE_ID_HEADER);
107+
108+
if ($headerValue === null) {
109+
return SpanContext::getInvalid();
110+
}
111+
112+
$pieces = explode(':', $headerValue);
113+
114+
if (count($pieces) != 4) {
115+
return SpanContext::getInvalid();
116+
}
117+
118+
[$traceId, $spanId, $parentSpanId, $traceFlags] = $pieces;
119+
120+
$traceId = str_pad($traceId, SpanContextValidator::TRACE_LENGTH, '0', STR_PAD_LEFT);
121+
$spanId = str_pad($spanId, SpanContextValidator::SPAN_LENGTH, '0', STR_PAD_LEFT);
122+
123+
if (!SpanContextValidator::isValidTraceId($traceId) || !SpanContextValidator::isValidSpanId($spanId)) {
124+
return SpanContext::getInvalid();
125+
}
126+
127+
if ((int) $traceFlags & self::IS_DEBUG) {
128+
$context = $context->with(JaegerDebugFlagContextKey::instance(), true);
129+
}
130+
131+
$isSampled = ((int) $traceFlags) & 1;
132+
133+
return SpanContext::createFromRemoteParent(
134+
$traceId,
135+
$spanId,
136+
$isSampled ? TraceFlags::SAMPLED : TraceFlags::DEFAULT
137+
);
138+
}
139+
}

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
[![Releases](https://img.shields.io/badge/releases-purple)](https://github.com/opentelemetry-php/extension-propagator-jaeger/releases)
2+
[![Source](https://img.shields.io/badge/source-extension--propagator--jaeger-green)](https://github.com/open-telemetry/opentelemetry-php/tree/main/src/Extension/Propagator/Jaeger)
3+
[![Mirror](https://img.shields.io/badge/mirror-opentelemetry--php:extension--propagator--jaeger-blue)](https://github.com/opentelemetry-php/extension-propagator-jaeger)
4+
[![Latest Version](http://poser.pugx.org/open-telemetry/extension-propagator-jaeger/v/unstable)](https://packagist.org/packages/open-telemetry/extension-propagator-jaeger/)
5+
[![Stable](http://poser.pugx.org/open-telemetry/extension-propagator-jaeger/v/stable)](https://packagist.org/packages/open-telemetry/extension-propagator-jaeger/)
6+
7+
# OpenTelemetry Extension
8+
### Jaeger Propagator
9+
10+
Jaeger is a propagator that supports the specification for the header "uber-trace-id" used for trace context propagation across
11+
service boundaries.(https://www.jaegertracing.io/docs/1.52/client-libraries/#propagation-format).
12+
OpenTelemetry PHP Jaeger Propagator Extension provides option to use Jaeger Baggage (https://www.jaegertracing.io/docs/1.52/client-libraries/#baggage) propagator.
13+
14+
### Usage
15+
For Jaeger trace propagator:
16+
```text
17+
JaegerPropagator::getInstance()
18+
```
19+
20+
For Jaeger baggage propagator:
21+
```text
22+
JaegerBaggagePropagator::getInstance()
23+
```
24+
25+
Both of the above have `extract` and `inject` methods available to extract and inject respectively into the
26+
header.
27+
28+
## Contributing
29+
30+
This repository is a read-only git subtree split.
31+
To contribute, please see the main [OpenTelemetry PHP monorepo](https://github.com/open-telemetry/opentelemetry-php).

_register.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use OpenTelemetry\Extension\Propagator\Jaeger\JaegerBaggagePropagator;
6+
use OpenTelemetry\Extension\Propagator\Jaeger\JaegerPropagator;
7+
use OpenTelemetry\SDK\Common\Configuration\KnownValues;
8+
use OpenTelemetry\SDK\Registry;
9+
10+
Registry::registerTextMapPropagator(
11+
KnownValues::VALUE_JAEGER,
12+
JaegerPropagator::getInstance()
13+
);
14+
15+
Registry::registerTextMapPropagator(
16+
KnownValues::VALUE_JAEGER_BAGGAGE,
17+
JaegerBaggagePropagator::getInstance()
18+
);

composer.json

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"name": "open-telemetry/extension-propagator-jaeger",
3+
"description": "Jaeger propagator extension for OpenTelemetry PHP.",
4+
"keywords": ["opentelemetry", "otel", "tracing", "apm", "extension", "propagator", "jaeger"],
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\\Jaeger\\": "."
27+
},
28+
"files": [
29+
"_register.php"
30+
]
31+
},
32+
"extra": {
33+
"branch-alias": {
34+
"dev-main": "1.0.x-dev"
35+
}
36+
}
37+
}

0 commit comments

Comments
 (0)