Files
skills/docs/persistence/database/db/src/telemetry/trace.md
2026-05-29 08:20:56 +01:00

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.OK on successful completion
  • Sets SpanStatusCode.ERROR and records the exception on throw
  • Always ends the span (via finally) — no span leaks
  • Accepts optional Attributes for 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 span
  • attrs — optional Attributes map 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.