Correlation with Data
Correlate what a real user did in the browser with the exact backend work your services performed, and the log line(s) those services emitted using one continuous context. This page walks you through a RUM session → the backend trace → the attached logs, explaining what each panel means and how to read it confidently.
What you get
- A single request “thread” across browser, services, and logs (via
<trace_id>/<span_id>). - Visual timing (flame graph) to spot where time is actually spent.
- Request/response and headers to validate behaviour and caching.
- Log details to confirm status, payload sizes, and errors.
Prerequisites#
- RUM SDK initialised with tracing enabled, so network events carry trace context. What that takes differs by platform — see Platform support below.
- Backend services instrumented for tracing and configured to propagate W3C Trace Context (
traceparent/tracestate) through gateways/queues. - Logging is configured to enrich log events with
<trace_id>and<span_id>(so the Logs tab can stitch them automatically).
Platform support#
Correlation itself is platform‑agnostic: Middleware joins a RUM event to a backend trace whenever they share a <trace_id>. What differs is how much setup each SDK needs before its requests carry that ID.
| Platform | HTTP events | Trace propagation | What you have to do |
|---|---|---|---|
| Browser | Automatic | Every URL by default | Nothing |
| iOS | Automatic (URLSession) | Every URL by default | Nothing |
| React Native | Automatic (fetch/XHR) | Every URL by default | Nothing |
| Android | Manual | Every URL, once instrumented | Route all traffic through createRumOkHttpCallFactory(...) |
Android is the one platform that needs work before correlation happens, and it fails silently: sessions look healthy, but no HTTP events are recorded and no backend trace ever resolves. The SDK logs a warning at startup if no client was wrapped. Follow the Android link above for the fix.
On every platform you can narrow which hosts receive trace headers, so your trace IDs stay off third‑party APIs. See the platform page for the option name.
Short Answer: it’s close, but we can make the “what” and the “how” completely unmistakable by adding two small, explicit sections at the top. Below are drop-in blocks you can paste into your page. They keep your placeholders and reference your screenshots.
What is “correlation” in RUM?#
Correlation means stitching a single user action in the browser (a RUM event such as an XHR/Fetch) to the exact backend trace that handled it and to the log entries those services emitted so that you can follow one request end-to-end.
Middleware achieves this by propagating and storing shared identifiers, primarily <trace_id> and <span_id>, across RUM, tracing, and logging. When these IDs match, you are looking at the same request across all three data types.
How Middleware stitches RUM → Traces → Logs#
- Client (RUM SDK) — browser, iOS, Android, or React Native
- Instruments the app's HTTP client and starts/joins a distributed trace. See Platform support for which SDKs do this automatically.
- Attaches W3C Trace Context headers (
traceparent,tracestate) carrying<trace_id>/<span_id>on the network request. - Captures browser timings and attributes (e.g.,
resource.duration,http.url,browser.name) as well as the session ID (session.id). - Sends the RUM event with these fields to Middleware.
- Edge / Service (Tracing)
- Your tracing library extracts the incoming context from
traceparent. - It continues the same trace and creates server spans (e.g.,
app-api-v2span), preserving<trace_id>and generating child<span_id>values. - Spans are sent to the Middleware’s trace store.
- Your tracing library extracts the incoming context from
- Service Logging (Logs)
- Your logger is enriched to include
<trace_id>/<span_id>on every relevant log event (request logs, errors, DB warnings, etc.). - Logs are shipped to Middleware and indexed by these IDs.
- Your logger is enriched to include
- Index & Link
- Middleware indexes RUM events, traces, and logs on
<trace_id>(and also onsession.id/time). - From a RUM event, the UI can unambiguously fetch the trace and the logs that share the duplicate IDs.
- Middleware indexes RUM events, traces, and logs on
Start from the RUM Session (front end)#
Open RUM → Sessions and select a session that interests you (e.g., a user reporting slowness). In the left pane, choose Events. Filter for Type: xhr and click the network event (e.g., GET /api/v1/account/init).
You’re selecting the exact browser request that the user’s page fired. From here, Middleware pivots you to the backend view for this very request.

