OtelSpanHandle
Real SpanHandle, wrapping an active OpenTelemetry SpanInterface.
If the span was activated (pushed onto the current context via SpanInterface::activate() when Trace::span() created it), the owning ScopeInterface is detached exactly once, when OtelSpanHandle::end() runs.
A handle obtained from Trace::current() is a borrowed reference — it didn’t create the span and doesn’t own its lifecycle, so $ownsLifecycle = false there. This matters: Trace::current() is often used inline as a bare expression, e.g. Trace::current()->recordException($e)->setStatusError(...); — the temporary OtelSpanHandle this creates has no other reference and is destructed at the end of that statement. If __destruct() unconditionally called end(), that would end the REAL underlying span the caller merely borrowed a reference to, before whoever actually owns it (e.g. TelemetryMiddleware, still further up the call stack) gets a chance to — silently discarding every mutation made after that point, since a real OTel span ignores setStatus()/recordException()/etc. once ended. This is exactly the bug an earlier version of this file had, caught during the OTel Collector end-to-end verification (docs/OPENTELEMETRY_E2E_VERIFICATION.md): RoutingMiddleware captures Trace::current() into a local $root variable to rename it on a successful match; that local going out of scope (including mid-exception-unwind) was silently ending the root span long before TelemetryMiddleware’s own finally block ran, so an action exception’s Error status never made it onto the exported root span. An explicit ->end() call is still always honored, on any handle — this only changes whether destruction implies ending.
Every mutator is wrapped so a call site can never crash the request: attribute keys/values are validated by AttributeSanitizer against what the SDK’s own API accepts (bool|int|float|string|array|null, non-empty-string keys), so passing an object/resource/etc — a caller bug, or hostile/unexpected instrumentation input — throws there instead of reaching the SDK. That is swallowed and logged at debug level rather than propagating, matching the no-op layer’s “instrumenting a call site is * always safe” guarantee.
Synopsis
Section titled “Synopsis”final class OtelSpanHandle implements SpanHandle
| Implements | SpanHandle |
| Source | OtelSpanHandle.php |
Constructor
Section titled “Constructor”__construct()
Section titled “__construct()”public function __construct(SpanInterface $span, ?ScopeInterface $scope = null, bool $ownsLifecycle = true): mixed
| Parameter | Type | Description |
|---|---|---|
$span | SpanInterface | |
$scope | ?``ScopeInterface | |
$ownsLifecycle | bool |
Returns mixed
Methods
Section titled “Methods”| Method | Description |
|---|---|
__destruct(): mixed | |
addEvent(string $name, array $attributes = []): static | Adds a timestamped event to the span. |
end(): void | Ends the underlying span and detaches the scope it was activated in. |
recordException(Throwable $e): static | Records $e on the span as an exception event. |
setAttribute(string $key, mixed $value): static | Sets a single attribute, passing key and value through AttributeSanitizer::sanitizeEntry() first. |
setAttributes(array $attributes): static | Sets several attributes at once, sanitized as in OtelSpanHandle::setAttribute(). |
setStatusError(?string $description = null): static | Sets the span’s status to ERROR with an optional description. |
spanId(): ?string | The 16-hex-character span ID of the underlying span, or null under the same conditions as OtelSpanHandle::traceId(). |
traceId(): ?string | The 32-hex-character trace ID of the underlying span. |
updateName(string $name): static | Renames the underlying span, substituting (unnamed) for an empty string so an exported span always carries a name. |
__destruct()
Section titled “__destruct()”public function __destruct(): mixed
Returns mixed
addEvent()
Section titled “addEvent()”public function addEvent(string $name, array $attributes = []): static
Adds a timestamped event to the span.
Failures are swallowed and logged at debug level.
| Parameter | Type | Description |
|---|---|---|
$name | string | |
$attributes | array |
Returns static
public function end(): void
Ends the underlying span and detaches the scope it was activated in.
Guarded by an $ended flag, so repeated calls — including the one from __destruct() on a handle that owns its span’s lifecycle — do nothing after the first. Both the end and the detach are swallowed on failure and logged at debug level.
recordException()
Section titled “recordException()”public function recordException(Throwable $e): static
Records $e on the span as an exception event.
Does not change the span’s status — call OtelSpanHandle::setStatusError() for that. Failures are swallowed and logged at debug level.
| Parameter | Type | Description |
|---|---|---|
$e | Throwable |
Returns static
setAttribute()
Section titled “setAttribute()”public function setAttribute(string $key, mixed $value): static
Sets a single attribute, passing key and value through AttributeSanitizer::sanitizeEntry() first.
A key or value the SDK cannot accept makes the sanitizer throw; that is caught and logged at debug level, so the attribute is dropped rather than failing the caller.
| Parameter | Type | Description |
|---|---|---|
$key | string | |
$value | mixed |
Returns static
setAttributes()
Section titled “setAttributes()”public function setAttributes(array $attributes): static
Sets several attributes at once, sanitized as in OtelSpanHandle::setAttribute().
The batch is applied as a unit: if sanitizing any entry throws, none of them reach the span and the failure is logged at debug level.
| Parameter | Type | Description |
|---|---|---|
$attributes | array |
Returns static
setStatusError()
Section titled “setStatusError()”public function setStatusError(?string $description = null): static
Sets the span’s status to ERROR with an optional description.
The SDK ignores status changes on an already-ended span, so this has no effect after OtelSpanHandle::end(). Failures are swallowed and logged at debug level.
| Parameter | Type | Description |
|---|---|---|
$description | ?``string |
Returns static
spanId()
Section titled “spanId()”public function spanId(): ?string
The 16-hex-character span ID of the underlying span, or null under the same conditions as OtelSpanHandle::traceId().
Returns ?``string
traceId()
Section titled “traceId()”public function traceId(): ?string
The 32-hex-character trace ID of the underlying span.
Null when the span context is invalid (a non-recording or propagation placeholder span) or when reading it throws. Independent of the sampling decision — an unsampled span still reports its ID.
Returns ?``string
updateName()
Section titled “updateName()”public function updateName(string $name): static
Renames the underlying span, substituting (unnamed) for an empty string so an exported span always carries a name.
| Parameter | Type | Description |
|---|---|---|
$name | string |
Returns static