Attribute-based Instrumentation

Automatically create spans using PHP 8 attributes with OpenTelemetry PHP Distro.

OpenTelemetry PHP Distro supports automatic span creation using PHP 8 attributes. Annotate methods or functions with #[WithSpan] to create spans without writing instrumentation code manually.

Prerequisites

  • PHP 8.0 or later (PHP attributes require PHP 8+).
  • open-telemetry/api package installed in your application.
  • OTEL_PHP_ATTR_HOOKS_ENABLED=true set in the environment (disabled by default).

Enable

export OTEL_PHP_ATTR_HOOKS_ENABLED=true

Or in php.ini:

opentelemetry_distro.attr_hooks_enabled=true

Basic usage

use OpenTelemetry\API\Instrumentation\WithSpan;

class OrderService
{
    #[WithSpan]
    public function processOrder(int $orderId): string
    {
        // A span named "OrderService::processOrder" is created automatically.
        return "processed-{$orderId}";
    }
}

#[WithSpan] options

#[WithSpan(
    span_name: 'custom.span.name',          // default: "ClassName::methodName"
    span_kind: SpanKind::KIND_SERVER,        // default: KIND_INTERNAL
    attributes: ['key' => 'value'],          // static attributes added to the span
)]

All arguments are optional and can be passed positionally or by name:

// Positional
#[WithSpan('payment.charge', SpanKind::KIND_CLIENT, ['db.system' => 'redis'])]

// Named — any subset
#[WithSpan(span_kind: SpanKind::KIND_PRODUCER)]
#[WithSpan(span_name: 'message.publish', span_kind: SpanKind::KIND_PRODUCER)]

Capturing parameter values with #[SpanAttribute]

Add #[SpanAttribute] to function parameters to include their runtime values as span attributes:

use OpenTelemetry\API\Instrumentation\WithSpan;
use OpenTelemetry\API\Instrumentation\SpanAttribute;

class UserService
{
    #[WithSpan]
    public function createUser(
        #[SpanAttribute] string $username,               // attribute key = "username"
        string                  $password,               // not captured
        #[SpanAttribute('user.email')] string $email,   // attribute key = "user.email"
    ): int {
        // ...
    }
}

Capturing property values with #[SpanAttribute]

Apply #[SpanAttribute] to class properties to capture their value at the time the method is called:

class InvoiceService
{
    #[SpanAttribute]
    public string $customerId = '';

    #[SpanAttribute('invoice.currency')]
    public string $currency = 'EUR';

    #[WithSpan('invoice.generate')]
    public function generate(): string
    {
        // Span attributes include: customerId, invoice.currency
    }
}

Exception recording

If the annotated method throws, the span automatically records the exception and sets status to ERROR. The exception propagates normally.

#[WithSpan]
public function riskyOperation(): void
{
    throw new \RuntimeException('something went wrong');
    // Span is ended with STATUS_ERROR and exception event attached.
}

Nested spans

Calling one #[WithSpan] method from another creates nested spans automatically:

class Pipeline
{
    #[WithSpan('pipeline.run')]
    public function run(): void
    {
        $this->step1(); // child span: "pipeline.step1"
        $this->step2(); // child span: "pipeline.step2"
    }

    #[WithSpan('pipeline.step1')]
    private function step1(): void {}

    #[WithSpan('pipeline.step2')]
    private function step2(): void {}
}

Standalone functions

#[WithSpan] works on standalone functions, not only methods:

#[WithSpan('compute.result')]
function computeResult(#[SpanAttribute] int $input): int
{
    return $input * 2;
}

Standard span attributes

Every #[WithSpan] span includes these attributes from the declaration site:

AttributeValue
code.functionFunction or method name
code.namespaceClass name (empty for standalone functions)
code.filepathSource file path
code.linenoLine number of the declaration

Compatibility

#[WithSpan] and #[SpanAttribute] are the same attributes used by the official opentelemetry-php-instrumentation extension. Applications already using that extension can enable this feature without code changes.