Skip to content

Trace Context on HTTP Responses

Every SignalDB HTTP response returns the trace context of the server span that handled the request, plus server-side timing. This lets you correlate your own telemetry (browser RUM, an instrumented client) with SignalDB's trace of the same request, and see where server time went without opening a trace viewer.

The one exception, in the other direction, is the explore UI's own document request: it carries trace context inline in the HTML body, not just headers — see Trace context in the document body below.

Headers

For a request that SignalDB traced (self-monitoring enabled), the response carries:

Server-Timing: traceparent;desc="00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", querier;dur=12.480, convert;dur=0.312, total;dur=14.102
traceresponse: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
Timing-Allow-Origin: *
Header / entry Meaning
Server-Timing: traceparent;desc="..." W3C trace context of SignalDB's server span. The span id is the server's own span, not the caller's. Browsers expose this via the Performance API even for requests JavaScript never made (the HTML document, scripts, images).
traceresponse The same value as a W3C Trace Context Level 2 header, for standards-compliant clients.
<stage>;dur=<ms> Server-side stage timings in milliseconds. total is always present; query endpoints add stages (e.g. querier for the Flight round-trip, convert for result conversion).
Timing-Allow-Origin: * Lets cross-origin pages (e.g. a Grafana instance) read the serverTiming performance entries.

The final -01/-00 field reflects SignalDB's sampling decision: 01 means the server span was sampled and will appear in the self-monitoring trace store; 00 means it was sampled out.

No headers are emitted when self-monitoring tracing is disabled, or for _system tenant requests (SignalDB's own telemetry traffic).

Handlers accumulate the <stage>;dur=<ms> entries via ServerTimings (src/common/src/self_monitoring/app_metrics.rs), a small ordered name/duration list unrelated to the counter/histogram instruments the same file defines — those are the signaldb.* metrics (WAL — including the signaldb.wal.instance_cap_hits cap counter — Flight and RPC counters, histograms and gauges) exported over OTLP, not part of the response headers described here.

Correlating a request with its trace

If your client already sends a traceparent request header, SignalDB joins your trace: the returned trace id equals the one you sent, so your existing trace contains the server's spans. The response header is then confirmation — if the returned trace id differs from what you sent, an intermediary stripped your header.

If your client cannot send traceparent — above all the browser's initial document request — the response header is a fallback way to correlate: the SignalDB UI reads the navigation entry's serverTiming traceparent metric and links its documentLoad span to the server span that served the page. This is only a fallback because the explore UI has a more reliable mechanism — see the next section.

To find a request's trace by hand: copy the trace id from the traceresponse header (visible in browser DevTools → Network → Headers, or curl -sD -) and look it up in the trace view of the self-monitoring tenant.

Trace context in the document body

The Server-Timing/traceresponse headers above cover every SignalDB response except one: the explore UI's own index.html. Its serverTiming performance entry is only reliably populated after the navigation finishes — by which point the browser has already created its documentLoad span, and a SpanProcessor cannot change a span's parent after creation (see src/ui/src/telemetry/serverCorrelationSpanProcessor.ts).

So for index.html specifically, the router (serve_index_html in src/router/src/ui.rs) injects the trace context directly into the response body, ahead of </head>:

<meta
  name="traceparent"
  content="00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"
/>

The UI reads this tag before any spans exist (documentTraceParentContext() in src/ui/src/telemetry/documentTraceContext.ts) and uses it as the real parent of the documentLoad span — not a link, unlike every other correlation described on this page. That is a deliberate trade-off: it makes documentLoad a genuine child of the router's request span (so span navigation, not just a same-trace-id link, works between them), at the cost that OpenTelemetry JS's default ParentBasedSampler drops the whole documentLoad subtree whenever the server's span was sampled out (traceparent flags 00) — the browser accepts its parent's sampling decision, including "don't record this". A busier self-monitoring sampler ratio therefore now also thins out browser RUM data for the page loads it lands on, not just server spans.

No tag is emitted when self-monitoring is disabled entirely (no active span to read), and the UI falls back to the Server-Timing link described above when the tag is absent — an older router build, or a dev proxy serving index.html directly without going through the router.

Reading timings without any tooling

curl -sD - -o /dev/null \
  -H "Authorization: Bearer $API_KEY" -H "X-Tenant-ID: acme" \
  "https://signaldb.example.com/tempo/api/traces/0af7651916cd43dd8448eb211c80319c" \
  | grep -i -E "server-timing|traceresponse"

Browser DevTools render Server-Timing graphically: Network panel → select a request → Timing tab.