Skip to content

Link errors across tiers with traces

Turn on tracePropagation so browser errors, server errors and job runs from one request show up together under “Same trace”.

Markdown
On this page

One click in the browser can end in an error three tiers away: a server function, then a Worker, then a job. With trace propagation on, every error and job run caused by that click carries the same trace id, and Belvador shows them together under Same trace on each issue and on the Trace page.

This is linking, not performance tracing: the Belvador SDKs create no spans and no timings per tier — only which errors and runs share a trace. For spans and timings, send OpenTelemetry traces; the option below makes your errors line up with them.

Turn it on#

Available from @belvador/* 0.3.0. It is off by default, because adding headers to requests can trigger CORS checks on APIs you do not control.

Browser and React#

TypeScript
import { init } from "@belvador/browser"; // or @belvador/reactinit({  dsn: "https://<public-key>@ingest.belvador.dev/<project-id>",  // Only your own APIs. Every other URL is left alone.  tracePropagation: { targets: ["/api/", "https://api.example.com/"] },});

TanStack Start#

src/router.tsx
import { init } from "@belvador/tanstack-start/client";if (typeof window !== "undefined") {  init({    dsn: "https://<public-key>@ingest.belvador.dev/<project-id>",    // Same-origin server functions and routes; the server SDK continues the trace.    tracePropagation: { targets: ["/_serverFn/", "/api/"] },  });}

targets lists the URLs that get trace headers: a string matches any URL containing it (and, in the browser, the path of same-origin requests); a RegExp is tested against the URL. Only list your own APIs. An empty list turns propagation off.

The server packages (@belvador/tanstack-start/server, @belvador/cloudflare) continue an incoming trace automatically: an error or withMonitor run during that request carries the caller's trace id. Workers also forward headers on their own fetch calls when you set tracePropagation in the withBelvador options.

With OpenTelemetry#

If the app already runs OpenTelemetry, let OpenTelemetry own the trace and give Belvador its API. An error captured while a span is active then carries that span's trace id and span id, so the issue opens on the right span of the trace waterfall:

src/index.ts
import * as api from "@opentelemetry/api";import { withBelvador } from "@belvador/cloudflare"; // same option in every @belvador/* initexport default withBelvador(  (env: { BELVADOR_DSN?: string }) => ({    dsn: env.BELVADOR_DSN ?? "https://<public-key>@ingest.belvador.dev/<project-id>",    // The same @opentelemetry/api copy your OpenTelemetry setup uses.    otel: { api },  }),  handler,);
  • Pass the module itself (import * as api from "@opentelemetry/api") — the copy your OpenTelemetry SDK registered its provider with. Belvador never imports it, never creates spans and never registers a provider.

  • The ids come from the span that is active when the error is captured: captureException inside your span, or an error the framework reports before the span ends. An error that escapes the span and is reported later by the outer handler keeps the request's trace id without that span.

  • With otel set, tracePropagation is ignored (OpenTelemetry already sends traceparent), and the SDK reports the diagnostic BLV_TRACE_OWNER_OTEL until you remove it.

  • Available from @belvador/* 0.4.0.

Headers sent#

HeaderSentContent
traceparentAlways (W3C Trace Context)00-<trace id>-<span id>-00
sentry-traceAlways<trace id>-<span id>
baggageOnly with baggage: trueOnly the SDK's own sentry-* keys

Requests to Belvador's own ingest are never given trace headers.

CORS#

If the API is on another origin, its CORS preflight must allow the new headers, or the browser blocks the request:

Text
Access-Control-Allow-Headers: traceparent, sentry-trace# only when you also turn on baggage:Access-Control-Allow-Headers: traceparent, sentry-trace, baggage

Check this before turning propagation on in production: a missing header in Access-Control-Allow-Headers makes the request fail, not only the trace.

Reading a trace#

  • On an issue, Same trace lists up to 200 errors and runs with the same trace id within ±24 hours of that event, across the projects of your workspace you can see.

  • Open trace shows them in time order. You can paste a trace id or a whole traceparent header into ⌘K to open it.

  • A trace only joins data inside one workspace. A request into another workspace's app shows up there as a separate piece.

An event without a trace id says so on the issue; that is normal for errors that did not come from a propagated request.

Posts on this topic

All posts

Spotted something wrong or missing? Tell us: hello@belvador.dev