Skip to content
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

Add Jaeger Propagator #1188

Merged
merged 11 commits into from
Dec 15, 2023
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;
weslenteche marked this conversation as resolved.
Show resolved Hide resolved
}

$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"
}
}
}
3 changes: 3 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,7 @@ 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_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