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.
Runtime presets
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.
Each preset chooses an export strategy suited to how the runtime handles long work.
| Preset | Strategy | Behaviour |
|---|---|---|
Runtime::sync() | Batched | Buffers 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() | Direct | Exports each span as it ends; OpenSwoole's runtime hooks make that export non-blocking |
Runtime::amp() | Direct | Exports 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.
Configuring ObservabilityOptions
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.
| Option | Default | Description |
|---|---|---|
endpoint | env, then http://localhost:4318 | Base OTLP endpoint, with no signal path |
contentType | application/x-protobuf | OTLP payload encoding, or application/json |
headers | [] | Extra headers sent on every export, such as an ingest token |
serviceName | surrealdb-php | The service.name resource attribute |
samplerRatio | null | Head sampling probability in [0, 1]; null records every span |
traces | true | Emit spans |
metrics | true | Emit metrics |
finishRequestBeforeFlush | true | Batched strategy: call fastcgi_finish_request() before draining the buffer |
maxQueueSize, scheduledDelayMillis, maxExportBatchSize | null | Batched 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.
Flushing in a framework
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.
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.
Bridging an existing OpenTelemetry setup
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.
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.
To bridge a provider you manage explicitly, pass its tracer or meter to the constructor.
PSR-3 logs
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.
Both PSR-3 adapters log at debug level by default. Pass a PSR-3 log level as the second argument to change it.
What is emitted
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.
| Attribute | Description |
|---|---|
db.system.name | Always surrealdb |
db.operation.name | The RPC method, such as query or signin |
db.surrealdb.session | The session id, when the call targets a non-default session |
db.surrealdb.transaction | The transaction id, when the call runs inside one |
db.query.text | The SurrealQL text on query spans, only when query-text capture is enabled |
error.type | The error kind or exception class, on failures |
Two metrics are recorded per call:
| Metric | Type | Unit | Description |
|---|---|---|---|
db.client.operation.duration | Histogram | s | How long each operation took |
db.client.operation.count | Counter | {operation} | Number of operations, dimensioned by outcome |
Note
Custom backends
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.
| Contract | Methods |
|---|---|
Tracer | startSpan(string $name, SpanKind $kind = SpanKind::Client, array $attributes = []): Span |
Span | setAttribute(), recordException(), setStatus(), end() |
Meter | counter(string $name, ?string $unit, ?string $description): Counter, histogram(...): Histogram |
Counter | add(int\|float $value = 1, array $attributes = []): void |
Histogram | record(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.
Logging
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.
Learn more
Runtimes and workers for the presets that wire observability per runtime
Middleware for the pipeline the telemetry step runs in
Events for PSR-14 events you can observe per request
Utilities for the
DriverOptionsfields