# OpenTelemetry

> Database spans, metrics and trace propagation with the OpenTelemetry conventions.

Source: https://bettersupabase.com/docs/standards/otel

`better-supabase/otel` uses `@opentelemetry/api`, an optional peer
dependency. Set up an SDK as usual. Nothing is recorded without one.

```ts
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 [#spans]

Every repository operation gets a `CLIENT` span named after its
`db.query.summary`, for example `SELECT customers`. It follows the
[database semantic conventions](https://opentelemetry.io/docs/specs/semconv/database/)
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 [#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 [#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`.