What you see on the right:
- Flame Graph (top): stacked spans across services that handled this request.
- Services panel / Exec time % (right): Which service took the most time.
- Tabs (bottom): Trace Attributes, Events, Logs, Headers, Request, Response.
Why it matters:
- This is already filtered to the same request your browser made. No manual search across products; you’re looking at one stitched timeline.
Read the Trace (How to Interpret the Panels)#
1. The Flame Graph#
- Each bar is a span; its width is duration.
- Bars stack to show parent/child work (e.g., frontend gateway →
app-api-v2→ DB). - Zoom and hover to see where the latency sits. Long, wide bars are hot spots.
How to use it
- If the top bar is the HTTP handler and a child DB call dominates, you’ve found the bottleneck.
- If most of the time is spent before any server span, it’s likely due to network/TTFB/CDN issues.
2. Service Breakdown (Exec time %)#
- A quick at-a-glance ranking of which service is responsible for most of the end-to-end time.
3. Trace Attributes (context you’ll rely on)#
Open the Trace Attributes tab (it’s usually selected). You’ll see a merged view of browser and server attributes. Typical high-value fields:
| Attribute | Meaning | Example |
|---|---|---|
event.type | Browser event type | xhr |
ttp.method, http.url, http.status_code | Core request data | GET https://<host>/api/v1/account/init → 200 |
traceId, spanId | Correlation keys across RUM, trace, and logs | <trace_id>, <span_id> |
browser.trace | Browser tracing enabled | true |
resource.duration | Browser-observed total resource time | 369.3 ms |
resource.first_byte.duration | Time to first byte from server/CDN | 366.5 ms |
resource.download.duration | Time to read the response | 2.8 ms |
resource.size / resource.transfer_size | Uncompressed vs on-wire size | 14708 B / 5716 B |
http.response_content_length | Server-reported length | 5416 B |
project.name, mw.agent_id | Project/agent identity | <project_name>, <agent_id> |
browser.name/version, os | Client environment | Chrome 138, Linux |
cf-*, email, name | (Optional) geo/user fields | <city>, <country>, <email>, <user_name> |
Anchor the correlation on IDs: Write down <trace_id> and <span_id> from here; you’ll confirm them again in the Logs view. If they match, you’re unquestionably looking at the same request from browser → services → logs.
Jump to Logs (same request, same IDs)#
Click the Logs tab in the same right-hand inspector. You’ll see log entries scoped to this trace/span, no extra filters needed.

Open a log row to inspect its structured fields.

Key fields you’ll read here:
| Field | Why it matters | Example |
|---|---|---|
service.name | Which service emitted the log | app-api-v2 |
method, path, response_status | Confirms HTTP behaviour | GET, /api/v1/account/init, 200 |
duration | Service-reported time | 24 ms |
traceId / spanId | Must match the Trace view | <trace_id> / <span_id> |
response-body-size | What the service sent | 0 B / 14 K |
ip, user_id | Origin context (if logged) | <ip>, <user_id> |
project.name, mw_agent, telemetry.sdk.language | Runtime & agent metadata | <project_name>, true, go |
Sanity check: If the traceId here doesn’t match the Trace Attributes, your gateway/worker likely started a new trace. Ensure trace context headers are forwarded end-to-end.
Validate Payloads and Headers#
Use the Request, Response, and Headers tabs to confirm:
- Auth headers present/valid; cache headers as expected.
- Response status/text and content length match what the browser saw.
- Mismatch examples to look for:
- Server
durationis small butresource.first_byte.durationis large → network/CDN or queueing before your app. - Response length differs between server and browser fields → compression or proxy mutation (expected with HTTP/2 + gzip/brotli).
- Server
Field Glossary (front-end timings)#
These fields are reported by the browser and help you understand “time outside your code”:
resource.connect.duration: TCP connect time.resource.ssl.duration: TLS handshake time.resource.first_byte.duration: Time until first byte (server + network + intermediaries).resource.download.duration: Time to read response from socket.resource.duration: Sum of the above (roughly connect → download).
Tip: Compare resource.first_byte.duration with the service log duration. If first_byte ≫ service duration, most latency happened before your application code returned (CDN, WAF, upstream, or network).
Troubleshooting Correlation#
- No logs under the trace
- Ensure your logger attaches
<trace_id>/<span_id>and that log ingestion is enabled for the service. - Check sampling and log levels; the event might be filtered out.
- Ensure your logger attaches
- IDs don’t match between trace and logs
- A proxy/queue/async worker can start a new trace. Forward
traceparentheaders and bind server spans to incoming context.
- A proxy/queue/async worker can start a new trace. Forward
- The browser shows a long TTFB, but the service time is small
- Investigate CDN/WAF, upstreams, or network segments. Use headers (age, via, server-timing) to attribute the gap.
- No
browser.trace- Verify RUM init options; ensure CSP/ad-blockers aren’t disabling XHR/fetch instrumentation.
Need assistance or want to learn more about Middleware? Get in touch with us via our Contact Us or join our Slack channel.
What did you think of this content?