OpenTelemetry Trace Context Propagation [PHP]

This guide covers PHP-specific implementation of context propagation. For a comprehensive overview of context propagation concepts, W3C TraceContext, propagators, and troubleshooting, see the OpenTelemetry Context Propagation guide.

Automatic propagation

OpenTelemetry PHP handles traceparent headers automatically in most scenarios. When using auto-instrumentation libraries, HTTP client libraries automatically inject traceparent headers into outgoing requests, and server libraries automatically extract them from incoming requests.

Auto-instrumentation

bash
# Enable auto-instrumentation
export OTEL_PHP_AUTOLOAD_ENABLED=true
export OTEL_SERVICE_NAME=my-service
php my-application.php

Auto-instrumentation packages for HTTP clients and interfaces automatically inject W3C tracecontext headers to outgoing HTTP requests.

Manual propagation

When automatic instrumentation is not available, you can manually handle traceparent headers using OpenTelemetry's TraceContextPropagator API.

Extracting context

php
<?php

use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;
use OpenTelemetry\API\Trace\SpanKind;

// Extract context from incoming request headers
$context = TraceContextPropagator::getInstance()->extract($request->getHeaders());

// Create a new span with the extracted parent context
$span = $tracer->spanBuilder('HTTP ' . $_SERVER['REQUEST_METHOD'])
    ->setParent($context)
    ->setSpanKind(SpanKind::KIND_SERVER)
    ->startSpan();

$scope = $span->activate();

try {
    // Your business logic here
    processRequest();
} finally {
    $span->end();
    $scope->detach();
}

Injecting context

php
<?php

use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;
use GuzzleHttp\Psr7\Request;

// Create carrier array for headers
$carrier = [];

// Inject current trace context into carrier
TraceContextPropagator::getInstance()->inject($carrier);

// Create request with injected headers
$request = new Request('GET', 'http://example.com/api');

// Add trace headers to request
foreach ($carrier as $name => $value) {
    $request = $request->withAddedHeader($name, $value);
}

// Send request
$client->send($request);

Debugging propagation

Logging context

Log incoming traceparent headers and current span context for debugging:

php
<?php

use OpenTelemetry\API\Trace\Span;

// Log incoming traceparent header
if (isset($_SERVER['HTTP_TRACEPARENT'])) {
    error_log('Incoming traceparent: ' . $_SERVER['HTTP_TRACEPARENT']);
}

// Log current span context
$spanContext = Span::getCurrent()->getSpanContext();
if ($spanContext->isValid()) {
    error_log(sprintf(
        'Current trace context - TraceId: %s, SpanId: %s, Sampled: %s',
        $spanContext->getTraceId(),
        $spanContext->getSpanId(),
        $spanContext->isSampled() ? 'true' : 'false'
    ));
} else {
    error_log('No valid span context found');
}

Validating format

Validate traceparent headers to ensure they follow the W3C specification:

php
<?php

class TraceparentValidator
{
    private const TRACEPARENT_PATTERN = '/^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$/';

    public static function isValidTraceparent(string $traceparent): bool
    {
        return !empty($traceparent) && preg_match(self::TRACEPARENT_PATTERN, $traceparent) === 1;
    }

    public static function parseTraceparent(string $traceparent): array
    {
        if (!self::isValidTraceparent($traceparent)) {
            throw new InvalidArgumentException('Invalid traceparent format: ' . $traceparent);
        }

        $parts = explode('-', $traceparent);
        return [
            'version' => $parts[0],
            'traceId' => $parts[1],
            'spanId' => $parts[2],
            'flags' => $parts[3],
            'isSampled' => $parts[3] === '01'
        ];
    }
}

Getting trace info

Access current trace context information:

php
<?php

use OpenTelemetry\API\Trace\Span;

function getTraceInfo(): array
{
    $spanContext = Span::getCurrent()->getSpanContext();

    if (!$spanContext->isValid()) {
        return ['error' => 'No valid span context available'];
    }

    return [
        'traceId' => $spanContext->getTraceId(),
        'spanId' => $spanContext->getSpanId(),
        'isSampled' => $spanContext->isSampled(),
        'isRemote' => $spanContext->isRemote(),
    ];
}

What's next?