Skip to content

feat(browser): Replace element timing spans with metrics#19869

Open
logaretm wants to merge 20 commits intodevelopfrom
awad/js-1678-element-timing-metrics-integration
Open

feat(browser): Replace element timing spans with metrics#19869
logaretm wants to merge 20 commits intodevelopfrom
awad/js-1678-element-timing-metrics-integration

Conversation

@logaretm
Copy link
Member

@logaretm logaretm commented Mar 19, 2026

Removes element timing span creation from browserTracingIntegration (deprecates enableElementTiming option, introduces a new standalone elementTimingIntegration that emits Element Timing API data as Sentry distribution metrics instead of spans.

Emits element_timing.render_time and element_timing.load_time metrics with element.identifier and element.paint_type attributes. I believe users can query by the element identifier if they are interested in metrics for a specific element.

Me and Lukas think this is a safe change because it was never documented, even then I made sure to export NO-OP replacement functions to stub them out.

Reasoning for the change

Element Timing values (renderTime, loadTime) are point-in-time timestamps, not durations. Modeling them as spans required awkward workarounds (zero-duration spans, arbitrary start times) that didn't produce meaningful trace data. Metrics are the correct abstraction here.

See discussion in #19261 for full context.

Usage

Sentry.init({
  integrations: [
    Sentry.browserTracingIntegration(),
    Sentry.elementTimingIntegration(),
  ],
});

closes #19260

@linear-code
Copy link

linear-code bot commented Mar 19, 2026

@github-actions
Copy link
Contributor

github-actions bot commented Mar 19, 2026

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

  • (browser) Replace element timing spans with metrics by logaretm in #19869
  • (node) Add nodeRuntimeMetricsIntegration by chargome in #19923
  • (nuxt) Support parametrized SSR routes in Nuxt 5 by s1gr1d in #19977

Bug Fixes 🐛

  • (opentelemetry) Convert seconds timestamps in span.end() to milliseconds by logaretm in #19958

Documentation 📚

  • (release) Update publishing-a-release.md by nicohrubec in #19982

Internal Changes 🔧

  • (core) Consolidate getOperationName into one shared utility by nicohrubec in #19971
  • (deno) Expand Deno E2E test coverage by chargome in #19957

🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 19, 2026

size-limit report 📦

⚠️ Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

Path Size % Change Change
@sentry/browser 25.69 kB +0.2% +49 B 🔺
@sentry/browser - with treeshaking flags 24.17 kB +0.14% +33 B 🔺
@sentry/browser (incl. Tracing) 42.17 kB -1.04% -443 B 🔽
@sentry/browser (incl. Tracing, Profiling) 46.79 kB -1.03% -485 B 🔽
@sentry/browser (incl. Tracing, Replay) 80.98 kB -0.55% -440 B 🔽
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 70.6 kB -0.57% -398 B 🔽
@sentry/browser (incl. Tracing, Replay with Canvas) 85.7 kB -0.49% -422 B 🔽
@sentry/browser (incl. Tracing, Replay, Feedback) 97.97 kB -0.42% -406 B 🔽
@sentry/browser (incl. Feedback) 42.48 kB +0.08% +30 B 🔺
@sentry/browser (incl. sendFeedback) 30.35 kB +0.15% +44 B 🔺
@sentry/browser (incl. FeedbackAsync) 35.4 kB +0.12% +39 B 🔺
@sentry/browser (incl. Metrics) 26.96 kB +0.14% +37 B 🔺
@sentry/browser (incl. Logs) 27.1 kB +0.12% +32 B 🔺
@sentry/browser (incl. Metrics & Logs) 27.78 kB +0.15% +39 B 🔺
@sentry/react 27.45 kB +0.22% +58 B 🔺
@sentry/react (incl. Tracing) 44.52 kB -0.96% -429 B 🔽
@sentry/vue 30.13 kB +0.16% +46 B 🔺
@sentry/vue (incl. Tracing) 44.08 kB -0.92% -405 B 🔽
@sentry/svelte 25.7 kB +0.16% +40 B 🔺
CDN Bundle 28.38 kB +0.38% +105 B 🔺
CDN Bundle (incl. Tracing) 43.18 kB -0.76% -327 B 🔽
CDN Bundle (incl. Logs, Metrics) 29.75 kB +2.09% +608 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) 44.23 kB -0.28% -122 B 🔽
CDN Bundle (incl. Replay, Logs, Metrics) 68.55 kB +0.5% +338 B 🔺
CDN Bundle (incl. Tracing, Replay) 80.06 kB -0.34% -272 B 🔽
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 81.14 kB -0.13% -98 B 🔽
CDN Bundle (incl. Tracing, Replay, Feedback) 85.6 kB -0.32% -272 B 🔽
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 86.66 kB -0.14% -113 B 🔽
CDN Bundle - uncompressed 82.88 kB +0.31% +256 B 🔺
CDN Bundle (incl. Tracing) - uncompressed 128.02 kB -0.42% -537 B 🔽
CDN Bundle (incl. Logs, Metrics) - uncompressed 87.02 kB +1.8% +1.53 kB 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 131.43 kB +0.01% +7 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 210.01 kB +0.43% +886 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed 244.9 kB -0.21% -512 B 🔽
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 248.3 kB +0.02% +32 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 257.81 kB -0.2% -512 B 🔽
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 261.2 kB +0.02% +32 B 🔺
@sentry/nextjs (client) 46.93 kB -0.93% -438 B 🔽
@sentry/sveltekit (client) 42.67 kB -0.94% -401 B 🔽
@sentry/node-core 56.46 kB +0.2% +112 B 🔺
@sentry/node 173.5 kB +0.2% +338 B 🔺
@sentry/node - without tracing 96.47 kB +0.13% +123 B 🔺
@sentry/aws-serverless 113.48 kB +0.13% +143 B 🔺

