OpenTelemetry
Database spans, metrics and trace propagation with the OpenTelemetry conventions.
better-supabase/otel uses @opentelemetry/api, an optional peer
dependency. Set up an SDK as usual. Nothing is recorded without one.
import { otel, traceAuth, tracedFetch } from "better-supabase/otel";
export const betterSupabase = defineSupabase(schema).use(otel());
traceAuth(server); // auth.resolve and auth.refresh spans from betterSupabase.events
const supabase = createClient(url, key, { global: { fetch: tracedFetch() } });Spans
Every repository operation gets a CLIENT span named after its
db.query.summary, for example SELECT customers. It follows the
database semantic conventions
at the version in SPEC_PINS.otelSemconv:
| Attribute | Example |
|---|---|
db.system.name | postgresql |
db.namespace | postgres|public |
db.collection.name | customers |
db.operation.name | SELECT, INSERT, UPSERT, COUNT |
db.query.summary | SELECT customers |
db.response.returned_rows | 20 |
error.type, db.response.status_code | conflict, 23505 |
better_supabase.executor | postgrest or postgres |
server.address, server.port | abc.supabase.co, 443 |
db.namespace is {database}|{schema}. The database defaults to postgres,
the name every Supabase project uses; set another with
otel({ database: "crm" }). server.address and server.port come from
otel({ server: { address, port } }) and are left out without it.
db.response.status_code is set only for a Postgres SQLSTATE, not for
PostgREST's own codes such as PGRST116.
db.$rpc calls get a span named EXECUTE <function>, with
db.operation.name set to EXECUTE and the function in
db.stored_procedure.name. The reads in one db.$many call keep a span each.
Filter values, row data and query text are never recorded, so spans carry no personal data by default.
Metrics
db.client.operation.duration is a histogram in seconds with the system,
table, operation, error.type and the server attributes when they are set. Turn it off with otel({ metrics: false }).
Trace context
tracedFetch() wraps fetch so every PostgREST, Auth and Storage request
carries the W3C traceparent of the active span. Pass it to supabase-js as
global.fetch.
Last updated on