feat(browser): Emit web vitals as streamed spans#19827
feat(browser): Emit web vitals as streamed spans#19827logaretm wants to merge 3 commits intolms/feat-span-firstfrom
Conversation
size-limit report 📦
|
8bf8eaf to
c966a4a
Compare
ce4aa43 to
1a5cfb3
Compare
…NTRY_MAP Add `addFcpInstrumentationHandler` using the existing `onFCP` web-vitals library integration, following the same pattern as the other metric handlers. Export `INP_ENTRY_MAP` from inp.ts for reuse in the new web vital spans module. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…is enabled Add non-standalone web vital spans that flow through the v2 span streaming pipeline (afterSpanEnd -> captureSpan -> SpanBuffer). Each web vital gets `browser.web_vital.<metric>.value` attributes and span events for measurement extraction. Spans have meaningful durations showing time from navigation start to the web vital event (except CLS which is a score, not a duration). New tracking functions: trackLcpAsSpan, trackClsAsSpan, trackInpAsSpan, trackTtfbAsSpan, trackFcpAsSpan, trackFpAsSpan — wired up in browserTracingIntegration.setup() when hasSpanStreamingEnabled(client). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add Playwright integration tests verifying CLS, LCP, FCP, FP, and TTFB are emitted as streamed spans with correct attributes, value attributes, and meaningful durations when span streaming is enabled. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1a5cfb3 to
28c0d45
Compare
Semver Impact of This PR🟡 Minor (new features) 📋 Changelog PreviewThis is how your changes will appear in the changelog. New Features ✨
Bug Fixes 🐛Deps
Other
Internal Changes 🔧
🤖 This preview updates automatically when you update the PR. |
| export function trackTtfbAsSpan(client: Client): void { | ||
| addTtfbInstrumentationHandler(({ metric }) => { | ||
| _sendTtfbSpan(metric.value, client); | ||
| }); | ||
| } |
There was a problem hiding this comment.
Bug: The trackTtfbAsSpan function immediately emits a TTFB span during setup() using a cached metric value, before the parent pageload span is created in afterAllSetup().
Severity: HIGH
Suggested Fix
To fix this, the addMetricObserver function should be modified to not immediately invoke the callback with a previousValue. Alternatively, the calls to trackTtfbAsSpan and other web vital span tracking functions should be moved from the setup hook to the afterAllSetup hook, ensuring they run after the pageload span has been created and is active.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: packages/browser-utils/src/metrics/webVitalSpans.ts#L273-L277
Potential issue: During the `setup()` lifecycle hook, `startTrackingWebVitals()` is
called, which instruments and caches the Time to First Byte (TTFB) metric in a
`_previousTtfb` variable. Subsequently, if span streaming is enabled,
`trackTtfbAsSpan()` is called within the same `setup()` hook. This function calls
`addMetricObserver`, which detects the cached `_previousTtfb` value and immediately
invokes its callback to create a TTFB span. This occurs synchronously during setup,
before the main pageload span is created in the `afterAllSetup()` hook. As a result, the
TTFB span is emitted prematurely without a parent span, leading to an incorrect trace
hierarchy and missing context.
Did we get this right? 👍 / 👎 to inform future reviews.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| }; | ||
|
|
||
| addInpInstrumentationHandler(onInp); | ||
| } |
There was a problem hiding this comment.
Missing INP duration sanity check in streamed span path
Medium Severity
The existing standalone INP handler in inp.ts includes a MAX_PLAUSIBLE_INP_DURATION (60 seconds) sanity check to guard against "occasional reports of hour-long INP values." The new trackInpAsSpan in webVitalSpans.ts omits this check, meaning it will emit streamed spans for unrealistically long INP durations that the standalone path already filters out.


Summary
Closes #17931
When span streaming is enabled (
traceLifecycle: 'stream'), emit web vital values as non-standalone spans that flow through the v2 pipeline (afterSpanEnd→captureSpan()→SpanBuffer).This is additive — existing standalone spans and pageload measurements remain untouched. The new spans are only emitted when
hasSpanStreamingEnabled(client)is true.Each web vital span carries
browser.web_vital.*attributes per sentry-conventions PRs 229, 233-235:browser.web_vital.lcp.{value,element,id,url,size,load_time,render_time}browser.web_vital.cls.{value,source.<N>}browser.web_vital.inp.valuebrowser.web_vital.ttfb.{value,request_time}browser.web_vital.fcp.valuebrowser.web_vital.fp.valueSpans have meaningful durations (navigation start → event time) instead of being point-in-time, except CLS which is a score.