3.1 KiB
withDbSpan
Purpose
withDbSpan is a thin OTel tracing helper that wraps any async database operation in a named span. It handles the boilerplate of starting a span, setting its status on success or error, recording exceptions, and always ending the span — so concrete adapters and migration runners can add tracing with a single wrapper call rather than repeating the span lifecycle pattern in every method.
Like the instruments module, withDbSpan relies on the OTel no-op tracer when no SDK is configured, so it is safe to call in any environment including tests with no OTel setup.
Features
- Creates a named span via
trace.getTracer("@suaveplan/db") - Sets
SpanStatusCode.OKon successful completion - Sets
SpanStatusCode.ERRORand records the exception on throw - Always ends the span (via
finally) — no span leaks - Accepts optional
Attributesfor span metadata (e.g.db.name,db.operation) - Works with the OTel no-op provider — zero overhead when no exporter is registered
Usage: Basic Example
import { withDbSpan } from "@suaveplan/db/telemetry/trace";
const rows = await withDbSpan("db.query", async () => {
return driver.query("SELECT * FROM users");
});
Usage: Advanced Example
import { withDbSpan } from "@suaveplan/db/telemetry/trace";
// With span attributes for richer trace data
const result = await withDbSpan(
"db.execute",
async () => driver.execute("DELETE FROM sessions WHERE expires_at < ?", [Date.now()]),
{
"db.name": "app",
"db.operation": "execute",
"db.statement": "DELETE FROM sessions",
}
);
// Wrapping a full transaction
const transferResult = await withDbSpan("db.transaction.transfer", async () => {
return adapter.transaction(async (tx) => {
await tx.execute("UPDATE accounts SET balance = balance - ? WHERE id = ?", [100, "sender"]);
await tx.execute("UPDATE accounts SET balance = balance + ? WHERE id = ?", [100, "receiver"]);
return { ok: true };
});
}, { "transfer.amount": 100 });
API Reference
withDbSpan<T>
withDbSpan<T>(
name: string,
fn: () => Promise<T>,
attrs?: Attributes
): Promise<T>
name— OTel span name (e.g."db.query","db.migrate")fn— async function to execute inside the spanattrs— optionalAttributesmap attached to the span at creation time- Returns — the resolved value of
fn() - Throws — re-throws whatever
fn()throws after recording the exception on the span
Implementation Notes
withDbSpan uses the module-level tracer constant (created once at import time via trace.getTracer("@suaveplan/db")) rather than calling getTracer on every invocation. The OTel spec guarantees that getTracer with the same name returns the same tracer instance, so this is a safe optimisation.
Non-Error thrown values (e.g. strings, numbers) are wrapped in new Error(String(err)) before being passed to span.recordException. This satisfies the OTel SDK's type requirement for recordException while still re-throwing the original value, so callers that catch strings continue to work as before.