mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 18:05:11 +03:00
<!-- CURSOR_AGENT_PR_BODY_BEGIN --> ## Stack Draft stack extracted from `docs/monitoring`. Merge bottom-up. The troubleshooting *catalog* rewrite (`content/troubleshooting` and the Diagnosing UI) stays out of scope. 1. #49503 move inspect and advisors 2. #49501 split Studio logs from ClickHouse queries 3. #49500 treat reports as signal dashboards 4. #49502 add Observe the data hub 5. #49506 add agent setup components 6. #49504 add hire-an-agent templates 7. **#49505** restructure observability nav, overview, Detecting, and flatten Observe the data ← **this PR** ## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Docs update. Top layer in the observability stack. ## What is the current behavior? The section is still titled Monitoring and Debugging, with a Debugging / Monitoring split that does not match the new pages. The debugging guide is still the master layer-isolation + symptom table. Observe the data is split into “what data” vs “where to observe it,” which duplicates the source pages. ## What is the new behavior? - Section title is Observability - Overview groups Observe the data, Detect and resolve, Hire an agent, and Export - **Observe the data is flattened by source.** Logs, Metrics API, Database, Advisors, and Reports each list where to read that source. There is no separate MCP/API/CLI/Studio nav group. - **Observe vs Detecting:** Observe is the catalog (what exists, how to access it). Detecting is how to *use* those sources to pick up a Health / Security / Performance / Usage signal. Named errors skip to Diagnosing. - Studio Logs sits under Logs. Reports sits beside the other sources. - Troubleshooting stays in the global menu and also appears as Diagnosing under Detect and resolve ## Additional context This is the last PR in the stack. Together the seven PRs reconstruct the `docs/monitoring` observability IA and guide content, without shipping the troubleshooting catalog overhaul. <!-- CURSOR_AGENT_PR_BODY_END --> <div><a href="https://cursor.com/agents/bc-a3cb5ece-925b-4046-b58a-5d69e9a9d794?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-a3cb5ece-925b-4046-b58a-5d69e9a9d794&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Saxon Fletcher <SaxonF@users.noreply.github.com> Co-authored-by: Nik Richers <nik@validmind.ai>
283 lines
18 KiB
Plaintext
283 lines
18 KiB
Plaintext
---
|
||
id: 'client-side-tracing'
|
||
title: 'Client-side tracing'
|
||
description: 'Propagate W3C trace context from the Supabase JS, Swift, and Dart SDKs through Supabase services'
|
||
---
|
||
|
||
The Supabase JS, Swift, Dart and Python SDKs can attach [W3C Trace Context](https://www.w3.org/TR/trace-context/) headers (`traceparent`, `tracestate`, `baggage`) to outgoing requests. The resulting `trace_id` flows through Supabase services and appears in API Gateway and Edge Function logs, so you can correlate client-side spans with the server-side logs they produced — end-to-end, across the network boundary.
|
||
|
||
Because the headers follow the W3C standard, any compliant tracing SDK (such as OpenTelemetry, Sentry, Datadog, or Honeycomb) can pick up the trace on the server side, including in self-hosted collectors. On the client side, some vendor SDKs need a small configuration change before they emit the standard headers — see [Using a vendor tracing SDK](#using-a-vendor-tracing-sdk) in the JavaScript tab.
|
||
|
||
<Tabs
|
||
scrollable
|
||
size="small"
|
||
type="underlined"
|
||
defaultActiveId="js"
|
||
queryGroup="language"
|
||
>
|
||
|
||
<TabPanel id="js" label="JavaScript">
|
||
|
||
## Requirements
|
||
|
||
- `@supabase/supabase-js` version `2.106.0` or later
|
||
- `@opentelemetry/api` available at runtime — either installed directly or pulled in as a transitive dependency of your tracing SDK
|
||
- A tracing SDK that registers a W3C-compliant propagator with the OpenTelemetry API
|
||
|
||
<Admonition type="caution">
|
||
|
||
As of `@supabase/supabase-js` version `2.112.0`, the OpenTelemetry integration lives in an opt-in subpath that you load once at your application entry point:
|
||
|
||
```ts
|
||
import '@supabase/supabase-js/tracing'
|
||
```
|
||
|
||
The main bundle contains no OpenTelemetry code — this import is what wires it up. The subpath imports `@opentelemetry/api` directly, so your bundler includes it and module resolution fails loudly if it isn't installed. If `tracePropagation` is enabled without this import, the SDK logs a one-time warning and sends requests without trace headers.
|
||
|
||
On versions `2.106.0`–`2.111.x`, the subpath doesn't exist — don't add the import there. Those versions load `@opentelemetry/api` dynamically and silently no-op when it's missing.
|
||
|
||
</Admonition>
|
||
|
||
Trace propagation isn't available through the CDN (UMD) build — there's no way to load the tracing runtime there.
|
||
|
||
## Set up OpenTelemetry first
|
||
|
||
The SDK reads from whatever `TracerProvider` you register globally — it doesn't configure one for you. If you haven't instrumented your app yet, follow the [OpenTelemetry JavaScript getting started guide](https://opentelemetry.io/docs/languages/js/getting-started/) to install an SDK (`@opentelemetry/sdk-trace-node` for Node, `@opentelemetry/sdk-trace-web` for browsers) and an exporter for your backend (OTLP, Jaeger, Zipkin, or a vendor-specific one).
|
||
|
||
The Supabase SDK only propagates the trace context that's already active when a request is made.
|
||
|
||
## Enable trace propagation
|
||
|
||
Trace propagation is opt-in and takes two steps: load the tracing runtime at your entry point (version `2.112.0` and later), and pass `tracePropagation: true` when creating the client:
|
||
|
||
```ts
|
||
import '@supabase/supabase-js/tracing'
|
||
|
||
import { trace } from '@opentelemetry/api'
|
||
import { createClient } from '@supabase/supabase-js'
|
||
|
||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY, {
|
||
tracePropagation: true,
|
||
})
|
||
|
||
const tracer = trace.getTracer('my-app')
|
||
|
||
await tracer.startActiveSpan('fetch-users', async (span) => {
|
||
// Outgoing request carries the active trace context.
|
||
const { data, error } = await supabase.from('users').select('*')
|
||
span.end()
|
||
})
|
||
```
|
||
|
||
For security, trace headers are only attached to requests targeting Supabase domains (`*.supabase.co`, `*.supabase.in`, and `localhost` for local development). Third-party hosts called through a custom `fetch` are never tagged.
|
||
|
||
<Admonition type="note">
|
||
|
||
Calling Edge Functions from the browser with trace propagation enabled requires the function's CORS allow-list to include the trace headers. In the function, import `corsHeaders` from `npm:@supabase/supabase-js@^2.112.3/cors` or add `traceparent`, `tracestate`, and `baggage` to your own allow-list, then redeploy the function. See [CORS support for Edge Functions](/docs/guides/functions/cors).
|
||
|
||
</Admonition>
|
||
|
||
## Advanced configuration
|
||
|
||
Pass an object instead of `true` for fine-grained control:
|
||
|
||
```ts
|
||
import '@supabase/supabase-js/tracing'
|
||
|
||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY, {
|
||
tracePropagation: {
|
||
enabled: true,
|
||
// Default: true. Non-sampled requests carry only `traceparent` (with the
|
||
// sampled flag preserved, so nothing is recorded downstream) — log
|
||
// correlation keeps working while `tracestate` and `baggage` are withheld.
|
||
// Set to false to always send the full trace context regardless of sampling.
|
||
respectSamplingDecision: false,
|
||
},
|
||
})
|
||
```
|
||
|
||
| Option | Type | Default | Description |
|
||
| ------------------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| `enabled` | `boolean` | `false` | Enable trace propagation. |
|
||
| `respectSamplingDecision` | `boolean` | `true` | When `true`, non-sampled requests send only `traceparent` (sampled flag preserved) and omit `tracestate` and `baggage`; `false` always sends the full trace context. On versions before `2.112.3`, `true` skipped all trace headers for non-sampled requests. |
|
||
|
||
## Using a vendor tracing SDK
|
||
|
||
Many tracing SDKs are built on top of OpenTelemetry, but they differ in whether their propagator emits the standard `traceparent` header by default:
|
||
|
||
| Vendor setup | Works with `tracePropagation`? | Required configuration |
|
||
| --------------------------------------------------------------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| OpenTelemetry SDK (also Honeycomb, Grafana, New Relic via OTLP) | Yes | None — the W3C propagator is the default |
|
||
| Sentry (Node.js, including Next.js server-side) | Yes, with one flag | Set `propagateTraceparent: true` in `Sentry.init()` — Sentry's propagator omits `traceparent` by default |
|
||
| Sentry (browser) | Via Sentry's own instrumentation | Set `propagateTraceparent: true` and add your project URL (`https://<ref>.supabase.co`) to `tracePropagationTargets` — Sentry's browser SDK only attaches headers cross-origin for listed targets. For Edge Functions, also add `sentry-trace` to the function's CORS allow-list: Sentry always sends its own header, and it isn't part of `corsHeaders` |
|
||
| Datadog `dd-trace` (Node.js) | Out of the box | None — `dd-trace` injects W3C headers at the HTTP layer itself, even without `tracePropagation` |
|
||
| Datadog Browser RUM | Yes, with configuration | Add your project URL to `allowedTracingUrls` with the `tracecontext` propagator type |
|
||
|
||
If a propagator is active but doesn't emit `traceparent`, the SDK logs a one-time console warning naming the headers the propagator wrote (version `2.112.3` and later).
|
||
|
||
## Troubleshooting
|
||
|
||
The SDK never throws when it can't propagate, which keeps it safe to enable but can mask configuration issues. If `trace_id` is missing from your Supabase logs, check these in order:
|
||
|
||
- **The tracing runtime isn't loaded** (version `2.112.0` and later). `tracePropagation` is enabled but your entry point never imports `@supabase/supabase-js/tracing`. The SDK logs a one-time console warning and sends requests without trace headers — look for that warning in your console.
|
||
- **No active span at request time.** The SDK reads the _current_ context. If `supabase.from(...)` is called outside `tracer.startActiveSpan(...)` (or equivalent), there's nothing to propagate. Wrap the call in a span or use OpenTelemetry's automatic instrumentation.
|
||
- **`@opentelemetry/api` is not installed** in the app making the request. On `2.112.0` and later the tracing subpath imports it directly, so a missing package surfaces as a module resolution error. On `2.106.0`–`2.111.x` it's loaded dynamically and the SDK silently no-ops.
|
||
- **No `TracerProvider` registered.** `@opentelemetry/api` defaults to a noop provider that produces non-recorded spans. Ensure your app calls `provider.register()` (or your vendor SDK's equivalent) before making requests.
|
||
- **Your tracing SDK's propagator doesn't emit W3C `traceparent`.** Sentry's propagator, for example, only emits it when `propagateTraceparent: true` is set. From version `2.112.3` the SDK logs a one-time warning naming the headers the propagator wrote — see [Using a vendor tracing SDK](#using-a-vendor-tracing-sdk).
|
||
- **The upstream trace is not sampled** (versions before `2.112.3`). Older versions skip all trace headers when the upstream trace is not sampled. From `2.112.3`, non-sampled requests still carry `traceparent`, so log correlation keeps working by default. Set `respectSamplingDecision: false` to always send the full trace context.
|
||
- **You're calling a non-Supabase host through a custom `fetch`.** Trace headers are only attached to Supabase domains (`*.supabase.co`, `*.supabase.in`, `localhost`).
|
||
- **You're using the CDN (UMD) build.** Trace propagation isn't available there — the tracing runtime can't be loaded from a script tag.
|
||
|
||
</TabPanel>
|
||
|
||
<TabPanel id="swift" label="Swift">
|
||
|
||
Requires `supabase-swift` `2.51.0` or later and `swift-tools-version: 6.1` or later (SwiftPM trait support).
|
||
|
||
1. **Add the `OpenTelemetry` trait** to your dependency declaration in `Package.swift`:
|
||
|
||
```swift
|
||
// Package.swift
|
||
.package(
|
||
url: "https://github.com/supabase/supabase-swift.git",
|
||
from: "2.51.0",
|
||
traits: ["OpenTelemetry"]
|
||
)
|
||
```
|
||
|
||
No changes to `SupabaseClient` are required. After enabling the trait, the active OpenTelemetry span's trace context is automatically injected as a `traceparent` header on every outgoing request across PostgREST, Storage, Auth, Functions, and Realtime. When there is no active span, the header is not added.
|
||
|
||
2. **Register a `TracerProvider`** at app start. The SDK reads from whatever provider you register globally:
|
||
|
||
```swift
|
||
import Supabase
|
||
import OpenTelemetryApi
|
||
import OpenTelemetrySdk
|
||
|
||
let exporter = /* your OTLP / Jaeger / Zipkin exporter */
|
||
let spanProcessor = SimpleSpanProcessor(spanExporter: exporter)
|
||
let provider = TracerProviderBuilder()
|
||
.add(spanProcessor: spanProcessor)
|
||
.build()
|
||
OpenTelemetry.registerTracerProvider(tracerProvider: provider)
|
||
```
|
||
|
||
3. **Create your `SupabaseClient`**. Any active span is now propagated automatically:
|
||
|
||
```swift
|
||
let supabase = SupabaseClient(
|
||
supabaseURL: URL(string: "https://xyzcompany.supabase.co")!,
|
||
supabaseKey: "your-publishable-key"
|
||
)
|
||
```
|
||
|
||
</TabPanel>
|
||
|
||
<TabPanel id="dart" label="Dart">
|
||
|
||
Requires `supabase` `2.x` or later (Flutter or Dart-only).
|
||
|
||
1. **Implement a `traceContextProvider`** that returns the current `TraceContext` from your tracing library. Return `null` when there is no active span.
|
||
|
||
2. **Pass `TracePropagationOptions`** when creating the client:
|
||
|
||
```dart
|
||
import 'package:supabase/supabase.dart';
|
||
|
||
final supabase = SupabaseClient(
|
||
'https://xyzcompany.supabase.co',
|
||
'your-publishable-key',
|
||
tracePropagationOptions: TracePropagationOptions(
|
||
enabled: true,
|
||
traceContextProvider: () {
|
||
final span = YourTracer.activeSpan;
|
||
if (span == null) return null;
|
||
return TraceContext(
|
||
traceparent: span.traceparent,
|
||
tracestate: span.tracestate,
|
||
);
|
||
},
|
||
),
|
||
);
|
||
```
|
||
|
||
For `supabase_flutter`, pass the same option through `Supabase.initialize`:
|
||
|
||
```dart
|
||
await Supabase.initialize(
|
||
url: 'https://xyzcompany.supabase.co',
|
||
anonKey: 'your-publishable-key',
|
||
tracePropagationOptions: TracePropagationOptions(
|
||
enabled: true,
|
||
traceContextProvider: () => yourTraceContextProvider(),
|
||
),
|
||
);
|
||
```
|
||
|
||
## Options
|
||
|
||
| Option | Type | Default | Description |
|
||
| ------------------------- | ----------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| `enabled` | `bool` | `false` | Enable trace propagation. |
|
||
| `respectSamplingDecision` | `bool` | `true` | When `true`, skips propagation if the upstream trace is not sampled. Set to `false` to always attach a `trace_id` — useful for log correlation even when traces are not exported. |
|
||
| `traceContextProvider` | `TraceContextProvider?` | `null` | Callback returning the current `TraceContext`. Return `null` when there is no active span. |
|
||
|
||
Headers are only injected on requests targeting Supabase hosts (`*.supabase.co`, `*.supabase.in`, your project host, and loopback addresses for local development). Third-party hosts never receive trace headers.
|
||
|
||
</TabPanel>
|
||
|
||
<TabPanel id="python" label="Python">
|
||
|
||
The Python `opentelemetry` propagation is handled entirely through the `opentelemetry-instrumentation-httpx` package.
|
||
|
||
1. **Add** the `opentelemetry-sdk` and `opentelemetry-instrumentation-httpx` package:
|
||
|
||
```sh
|
||
uv add opentelemetry-sdk opentelemetry-instrumentation-httpx
|
||
```
|
||
|
||
2. **Instrument** the `httpx` client using the `HTTPXClientInstrumentor`:
|
||
|
||
```python
|
||
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
|
||
HTTPXClientInstrumentor().instrument()
|
||
```
|
||
|
||
<Admonition type="note">
|
||
|
||
This will instrument all `httpx` clients in your process. If you want to instrument only the Supabase client, you can use `HTTPXClientInstrumentor.instrument_client` in the specific sub-package client you want to trace.
|
||
|
||
</Admonition>
|
||
|
||
3. **Create** your `SupabaseClient`. Any active span is now propagated automatically:
|
||
|
||
```python
|
||
from opentelemetry import trace
|
||
from opentelemetry.sdk.trace import TracerProvider
|
||
|
||
from supabase import AsyncClient
|
||
|
||
trace.set_tracer_provider(TracerProvider())
|
||
tracer = trace.get_tracer(__name__)
|
||
|
||
async def query(client: AsyncClient):
|
||
with tracer.start_as_current_span("orchestral_query") as span:
|
||
await client.table("orchestral_sections") \
|
||
.select("name, instruments(name)") \
|
||
.order("name", desc=True, foreign_table="instruments") \
|
||
.execute()
|
||
```
|
||
|
||
</TabPanel>
|
||
|
||
</Tabs>
|
||
|
||
## Correlating with Supabase logs
|
||
|
||
After trace context is flowing through, the `trace_id` appears in:
|
||
|
||
- **API Gateway logs** — every request to PostgREST, Auth, Storage, and Realtime
|
||
- **Edge Function logs** — invocations and any structured logs emitted from within the function
|
||
|
||
If you forward Supabase logs to a third-party backend via [Log Drains](/docs/guides/observability/log-drains), you can join Supabase logs to your own client and server traces using the shared `trace_id`. This is especially useful for self-hosted setups where you already operate your own OpenTelemetry collector — Supabase logs become first-class citizens in your existing tracing UI.
|