Skip to content

Commit

Permalink
Add Jaeger Propagator (#1188)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
weslenteche authored Dec 15, 2023
1 parent d376566 commit d8827f9
Show file tree
Hide file tree
Showing 16 changed files with 840 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .gitsplit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ splits:
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/extension-propagator-b3.git"
- prefix: "src/Extension/Propagator/CloudTrace"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/extension-propagator-cloudtrace.git"
- prefix: "src/Extension/Propagator/Jaeger"
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/extension-propagator-jaeger.git"

# List of references to split (defined as regexp)
origins:
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"src/Contrib/Zipkin/_register.php",
"src/Extension/Propagator/B3/_register.php",
"src/Extension/Propagator/CloudTrace/_register.php",
"src/Extension/Propagator/Jaeger/_register.php",
"src/SDK/Logs/Exporter/_register.php",
"src/SDK/Metrics/MetricExporter/_register.php",
"src/SDK/Propagation/_register.php",
Expand Down
85 changes: 85 additions & 0 deletions src/Extension/Propagator/Jaeger/JaegerBaggagePropagator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Extension\Propagator\Jaeger;

use OpenTelemetry\API\Baggage\Baggage;
use OpenTelemetry\API\Baggage\Entry; /** @phan-suppress-current-line PhanUnreferencedUseNormal */
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;

/**
* JaegerBaggagePropagator is a baggage propagator that supports the specification for the header
* "uberctx" used for baggage propagation.
* (https://www.jaegertracing.io/docs/1.52/client-libraries/#baggage)
*/
class JaegerBaggagePropagator implements TextMapPropagatorInterface
{
private const UBER_BAGGAGE_HEADER_PREFIX = 'uberctx-';

private static ?TextMapPropagatorInterface $instance = null;

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

return self::$instance;
}

public function fields(): array
{
return [];
}

/** {@inheritdoc} */
public function inject(&$carrier, PropagationSetterInterface $setter = null, ContextInterface $context = null): void
{
$setter ??= ArrayAccessGetterSetter::getInstance();
$context ??= Context::getCurrent();

$baggage = Baggage::fromContext($context);

if ($baggage->isEmpty()) {
return;
}

/** @var Entry $entry */
foreach ($baggage->getAll() as $key => $entry) {
$key = self::UBER_BAGGAGE_HEADER_PREFIX . $key;
$value = rawurlencode((string) $entry->getValue());
$setter->set($carrier, $key, $value);
}
}

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

$baggageKeys = $getter->keys($carrier);

if ($baggageKeys === []) {
return $context;
}

$baggageBuilder = Baggage::getBuilder();

foreach ($baggageKeys as $key) {
if (strpos($key, self::UBER_BAGGAGE_HEADER_PREFIX) === 0) {
$baggageKey = substr($key, strlen(self::UBER_BAGGAGE_HEADER_PREFIX));
$value = $getter->get($carrier, $key) ?? '';
$baggageBuilder->set($baggageKey, rawurldecode($value));
}
}

return $context->withContextValue($baggageBuilder->build());
}
}
27 changes: 27 additions & 0 deletions src/Extension/Propagator/Jaeger/JaegerDebugFlagContextKey.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Extension\Propagator\Jaeger;

use OpenTelemetry\Context\Context;
use OpenTelemetry\Context\ContextKeyInterface;

/**
* @psalm-internal \OpenTelemetry
*/
final class JaegerDebugFlagContextKey
{
private const KEY_NAME = 'jaeger-debug-key';

private static ?ContextKeyInterface $instance = null;

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

return self::$instance;
}
}
139 changes: 139 additions & 0 deletions src/Extension/Propagator/Jaeger/JaegerPropagator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Extension\Propagator\Jaeger;

use OpenTelemetry\API\Trace\Span;
use OpenTelemetry\API\Trace\SpanContext;
use OpenTelemetry\API\Trace\SpanContextInterface;
use OpenTelemetry\API\Trace\SpanContextValidator;
use OpenTelemetry\API\Trace\TraceFlags;
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;

