• Start

Languages

/

PHP

/

v2 (alpha)

/

Concepts

Observability

Emit OpenTelemetry traces and metrics from version 2 of the PHP SDK, with runtime-aware presets, a configurable provider factory, and vendor-neutral tracing and metrics seams.

Version 2 of the PHP SDK can emit a trace span and metrics for every RPC it sends. Tracing and metrics are exposed as vendor-neutral seams, in the same way logging is exposed through PSR-3: the SDK depends only on a Tracer and a Meter contract, and concrete backends plug in behind them.

Both seams default to a no-op, so the SDK never depends on a telemetry vendor unless you opt in. You enable them by setting a tracer and a meter, either through a runtime preset or directly on the DriverOptions. When either is a real implementation, the SDK adds a telemetry step to its middleware pipeline automatically and instruments every RPC.

The simplest way to enable OpenTelemetry is to pass an ObservabilityOptions to a runtime preset. The preset builds the OpenTelemetry providers, configures an OTLP exporter, and picks the export strategy that fits the runtime, then wires the tracer and meter onto the DriverOptions for you.

The presets need the OpenTelemetry SDK and the OTLP exporter.

composer require open-telemetry/sdk open-telemetry/exporter-otlp
use SurrealDB\SDK\Surreal;
use SurrealDB\SDK\Runtime\Runtime;
use SurrealDB\SDK\Telemetry\OpenTelemetry\ObservabilityOptions;

$observability = new ObservabilityOptions(
endpoint: 'http://localhost:4318', // OTLP collector
serviceName: 'my-app',
);

// PHP-FPM / CLI: buffer spans, flush after the request.
$db = new Surreal(Runtime::sync(observability: $observability));

// OpenSwoole: direct, non-blocking export.
$db = new Surreal(Runtime::swoole(observability: $observability));

// FrankenPHP / Amp: direct export over a non-blocking client.
$db = new Surreal(Runtime::amp(observability: $observability));

Each preset chooses an export strategy suited to how the runtime handles long work.

PresetStrategyBehaviour
Runtime::sync()BatchedBuffers spans in memory and flushes once the request finishes, after fastcgi_finish_request() under PHP-FPM, so export stays off the user-visible request path
Runtime::swoole()DirectExports each span as it ends; OpenSwoole's runtime hooks make that export non-blocking
Runtime::amp()DirectExports each span as it ends over a non-blocking Amp PSR-18 client, so export yields on the event loop

The Amp preset additionally needs amphp/http-client-psr7 for its non-blocking exporter.

composer require amphp/http-client-psr7

ObservabilityOptions is a value object in the SurrealDB\SDK\Telemetry\OpenTelemetry namespace. Every field has a default, so new ObservabilityOptions() works against a local collector.

OptionDefaultDescription
endpointenv, then http://localhost:4318Base OTLP endpoint, with no signal path
contentTypeapplication/x-protobufOTLP payload encoding, or application/json
headers[]Extra headers sent on every export, such as an ingest token
serviceNamesurrealdb-phpThe service.name resource attribute
samplerRationullHead sampling probability in [0, 1]; null records every span
tracestrueEmit spans
metricstrueEmit metrics
finishRequestBeforeFlushtrueBatched strategy: call fastcgi_finish_request() before draining the buffer
maxQueueSize, scheduledDelayMillis, maxExportBatchSizenullBatched strategy tuning; null uses the OpenTelemetry defaults

When endpoint is empty, the SDK falls back to the OTEL_EXPORTER_OTLP_ENDPOINT environment variable, then http://localhost:4318. The /v1/traces and /v1/metrics paths are appended automatically.

$observability = new ObservabilityOptions(
serviceName: 'checkout-api',
headers: ['Authorization' => 'Bearer ' . $token],
samplerRatio: 0.1, // sample 10% of traces
);

The Runtime::sync() preset registers a shutdown hook to flush buffered telemetry. When a framework owns the response lifecycle, such as Laravel, build the providers yourself with OtelObservability and flush in a terminable hook instead.

use SurrealDB\SDK\Surreal;
use SurrealDB\SDK\Connection\DriverOptions;
use SurrealDB\SDK\Telemetry\OpenTelemetry\ObservabilityOptions;
use SurrealDB\SDK\Telemetry\OpenTelemetry\OtelObservability;

$telemetry = OtelObservability::batched(new ObservabilityOptions(serviceName: 'my-app'));

$db = new Surreal(new DriverOptions(
tracer: $telemetry->tracer(),
meter: $telemetry->meter(),
));