View base workflow run

@github-actions
Copy link
Contributor

github-actions bot commented Mar 19, 2026

node-overhead report 🧳

Note: This is a synthetic benchmark with a minimal express app and does not necessarily reflect the real-world performance impact in an application.

Scenario Requests/s % of Baseline Prev. Requests/s Change %
GET Baseline 11,165 - 9,043 +23%
GET With Sentry 1,893 17% 1,673 +13%
GET With Sentry (error only) 7,491 67% 6,094 +23%
POST Baseline 1,288 - 1,202 +7%
POST With Sentry 640 50% 588 +9%
POST With Sentry (error only) 1,140 89% 1,059 +8%
MYSQL Baseline 3,514 - 3,246 +8%
MYSQL With Sentry 509 14% 451 +13%
MYSQL With Sentry (error only) 2,962 84% 2,637 +12%

View base workflow run

@logaretm logaretm marked this pull request as ready for review March 19, 2026 18:06
@logaretm logaretm requested review from Lms24 and Copilot March 19, 2026 18:07
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates Element Timing collection in the browser SDK from (awkward) span modeling to Sentry distribution metrics, by removing Element Timing span creation from browserTracingIntegration and introducing a standalone elementTimingIntegration export.

Changes:

  • Remove Element Timing span tracking from browserTracingIntegration and deprecate the enableElementTiming option.
  • Add elementTimingIntegration (re-exported from @sentry-internal/browser-utils) and emit element_timing.render_time / element_timing.load_time as distribution metrics with element.identifier and element.paint_type attributes.
  • Update unit + Playwright integration tests to validate metric emission instead of spans.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/browser/src/tracing/browserTracingIntegration.ts Stops starting Element Timing tracking; deprecates enableElementTiming.