/**
* JaegerPropagator is a propagator that supports the specification for the header
* "uber-trace-id" used for trace context propagation across service boundaries.
* (https://www.jaegertracing.io/docs/1.52/client-libraries/#propagation-format)
*/
class JaegerPropagator implements TextMapPropagatorInterface
{
private const UBER_TRACE_ID_HEADER = 'uber-trace-id';

private const IS_NOT_SAMPLED = 0;
private const IS_SAMPLED = 1;
private const IS_DEBUG = 2;
private const DEFAULT_PARENT_SPAN_ID = 0;

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

private static ?TextMapPropagatorInterface $instance = null;

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

return self::$instance;
}

public function fields(): array
{
return self::FIELDS;
}

/** {@inheritdoc} */
public function inject(&$carrier, PropagationSetterInterface $setter = null, ContextInterface $context = null): void
{
$setter ??= ArrayAccessGetterSetter::getInstance();
$context ??= Context::getCurrent();
$spanContext = Span::fromContext($context)->getContext();

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

$flag = $this->getFlag($spanContext, $context);

$uberTraceId = sprintf(
'%s:%s:%d:%d',
$spanContext->getTraceId(),
$spanContext->getSpanId(),
self::DEFAULT_PARENT_SPAN_ID,
$flag
);

$setter->set($carrier, self::UBER_TRACE_ID_HEADER, $uberTraceId);
}

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

$spanContext = self::extractImpl($carrier, $getter, $context);
if (!$spanContext->isValid()) {
return $context;
}

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

private function getFlag(SpanContextInterface $spanContext, ContextInterface $context): int
{
if ($spanContext->isSampled()) {
if ($context->get(JaegerDebugFlagContextKey::instance())) {
return self::IS_DEBUG | self::IS_SAMPLED;
}

return self::IS_SAMPLED;
}

return self::IS_NOT_SAMPLED;
}