// Laravel: flush after the response has been sent to the client.
app()->terminating(static fn () => $telemetry->forceFlush());

OtelObservability is the factory the presets use. Build it with batched() for the FPM/CLI strategy or direct() for async runtimes, pass ampHttpClient() as the direct() client on Amp, and call forceFlush() to drain buffered telemetry or shutdown() to flush and tear the providers down. Both factories accept optional span and metric exporters, mainly for tests and custom backends.

If your application already configures the OpenTelemetry SDK, bridge its providers onto the SDK seams rather than building new ones. This needs only open-telemetry/api.

composer require open-telemetry/api

Use fromGlobals() to read the globally registered provider, so SDK spans nest under whatever ambient span is active in the calling code. You manage the exporters and flushing through your own provider.

use SurrealDB\SDK\Surreal;
use SurrealDB\SDK\Connection\DriverOptions;
use SurrealDB\SDK\Telemetry\OpenTelemetry\OtelTracer;
use SurrealDB\SDK\Telemetry\OpenTelemetry\OtelMeter;

$db = new Surreal(new DriverOptions(
tracer: OtelTracer::fromGlobals(),
meter: OtelMeter::fromGlobals(),
));

To bridge a provider you manage explicitly, pass its tracer or meter to the constructor.

$db = new Surreal(new DriverOptions(
tracer: new OtelTracer($tracerProvider->getTracer('surrealdb/surrealdb.php')),
meter: new OtelMeter($meterProvider->getMeter('surrealdb/surrealdb.php')),
));

When you do not want an OpenTelemetry dependency, the PSR-3 adapter records spans and measurements as structured log lines. It is zero-dependency and useful for getting span timing into logs you already collect.

use SurrealDB\SDK\Surreal;
use SurrealDB\SDK\Connection\DriverOptions;
use SurrealDB\SDK\Telemetry\Psr\Psr3Tracer;
use SurrealDB\SDK\Telemetry\Psr\Psr3Meter;

$db = new Surreal(new DriverOptions(
tracer: new Psr3Tracer($logger),
meter: new Psr3Meter($logger),
));

Both PSR-3 adapters log at debug level by default. Pass a PSR-3 log level as the second argument to change it.

The telemetry step follows the OpenTelemetry database semantic conventions. For each RPC it opens a span named surrealdb.<method> (for example surrealdb.query) with the Client span kind, then sets its status to Ok or Error and records any thrown exception.

AttributeDescription
db.system.nameAlways surrealdb
db.operation.nameThe RPC method, such as query or signin
db.surrealdb.sessionThe session id, when the call targets a non-default session
db.surrealdb.transactionThe transaction id, when the call runs inside one
db.query.textThe SurrealQL text on query spans, only when query-text capture is enabled
error.typeThe error kind or exception class, on failures

Two metrics are recorded per call:

MetricTypeUnitDescription
db.client.operation.durationHistogramsHow long each operation took
db.client.operation.countCounter{operation}Number of operations, dimensioned by outcome

To send telemetry somewhere the bundled adapters do not cover, implement the contracts in the SurrealDB\SDK\Contracts namespace. A backend can implement tracing, metrics, or both; the two seams are independent.

ContractMethods
TracerstartSpan(string $name, SpanKind $kind = SpanKind::Client, array $attributes = []): Span
SpansetAttribute(), recordException(), setStatus(), end()
Metercounter(string $name, ?string $unit, ?string $description): Counter, histogram(...): Histogram
Counteradd(int\|float $value = 1, array $attributes = []): void
Histogramrecord(int\|float $value, array $attributes = []): void

Spans use two enums from SurrealDB\SDK\Enum. SpanKind has Internal, Client, Server, Producer, and Consumer. SpanStatus has Unset, Ok, and Error.

use SurrealDB\SDK\Contracts\Tracer;
use SurrealDB\SDK\Contracts\Span;
use SurrealDB\SDK\Enum\SpanKind;

final class CustomTracer implements Tracer
{
public function startSpan(string $name, SpanKind $kind = SpanKind::Client, array $attributes = []): Span
{
// return your own Span implementation
}
}

Telemetry is for traces and metrics. To log each RPC instead, pass a PSR-3 logger to DriverOptions. The SDK then adds its logging step to the pipeline automatically, recording every request, its outcome, and its timing. See Middleware for the built-in logging step.

$db = new Surreal(new DriverOptions(logger: $logger));

Was this page helpful?