feat(browser): request/response headers on spans, replay bodies and canvas - #1157
Conversation
…anvas - tracing.captureHeaders records allowlisted headers on fetch/XHR spans as the HTTP semconv attributes http.request.header.<name> / http.response.header.<name> (string arrays). authorization, proxy-authorization, cookie and set-cookie are never recorded, even when listed. XHR spans get response headers only. - replay.networkBodies keeps text/JSON request and response bodies of the listed URLs on the replay's network events (bodies have no semconv attribute, so they stay off spans), cut to maxLength (default 10,000). The response is read from a clone in the background, so the app gets it untouched and unwaited. Nothing is captured with privacy.maskAllText. - replay.canvasFps records <canvas> content as WebP frames through rrweb's canvas capture, in both the streaming and buffered recorders. Off by default. Eager budget 43.5 -> 44 kB, first-party 18 -> 18.5 kB for the header hooks.
|
Warning Review limit reachedNext included review available in 54 minutes. View limit detailsLimit details: You’ve used all 4 included reviews currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (17)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Maple reviewConfidence 3/5 · needs attention Adds opt-in request/response header attributes on fetch/XHR spans, text bodies on replay network events, and rrweb canvas frames. The mechanics are sound and tested; canvas capture is the one path that ignores the masking rule the bodies path honours.
FindingsWarning · F1 · Canvas capture ignores
|
Canvas frames are pixels, so text drawn into a canvas (chart labels, grids) reached Maple even when the app asked for all text masked. canvasFps is now ignored with maskAllText.
|
Note A newer push replaced |
…hole - Response bodies were read in full to keep maxLength characters; the cloned stream is now read until maxLength and cancelled. - Header values were split on commas, mangling values like date and cache-control. Each is one array entry; the browser has already joined repeated headers.
|
Note A newer push replaced |
…etwork-canvas # Conflicts: # packages/browser/scripts/size.ts
Maple reviewConfidence 3/5 · needs attention Adds allowlisted request/response headers on fetch/XHR spans, opt-in body capture on replay network events, and opt-in rrweb canvas frames. Both earlier findings are fixed; two data-fidelity issues remain, neither breaking.
FindingsWarning · F4 ·
|
| Change | Kind | Observable | Evidence |
|---|---|---|---|
| fetch/XHR request and response header capture | span attributes | yes | packages/browser/src/tracing.ts:267-289 sets http.request.header.* / http.response.header.* on the instrumentation span |
| replay network body capture | session event attributes | yes | replay/capture/network.ts:26-32 puts bodies on the network session event; off spans by design |
| canvas frame capture | replay recording | yes | replay/record.ts:266-274 enables rrweb recordCanvas in both recorders |
Copy all findings (2)
Findings from an automated review of commit b536f891793cfd8b196386ec8a7a405fe977f178. Verify each one against the current code before changing anything, fix only those that still apply, and keep each fix to the lines it names.
---
F4 · Warning · correctness · packages/browser/src/config.ts:302
`networkBodies` bodies past 1,024 bytes are dropped by ingest
A body is cut to `maxLength` (default 10,000) in the browser, then sent as the `request.body` / `response.body` attribute of a session event — and `/v1/sessionEvents` clamps every attribute value to `SESSION_EVENT_MAX_ATTRIBUTE_VALUE_BYTES` = 1,024 bytes (`apps/ingest/src/session_analytics.rs:67`, called from `apps/ingest/src/main.rs:3419`). A listed endpoint's body therefore arrives cut at ~1 KB while the docs promise `maxLength` characters, and any `maxLength` above 1,024 buys nothing but upload. Default to 1_024 and document the gateway cap.
Suggested fix: Set the default `maxLength` to `1_024` so the SDK cuts where the gateway would, and say so in the `networkBodies` doc comment and the `docs/browser-sdk.md` section (or raise the ingest cap if 10 KB bodies are wanted).
---
F5 · Note · correctness · packages/browser-session/src/replay/capture/network.ts:8-9
A `g` RegExp in `networkBodies.urls` matches only every other request
`matchesUrl` calls `pattern.test(url)` on the app-supplied `RegExp`, and a `g` (or `y`) regex keeps `lastIndex` between calls: with `urls: [/\/api\//g]` the first matching request is captured and the next one to a matching URL is silently not, and so on. Reset `lastIndex` before each test.
Replace those lines with:
const matchesUrl = (url: string, patterns: ReadonlyArray<string | RegExp>): boolean =>
patterns.some((pattern) => {
if (typeof pattern === "string") return url.includes(pattern)
// A `g`/`y` regex is stateful: `test` advances `lastIndex`, so reset it first.
pattern.lastIndex = 0
return pattern.test(url)
})
b536f89 · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple to ask about one.
…gexes - /v1/sessionEvents keeps 1,024 bytes of an attribute, so a 10,000 character body arrived cut to ~1 KB anyway. maxLength now defaults to and is capped at 1,000 characters (room for the cut marker), and the docs say so. Reading the body stops there too. - A g/y RegExp in networkBodies.urls matched only every other request; lastIndex is reset before each test.
Maple reviewConfidence 3/5 · needs attention Adds allowlisted fetch/XHR headers as HTTP semconv span attributes, optional text bodies on replay network events, and opt-in rrweb canvas capture. Safe to merge apart from the deferred network event on streamed responses.
FindingsWarning · F6 · Fetch network event is deferred to the body read, so a streamed response records nonecorrectness · When Fixed since the last review
What was checked
Observability coverage: 1 of 2 changes observable
Copy all findings (1)
|
| const requestBody = typeof init?.body === "string" ? init.body : undefined | ||
| const done = performance.now() | ||
| const contentType = res.headers.get("content-type") ?? "" | ||
| void ( |
There was a problem hiding this comment.
Fetch network event is deferred to the body read, so a streamed response records none
F6 · Warning · correctness
When networkBodies matches the URL, record runs only inside the .then after readPrefix, which loops until the accumulated text exceeds maxLength (1,000 by default) and awaits reader.read() in between (network.ts:157). A text/event-stream or long-poll response that stays open without delivering 1,000 characters never resolves the read, so the request never appears in the replay network panel at all — before this change the event was recorded as soon as the fetch settled. TEXT_CONTENT matches text/event-stream because of its text/ prefix.
Emit the network event as soon as the response settles and attach the bodies only to the read that has finished, or exclude `text/event-stream` from `TEXT_CONTENT` so streams take the plain path.
Prompt for an AI agent
In `packages/browser-session/src/replay/capture/network.ts:61-78`: Fetch network event is deferred to the body read, so a streamed response records none.
When `networkBodies` matches the URL, `record` runs only inside the `.then` after `readPrefix`, which loops until the accumulated text exceeds `maxLength` (1,000 by default) and awaits `reader.read()` in between (network.ts:157). A `text/event-stream` or long-poll response that stays open without delivering 1,000 characters never resolves the read, so the request never appears in the replay network panel at all — before this change the event was recorded as soon as the fetch settled. `TEXT_CONTENT` matches `text/event-stream` because of its `text/` prefix.
Suggested fix: Emit the network event as soon as the response settles and attach the bodies only to the read that has finished, or exclude `text/event-stream` from `TEXT_CONTENT` so streams take the plain path.
Verify the problem exists at that location before changing it, and keep the fix to those lines.
Part 11 of the browser SDK stack. Based on the offline queue PR.
What changes
tracing.captureHeadersrecords allowlisted headers on fetch/XHR spans as the HTTP semconv attributeshttp.request.header.<name>/http.response.header.<name>(string arrays).authorization,proxy-authorization,cookieandset-cookieare never recorded, even when listed.replay.networkBodieskeeps text/JSON request and response bodies of the listed URLs on the replay's network events.maxLength(default 10,000).privacy.maskAllText.@maple/browser-session's network capture, so the Effect SDK can adopt it later.replay.canvasFpsrecords<canvas>content as WebP frames through rrweb's canvas capture, in both the streaming and buffered recorders. Off by default.Eager budget 43.5 → 44 kB, first-party 18 → 18.5 kB for the header hooks, which must live in the instrumentation config.
Follow-up
The session replay network panel doesn't render
attrsbodies yet; the data is stored on the session event rows.Testing
authorizationdoesn't.canvasFpsis set.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.