private static function extractImpl($carrier, PropagationGetterInterface $getter, ContextInterface &$context): SpanContextInterface
{
$headerValue = $getter->get($carrier, self::UBER_TRACE_ID_HEADER);

if ($headerValue === null) {
return SpanContext::getInvalid();
}

$pieces = explode(':', $headerValue);

if (count($pieces) != 4) {
return SpanContext::getInvalid();
}

[$traceId, $spanId, $parentSpanId, $traceFlags] = $pieces;

$traceId = str_pad($traceId, SpanContextValidator::TRACE_LENGTH, '0', STR_PAD_LEFT);
$spanId = str_pad($spanId, SpanContextValidator::SPAN_LENGTH, '0', STR_PAD_LEFT);

if (!SpanContextValidator::isValidTraceId($traceId) || !SpanContextValidator::isValidSpanId($spanId)) {
return SpanContext::getInvalid();
}

if ((int) $traceFlags & self::IS_DEBUG) {
$context = $context->with(JaegerDebugFlagContextKey::instance(), true);
}

$isSampled = ((int) $traceFlags) & 1;

return SpanContext::createFromRemoteParent(
$traceId,
$spanId,
$isSampled ? TraceFlags::SAMPLED : TraceFlags::DEFAULT
);
}
}
31 changes: 31 additions & 0 deletions src/Extension/Propagator/Jaeger/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[![Releases](https://img.shields.io/badge/releases-purple)](https://github.com/opentelemetry-php/extension-propagator-jaeger/releases)
[![Source](https://img.shields.io/badge/source-extension--propagator--jaeger-green)](https://github.com/open-telemetry/opentelemetry-php/tree/main/src/Extension/Propagator/Jaeger)
[![Mirror](https://img.shields.io/badge/mirror-opentelemetry--php:extension--propagator--jaeger-blue)](https://github.com/opentelemetry-php/extension-propagator-jaeger)
[![Latest Version](http://poser.pugx.org/open-telemetry/extension-propagator-jaeger/v/unstable)](https://packagist.org/packages/open-telemetry/extension-propagator-jaeger/)
[![Stable](http://poser.pugx.org/open-telemetry/extension-propagator-jaeger/v/stable)](https://packagist.org/packages/open-telemetry/extension-propagator-jaeger/)

# OpenTelemetry Extension
### Jaeger Propagator

Jaeger is a propagator that supports the specification for the header "uber-trace-id" used for trace context propagation across
service boundaries.(https://www.jaegertracing.io/docs/1.52/client-libraries/#propagation-format).
OpenTelemetry PHP Jaeger Propagator Extension provides option to use Jaeger Baggage (https://www.jaegertracing.io/docs/1.52/client-libraries/#baggage) propagator.

### Usage
For Jaeger trace propagator:
```text
JaegerPropagator::getInstance()
```

For Jaeger baggage propagator:
```text
JaegerBaggagePropagator::getInstance()
```

Both of the above have `extract` and `inject` methods available to extract and inject respectively into the
header.

## Contributing

This repository is a read-only git subtree split.
To contribute, please see the main [OpenTelemetry PHP monorepo](https://github.com/open-telemetry/opentelemetry-php).
18 changes: 18 additions & 0 deletions src/Extension/Propagator/Jaeger/_register.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

use OpenTelemetry\Extension\Propagator\Jaeger\JaegerBaggagePropagator;
use OpenTelemetry\Extension\Propagator\Jaeger\JaegerPropagator;
use OpenTelemetry\SDK\Common\Configuration\KnownValues;
use OpenTelemetry\SDK\Registry;

Registry::registerTextMapPropagator(
KnownValues::VALUE_JAEGER,
JaegerPropagator::getInstance()
);

Registry::registerTextMapPropagator(
KnownValues::VALUE_JAEGER_BAGGAGE,
JaegerBaggagePropagator::getInstance()
);
37 changes: 37 additions & 0 deletions src/Extension/Propagator/Jaeger/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "open-telemetry/extension-propagator-jaeger",
"description": "Jaeger propagator extension for OpenTelemetry PHP.",
"keywords": ["opentelemetry", "otel", "tracing", "apm", "extension", "propagator", "jaeger"],
"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\\Jaeger\\": "."
},
"files": [
"_register.php"
]
},
"extra": {
"branch-alias": {
"dev-main": "1.0.x-dev"
}
}
}
4 changes: 4 additions & 0 deletions src/SDK/Common/Configuration/KnownValues.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ interface KnownValues
public const VALUE_B3_MULTI = 'b3multi';
public const VALUE_CLOUD_TRACE = 'cloudtrace';
public const VALUE_CLOUD_TRACE_ONEWAY = 'cloudtrace-oneway';
public const VALUE_JAEGER = 'jaeger';
public const VALUE_JAEGER_BAGGAGE = 'jaeger-baggage';
public const VALUE_XRAY = 'xray';
public const VALUE_OTTRACE = 'ottrace';
public const VALUE_ALWAYS_ON = 'always_on';
Expand Down Expand Up @@ -109,6 +111,8 @@ interface KnownValues
self::VALUE_B3_MULTI, // B3 Multi
self::VALUE_CLOUD_TRACE, // GCP XCloudTraceContext
self::VALUE_CLOUD_TRACE_ONEWAY, // GCP XCloudTraceContext OneWay (Extract)
self::VALUE_JAEGER, // Jaeger Propagator
self::VALUE_JAEGER_BAGGAGE, // Jaeger Baggage Propagator
self::VALUE_XRAY, // AWS X-Ray (third party)
self::VALUE_OTTRACE, // OT Trace (third party)
self::VALUE_NONE, // No automatically configured propagator.
Expand Down
1 change: 1 addition & 0 deletions src/SDK/Metrics/MetricExporter/ConsoleMetricExporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ private function convertResource(ResourceInfo $resource): array
'dropped_attributes_count' => $resource->getAttributes()->getDroppedAttributesCount(),
];
}

private function convertInstrumentationScope(InstrumentationScopeInterface $scope): array
{
return [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ public function test_empty_trace_id(): void
$this->B3 => '-' . self::SPAN_ID_BASE16 . '-1',
]);
}

public function test_invalid_trace_id(): void
{
$this->assertInvalid([
Expand Down
Loading

0 comments on commit d8827f9

Please sign in to comment.