Loading

Attribute-based instrumentation

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

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

Attribute-based instrumentation is disabled by default. Enable it using either an environment variable or the php.ini file:

export OTEL_PHP_ATTR_HOOKS_ENABLED=true
		
export ELASTIC_OTEL_ATTR_HOOKS_ENABLED=true
		
opentelemetry_distro.attr_hooks_enabled=true
		
elastic_otel.attr_hooks_enabled=true
		
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(
    span_name: 'custom.span.name',          // default: "ClassName::methodName"
    span_kind: SpanKind::KIND_SERVER,
    attributes: ['key' => 'value'],
)]
		
  1. default: KIND_INTERNAL
  2. static attributes added to the span

All arguments are optional. You can pass them 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)]
		

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,
        #[SpanAttribute('user.email')] string $email,   // attribute key = "user.email"
    ): int {
        // ...
    }
}
		
  1. not captured

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
    }
}
		

If the annotated method throws an exception, the span automatically records it 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.
}
		

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 {}
}
		

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

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

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

Attribute Value
code.function Function or method name
code.namespace Class name (empty for standalone functions)
code.filepath Source file path
code.lineno Line number of the declaration

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