packages/browser/src/index.ts Re-exports elementTimingIntegration for SDK consumers.
packages/browser/src/index.bundle.tracing.ts Re-exports elementTimingIntegration in tracing bundle.
packages/browser/src/index.bundle.tracing.replay.ts Re-exports elementTimingIntegration in tracing+replay bundle.
packages/browser/src/index.bundle.tracing.replay.logs.metrics.ts Re-exports elementTimingIntegration in tracing+replay+logs+metrics bundle.
packages/browser/src/index.bundle.tracing.replay.feedback.ts Re-exports elementTimingIntegration in tracing+replay+feedback bundle.
packages/browser/src/index.bundle.tracing.replay.feedback.logs.metrics.ts Re-exports elementTimingIntegration in tracing+replay+feedback+logs+metrics bundle.
packages/browser/src/index.bundle.tracing.logs.metrics.ts Re-exports elementTimingIntegration in tracing+logs+metrics bundle.
packages/browser-utils/src/metrics/elementTiming.ts Implements elementTimingIntegration emitting distribution metrics; makes startTrackingElementTiming a deprecated no-op.
packages/browser-utils/src/index.ts Exports elementTimingIntegration from browser-utils package surface.
packages/browser-utils/test/metrics/elementTiming.test.ts Reworks unit tests to assert metrics.distribution calls instead of spans.
dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/test.ts Updates Playwright tests to assert metric envelopes and identifiers.
dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/init.js Enables Sentry.elementTimingIntegration() in the test app init.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +34 to +35
} catch {
return [];
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Dead default value for removed enableElementTiming usage
    • Removed the dead enableElementTiming: true default value from DEFAULT_BROWSER_TRACING_OPTIONS since the option is deprecated and no longer used.

Create PR

Or push these changes by commenting:

@cursor push 8056bbebb4
Preview (8056bbebb4)
diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/test.ts
--- a/dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/test.ts
+++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/test.ts
@@ -96,14 +96,7 @@
     await page.goto(url);
 
     // Wait until all expected element identifiers have been flushed as metrics
-    await collector.waitForIdentifiers([
-      'image-fast',
-      'text1',
-      'button1',
-      'image-slow',
-      'lazy-image',
-      'lazy-text',
-    ]);
+    await collector.waitForIdentifiers(['image-fast', 'text1', 'button1', 'image-slow', 'lazy-image', 'lazy-text']);
 
     const allMetrics = collector.getAll().filter(m => m.name.startsWith('element_timing.'));
     const renderTimeMetrics = allMetrics.filter(m => m.name === 'element_timing.render_time');

diff --git a/packages/browser/src/tracing/browserTracingIntegration.ts b/packages/browser/src/tracing/browserTracingIntegration.ts
--- a/packages/browser/src/tracing/browserTracingIntegration.ts
+++ b/packages/browser/src/tracing/browserTracingIntegration.ts
@@ -334,7 +334,6 @@
   enableLongTask: true,
   enableLongAnimationFrame: true,
   enableInp: true,
-  enableElementTiming: true,
   ignoreResourceSpans: [],
   ignorePerformanceApiSpans: [],
   detectRedirects: true,

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Copy link
Member

@Lms24 Lms24 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for taking this on! I had some comments around attributes and naming. Would also like to get a review from @chargome since my knowledge around auto-emitted metrics is rather limited. Let's make sure we follow all prior art here.

const renderTime = elementEntry.renderTime;
const loadTime = elementEntry.loadTime;
if (paintType) {
metricAttributes['element.paint_type'] = paintType;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m: Let's make sure we use ui.element.* attributes as defined in conventions

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had already done it but my clanker "forgot" to push it 🤦‍♂️

? [msToSec(renderTime), 'render-time']
: [timestampInSeconds(), 'entry-emission'];
if (renderTime) {
metrics.distribution(`element_timing.render_time`, renderTime, {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to sanity check: @chargome are you aware of any naming conventions around automatically collected metrics?

Copy link
Member Author

@logaretm logaretm Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we name it similarly to the attributes? It feels like metrics here are just an aggregation of what we collected previously as attributes. So ui.element.render_time?

But not sure if a metric having itself as an attribute is a bit weird/redundant or not....

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ui.element.render_time sounds good to me. But in this case we don't need the same attribute

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No strict conventions, ui.element.render_time seems fine

@Lms24 Lms24 requested a review from chargome March 23, 2026 08:58
@logaretm logaretm force-pushed the awad/js-1678-element-timing-metrics-integration branch from b77c217 to 4f49333 Compare March 23, 2026 18:00
Copy link
Member

@chargome chargome left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally LGTM but isn't this a behavioral breaking change? Since user need to opt-in to this intergation now?

Comment on lines +357 to +361
if (DEBUG_BUILD && 'enableElementTiming' in options) {
debug.warn(
'[Tracing] `enableElementTiming` is deprecated and no longer has any effect. Use the standalone `elementTimingIntegration` instead.',
);
}
Copy link
Member

@chargome chargome Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m: Should we rather log this unconditionally without a debug flag actually?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was never documented as far as we know, our DX team found it while trying to PR something similar.

I think yes, we should always log that in this case.

@logaretm
Copy link
Member Author

I renamed the metric and dropped the attribute of the same name

@logaretm logaretm requested review from Lms24 and chargome March 25, 2026 19:03
@Lms24
Copy link
Member

Lms24 commented Mar 25, 2026

Generally LGTM but isn't this a behavioral breaking change?

I think this is fine, given these spans never made it to sentry as Awad already said. Furthermore, the option and the spans weren't documented (which reminds me, @logaretm we still need to document the new integration once this is released). I think element timing is a good initial test candidate for browser metrics. We can do more of these in the future.

@logaretm logaretm force-pushed the awad/js-1678-element-timing-metrics-integration branch from ee505f9 to 7773964 Compare March 26, 2026 01:16
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Copy link
Member

@chargome chargome left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logaretm and others added 20 commits March 26, 2026 09:46
…ne integration

Remove element timing span creation from browserTracingIntegration and
introduce a new elementTimingIntegration that emits Element Timing API
data as Sentry distribution metrics instead of spans.

Element timing values (renderTime, loadTime) are point-in-time timestamps,
not durations, making metrics a better fit than spans. The new integration
emits `element_timing.render_time` and `element_timing.load_time` metrics
with `element.identifier` and `element.paint_type` attributes.

refs JS-1678

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rewrite Playwright integration tests to expect Sentry distribution
metrics instead of spans, matching the new elementTimingIntegration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Metrics are buffered and flushed after 5 seconds. The previous test
used a 3 second timeout which wasn't enough. Now properly waits for
metric envelopes with adequate timeouts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The integration was only exported from the npm package entry point
but not from the CDN bundle entry points, causing Sentry.elementTimingIntegration
to be undefined in bundle_tracing_* test configurations.

Verified locally:
- PW_BUNDLE=bundle_tracing_logs_metrics: 2 passed
- PW_BUNDLE=bundle_tracing_replay_feedback_logs_metrics_min: 2 passed
- browser-utils unit tests: 132 passed
- browser unit tests: 545 passed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ing tests

Instead of sleeping 8 seconds and hoping all metrics have arrived,
poll until the expected element identifiers appear in the collected
metrics. This makes the tests faster (6-13s vs 16-20s) and more
reliable — they complete as soon as data arrives and fail with clear
assertions when it doesn't.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The property was removed from defaults but the type still required it,
causing a build error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Align attribute names with Sentry conventions and add missing attributes
(id, type, url, width, height) from the Element Timing API.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Non-metrics bundles now export a no-op shim that warns at runtime,
matching the established pattern for browserTracingIntegrationShim.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ndles

The integration has no tracing dependency, so all .metrics bundles
should export the real implementation. Also fixes misleading shim
warning message.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use consoleSandbox instead of DEBUG_BUILD guard so the warning
shows in production bundles too.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rename element_timing.render_time → ui.element.render_time and
element_timing.load_time → ui.element.load_time to align with
Sentry attribute conventions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Avoids silently dropping metrics due to falsy `0` values from cross-origin
images without `Timing-Allow-Origin`, making the intent clearer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@logaretm logaretm force-pushed the awad/js-1678-element-timing-metrics-integration branch from 86042cb to 3dd7e22 Compare March 26, 2026 13:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ElementTiming span timestamp uses render time

4 participants