Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
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
15 changes: 12 additions & 3 deletions src/Propagation/Instana/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
OpenTelemetry Instana Propagator
[![Releases](https://img.shields.io/badge/releases-purple)](https://github.com/opentelemetry-php/contrib-propagator-instana/releases)
[![Issues](https://img.shields.io/badge/issues-pink)](https://github.com/open-telemetry/opentelemetry-php/issues)
[![Source](https://img.shields.io/badge/source-contrib-green)](https://github.com/open-telemetry/opentelemetry-php-contrib/tree/main/src/Propagation/Instana)
[![Mirror](https://img.shields.io/badge/mirror-opentelemetry--php--contrib-blue)](https://github.com/opentelemetry-php/contrib-propagator-instana)
[![Latest Version](http://poser.pugx.org/open-telemetry/opentelemetry-propagation-instana/v/unstable)](https://packagist.org/packages/open-telemetry/opentelemetry-propagation-instana/)
[![Stable](http://poser.pugx.org/open-telemetry/opentelemetry-propagation-instana/v/stable)](https://packagist.org/packages/open-telemetry/opentelemetry-propagation-instana/)

The OpenTelemetry Propagator for Instana provides HTTP header propagation for systems that are using IBM Observability by Instana.
This is a read-only subtree split of https://github.com/open-telemetry/opentelemetry-php-contrib.

# OpenTelemetry Instana Propagator

The OpenTelemetry Propagator for Instana provides HTTP header propagation and Baggage propagation for systems that are using IBM Observability by Instana.
This propagator translates the Instana trace correlation headers (`X-INSTANA-T/X-INSTANA-S/X-INSTANA-L`) into the OpenTelemetry `SpanContext`, and vice versa.
It does not handle `TraceState`.

Expand All @@ -14,7 +23,7 @@ composer require open-telemetry/opentelemetry-propagation-instana
## Usage

```
$propagator = InstanaMultiPropagator::getInstance();
$propagator = InstanaPropagator::getInstance();
```

Both of the above have extract and inject methods available to extract and inject respectively into the header.
Expand Down
6 changes: 4 additions & 2 deletions src/Propagation/Instana/_register.php
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
<?php

declare(strict_types=1);
use OpenTelemetry\Contrib\Propagation\Instana\InstanaMultiPropagator;

use OpenTelemetry\Contrib\Propagation\Instana\InstanaPropagator;
use OpenTelemetry\SDK\Registry;

if (!class_exists(Registry::class)) {
return;
}

Registry::registerTextMapPropagator(
'instana',
InstanaMultiPropagator::getInstance()
InstanaPropagator::getInstance()
);
172 changes: 172 additions & 0 deletions src/Propagation/Instana/src/InstanaContextPropagator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Contrib\Propagation\Instana;

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

/**
* InstanaPropagator is a propagator that supports the specification for multiple
* "instana" http headers used for trace context propagation across service
* boundaries.
*/
final class InstanaContextPropagator implements TextMapPropagatorInterface
{
/**
* The X-INSTANA-T header is required and is encoded as 32 lower-hex characters.
* For example, a 128-bit TraceId header might look like: X-Instana-T: 463ac35c9f6413ad48485a3953bb6124 .
*
*/
private const INSTANA_TRACE_ID_HEADER = 'X-INSTANA-T';

/**
* The X-Instana-S header must be present on a child span and absent on the root span.
* It is encoded as 16 lower-hex characters.
* For example, a ParentSpanId header might look like: X-Instana-S: 0020000000000001
*
*/
private const INSTANA_SPAN_ID_HEADER = 'X-INSTANA-S';

/**
* An accept sampling decision is encoded as X-INSTANA-L: 1 and a deny as X-INSTANA-L: 0.
* Absent means defer the decision to the receiver of this header.
* For example, a Sampled header might look like: X-Instana-L: 1
*
*/
private const INSTANA_LEVEL_HEADER = 'X-INSTANA-L';

private const IS_SAMPLED = '1';
private const VALID_SAMPLED = [self::IS_SAMPLED, 'true'];
private const IS_NOT_SAMPLED = '0';
private const VALID_NON_SAMPLED = [self::IS_NOT_SAMPLED, 'false'];

private const FIELDS = [
self::INSTANA_TRACE_ID_HEADER,
self::INSTANA_SPAN_ID_HEADER,
self::INSTANA_LEVEL_HEADER,
];

private static ?self $instance = null;

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

return self::$instance;
}

/**
* @suppress PhanUndeclaredClassAttribute
*/
#[\Override]
public function fields(): array
{
return self::FIELDS;
}

/**
* @suppress PhanUndeclaredClassAttribute
*/
#[\Override]
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;
}

// Inject multiple Instana headers
$setter->set($carrier, self::INSTANA_TRACE_ID_HEADER, $spanContext->getTraceId());
$setter->set($carrier, self::INSTANA_SPAN_ID_HEADER, $spanContext->getSpanId());
$setter->set($carrier, self::INSTANA_LEVEL_HEADER, $spanContext->isSampled() ? self::IS_SAMPLED : self::IS_NOT_SAMPLED);
}

/**
* @suppress PhanUndeclaredClassAttribute
*/
#[\Override]
public function extract($carrier, ?PropagationGetterInterface $getter = null, ?ContextInterface $context = null): ContextInterface
{
$getter ??= ArrayAccessGetterSetter::getInstance();
$context ??= Context::getCurrent();

$traceId = self::readHeader($carrier, $getter, self::INSTANA_TRACE_ID_HEADER);
$spanId = self::readHeader($carrier, $getter, self::INSTANA_SPAN_ID_HEADER);
$level = self::getSampledValue($carrier, $getter);

$spanContext = self::extractImpl($carrier, $getter);

if (($traceId === '' && $spanId === '') && $level !== null) {
return (new NonRecordingSpan($spanContext))
->storeInContext($context);
} elseif (!$spanContext->isValid()) {
return $context;
}

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

private static function readHeader($carrier, PropagationGetterInterface $getter, string $key): string
{
$header = $getter->get($carrier, $key) ?: '';

// Return the header or an empty string if not found
return $header;
}

private static function getSampledValue($carrier, PropagationGetterInterface $getter): ?int
{
$value = $getter->get($carrier, self::INSTANA_LEVEL_HEADER);

if ($value === null) {
return null;
}

if (in_array(strtolower($value), self::VALID_SAMPLED)) {
return (int) self::IS_SAMPLED;
}

if (in_array(strtolower($value), self::VALID_NON_SAMPLED)) {
return (int) self::IS_NOT_SAMPLED;
}

return null;
}

private static function extractImpl($carrier, PropagationGetterInterface $getter): SpanContextInterface
{
$traceId = self::readHeader($carrier, $getter, self::INSTANA_TRACE_ID_HEADER);
$spanId = self::readHeader($carrier, $getter, self::INSTANA_SPAN_ID_HEADER);
$level = self::getSampledValue($carrier, $getter);

if ($traceId && strlen($traceId) < 32) {
$traceId = str_pad($traceId, 32, '0', STR_PAD_LEFT);
}

if ($spanId && strlen($spanId) < 16) {
$spanId = str_pad($spanId, 16, '0', STR_PAD_LEFT);
}

return SpanContext::createFromRemoteParent(
$traceId,
$spanId,
$level ? TraceFlags::SAMPLED : TraceFlags::DEFAULT
);
}
}
76 changes: 76 additions & 0 deletions src/Propagation/Instana/src/InstanaPropagator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Contrib\Propagation\Instana;

use OpenTelemetry\API\Baggage\Propagation\BaggagePropagator;
use OpenTelemetry\Context\Context;
use OpenTelemetry\Context\ContextInterface;
use OpenTelemetry\Context\Propagation\PropagationGetterInterface;
use OpenTelemetry\Context\Propagation\PropagationSetterInterface;
use OpenTelemetry\Context\Propagation\TextMapPropagatorInterface;

class InstanaPropagator implements TextMapPropagatorInterface
{
/** @var TextMapPropagatorInterface[] */
private array $propagators;

private static ?self $instance = null;

public function __construct(array $propagators)
{
$this->propagators = $propagators;
}

public static function getInstance(): self
{
if (null === self::$instance) {
// Create propagator instances
$instanaPropagator = InstanaContextPropagator::getInstance();
$baggagePropagator = new BaggagePropagator();
self::$instance = new self([$instanaPropagator, $baggagePropagator]);
}

return self::$instance;
}

/**
* @suppress PhanUndeclaredClassAttribute
*/
#[\Override]
public function fields(): array
{
$fields = [];
foreach ($this->propagators as $propagator) {
$fields = array_merge($fields, $propagator->fields());
}

return array_values(array_unique($fields));
}

/**
* @suppress PhanUndeclaredClassAttribute
*/
#[\Override]
public function inject(&$carrier, ?PropagationSetterInterface $setter = null, ?ContextInterface $context = null): void
{
foreach ($this->propagators as $propagator) {
$propagator->inject($carrier, $setter, $context);
}
}

/**
* @suppress PhanUndeclaredClassAttribute
*/
#[\Override]
public function extract($carrier, ?PropagationGetterInterface $getter = null, ?ContextInterface $context = null): ContextInterface
{
$context ??= Context::getCurrent();
foreach ($this->propagators as $propagator) {
$context = $propagator->extract($carrier, $getter, $context);
}

return $context;
}
}
Loading
Loading