diff --git a/CHANGELOG.md b/CHANGELOG.md index c1d7bf7f1b48..681ea35eace4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Work in this release was contributed by @psh4607, @thijsw, @trinitiwowka, @nehap - feat(browser)!: `browser.navigation.type` on web vital and bfcache navigation spans now carries the navigation type exactly as web-vitals reports it. `bfcache` is now `back-forward-cache`, and a back/forward navigation that missed the bfcache (`back-forward`) or a discarded-tab restore (`restore`) is no longer folded into `navigate`. Update any dashboards or alerts filtering on `bfcache`. - feat(browser): The pageload span now carries `browser.navigation.type`, the navigation type web-vitals reported TTFB and FCP for. Those vitals, and FP, are attributes of the pageload span rather than spans of their own, so until now there was no navigation type to read them against. +- feat(browser): INP spans now carry `browser.web_vital.inp.target`, the element the user interacted with, and `browser.web_vital.inp.interaction_type` (`click`, `hover`, `drag` or `press`). Both were only available as the span's name and op, which always have a value. The attributes are left out when there is nothing to describe: an INP that web-vitals reports without an interaction gets neither, and an element that could not be resolved gets no target. - feat(core): Add `createFetchIntegration`, the shared implementation behind the global-`fetch` integrations in `@sentry/bun`, `@sentry/cloudflare`, `@sentry/deno` and `@sentry/vercel-edge`. Those four packages carried four copies of it; they now share one. Two changes come out of that: - All four gain a `tracePropagation` option (default `true`). Turn it off to stop injecting `sentry-trace` and `baggage` without also turning off spans. To scope propagation to specific URLs, keep using `tracePropagationTargets` in the client options. - Integration options now follow the client. Previously a second `Sentry.init()` in the same process silently reused the options of the first one. diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-late/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-late/test.ts index 6ebd77451657..1b500a7c18b4 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-late/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-late/test.ts @@ -77,6 +77,8 @@ sentryTest( 'sentry.op': { value: 'ui.interaction.click', type: 'string' }, 'sentry.exclusive_time': { value: inpValue, type: expect.stringMatching(/^(integer)|(double)$/) }, 'browser.web_vital.inp.value': { value: inpValue, type: expect.stringMatching(/^(integer)|(double)$/) }, + 'browser.web_vital.inp.target': { value: 'body > NormalButton', type: 'string' }, + 'browser.web_vital.inp.interaction_type': { value: 'click', type: 'string' }, 'browser.navigation.type': { value: 'navigate', type: 'string' }, 'sentry.transaction': { value: 'test-url', type: 'string' }, 'sentry.segment.name': { value: 'test-url', type: 'string' }, diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-navigate/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-navigate/test.ts index fc57a1f137ba..fc02119e9303 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-navigate/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-navigate/test.ts @@ -77,6 +77,8 @@ sentryTest( 'sentry.op': { value: 'ui.interaction.click', type: 'string' }, 'sentry.exclusive_time': { value: inpValue, type: expect.stringMatching(/^(integer)|(double)$/) }, 'browser.web_vital.inp.value': { value: inpValue, type: expect.stringMatching(/^(integer)|(double)$/) }, + 'browser.web_vital.inp.target': { value: 'body > nav#navigation > NavigationLink', type: 'string' }, + 'browser.web_vital.inp.interaction_type': { value: 'click', type: 'string' }, 'browser.navigation.type': { value: 'navigate', type: 'string' }, 'sentry.transaction': { value: 'test-url', type: 'string' }, 'sentry.segment.name': { value: 'test-url', type: 'string' }, @@ -163,6 +165,8 @@ sentryTest( 'sentry.op': { value: 'ui.interaction.click', type: 'string' }, 'sentry.exclusive_time': { value: inpValue, type: expect.stringMatching(/^(integer)|(double)$/) }, 'browser.web_vital.inp.value': { value: inpValue, type: expect.stringMatching(/^(integer)|(double)$/) }, + 'browser.web_vital.inp.target': { value: 'body > nav#navigation > NavigationLink', type: 'string' }, + 'browser.web_vital.inp.interaction_type': { value: 'click', type: 'string' }, 'browser.navigation.type': { value: 'navigate', type: 'string' }, 'sentry.transaction': { value: 'test-url', type: 'string' }, 'sentry.segment.name': { value: 'test-url', type: 'string' }, diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized-late/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized-late/test.ts index 2687f411aeed..362d568ebdc4 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized-late/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized-late/test.ts @@ -78,6 +78,8 @@ sentryTest( 'sentry.op': { value: 'ui.interaction.click', type: 'string' }, 'sentry.exclusive_time': { value: inpValue, type: expect.stringMatching(/^(integer)|(double)$/) }, 'browser.web_vital.inp.value': { value: inpValue, type: expect.stringMatching(/^(integer)|(double)$/) }, + 'browser.web_vital.inp.target': { value: 'body > NormalButton', type: 'string' }, + 'browser.web_vital.inp.interaction_type': { value: 'click', type: 'string' }, 'browser.navigation.type': { value: 'navigate', type: 'string' }, // the parametrized route name flows onto the INP span 'sentry.transaction': { value: 'test-route', type: 'string' }, diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized/test.ts index 880d8b5ca605..baec4c7f43e4 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized/test.ts @@ -78,6 +78,8 @@ sentryTest( 'sentry.op': { value: 'ui.interaction.click', type: 'string' }, 'sentry.exclusive_time': { value: inpValue, type: expect.stringMatching(/^(integer)|(double)$/) }, 'browser.web_vital.inp.value': { value: inpValue, type: expect.stringMatching(/^(integer)|(double)$/) }, + 'browser.web_vital.inp.target': { value: 'body > NormalButton', type: 'string' }, + 'browser.web_vital.inp.interaction_type': { value: 'click', type: 'string' }, 'browser.navigation.type': { value: 'navigate', type: 'string' }, // the parametrized route name flows onto the INP span 'sentry.transaction': { value: 'test-route', type: 'string' }, diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp/test.ts index c48ffe6feb10..f1fc6cc92f37 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp/test.ts @@ -81,6 +81,8 @@ sentryTest( 'sentry.op': { value: 'ui.interaction.click', type: 'string' }, 'sentry.exclusive_time': { value: inpValue, type: expect.stringMatching(/^(integer)|(double)$/) }, 'browser.web_vital.inp.value': { value: inpValue, type: expect.stringMatching(/^(integer)|(double)$/) }, + 'browser.web_vital.inp.target': { value: 'body > NormalButton', type: 'string' }, + 'browser.web_vital.inp.interaction_type': { value: 'click', type: 'string' }, 'browser.navigation.type': { value: 'navigate', type: 'string' }, 'sentry.transaction': { value: 'test-url', type: 'string' }, 'sentry.segment.name': { value: 'test-url', type: 'string' }, @@ -148,6 +150,8 @@ sentryTest( 'sentry.op': { value: 'ui.interaction.click', type: 'string' }, 'sentry.exclusive_time': { value: inpValue, type: expect.stringMatching(/^(integer)|(double)$/) }, 'browser.web_vital.inp.value': { value: inpValue, type: expect.stringMatching(/^(integer)|(double)$/) }, + 'browser.web_vital.inp.target': { value: 'body > SlowButton', type: 'string' }, + 'browser.web_vital.inp.interaction_type': { value: 'click', type: 'string' }, 'browser.navigation.type': { value: 'navigate', type: 'string' }, 'sentry.transaction': { value: 'test-url', type: 'string' }, 'sentry.segment.name': { value: 'test-url', type: 'string' }, diff --git a/packages/browser-utils/src/web-vitals/inp.ts b/packages/browser-utils/src/web-vitals/inp.ts index a0f7124fb944..af03ceafcd94 100644 --- a/packages/browser-utils/src/web-vitals/inp.ts +++ b/packages/browser-utils/src/web-vitals/inp.ts @@ -14,7 +14,7 @@ const INTERACTIONS_SPAN_MAP = new Map(); // Map to store element names by timestamp, since we get the DOM event before the PerformanceObserver entry /** What `htmlTreeAsString` returns when it cannot describe the target. */ -const UNKNOWN_ELEMENT_NAME = ''; +export const UNKNOWN_ELEMENT_NAME = ''; const ELEMENT_NAME_TIMESTAMP_MAP = new Map(); diff --git a/packages/browser-utils/src/web-vitals/spans.ts b/packages/browser-utils/src/web-vitals/spans.ts index 1996715d51cb..f887441de90c 100644 --- a/packages/browser-utils/src/web-vitals/spans.ts +++ b/packages/browser-utils/src/web-vitals/spans.ts @@ -13,7 +13,7 @@ import { import { DEBUG_BUILD } from '../debug-build'; import { htmlTreeAsString } from '../htmlTreeAsString'; import type { InteractionType } from './inp'; -import { getCachedInteractionContext, INP_ENTRY_MAP, MAX_PLAUSIBLE_INP_DURATION } from './inp'; +import { getCachedInteractionContext, INP_ENTRY_MAP, MAX_PLAUSIBLE_INP_DURATION, UNKNOWN_ELEMENT_NAME } from './inp'; import type { InstrumentationHandlerCallback, MetricNavigationType } from '../instrumentation/performanceObserver'; import { addClsInstrumentationHandler, @@ -381,7 +381,8 @@ export function _sendInpSpan( // `ui.interaction.*` family, because falling outside it would hide exactly the fast navigations // that web-vitals synthesizes these values for (GoogleChrome/web-vitals#724), reintroducing the // reporting bias they were added to remove. - const interactionType = (entry && INP_ENTRY_MAP[entry.name]) || 'click'; + const entryInteractionType = entry && INP_ENTRY_MAP[entry.name]; + const interactionType = entryInteractionType || 'click'; const cachedContext = entry && getCachedInteractionContext(entry.interactionId); const activeSpan = getActiveSpan(); @@ -392,15 +393,24 @@ export function _sendInpSpan( const spanToUse = attributedSpan || cachedContext?.span || rootSpan; const name = cachedContext?.elementName || (entry ? htmlTreeAsString(entry.target) : 'Interaction to next paint'); + const attributes: SpanAttributes = { + [SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: entry?.duration ?? inpValue, + }; + + // The span's name and op always have a value, even for an INP without an entry, so they can't + // say whether there was an interaction to describe. These attributes can: they are only set for + // what was actually observed. + // TODO: use the `@sentry/conventions` constants once getsentry/sentry-conventions#641 is released. + entry && name !== UNKNOWN_ELEMENT_NAME && (attributes['browser.web_vital.inp.target'] = name); + entryInteractionType && (attributes['browser.web_vital.inp.interaction_type'] = entryInteractionType); + _emitWebVitalSpan({ name, op: INTERACTION_TYPE_TO_SPAN_OP[interactionType], origin: 'auto.http.browser.inp', metricName: 'inp', value: inpValue, - attributes: { - [SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: entry?.duration ?? inpValue, - }, + attributes, startTime, endTime: startTime + duration, navigationType: metric?.navigationType, diff --git a/packages/browser-utils/test/web-vitals/spans.test.ts b/packages/browser-utils/test/web-vitals/spans.test.ts index daad65d7b5cb..a6417c099f3a 100644 --- a/packages/browser-utils/test/web-vitals/spans.test.ts +++ b/packages/browser-utils/test/web-vitals/spans.test.ts @@ -627,6 +627,8 @@ describe('_sendInpSpan', () => { 'sentry.exclusive_time': 120, 'sentry.transaction': 'test-route', 'sentry.segment.name': 'test-route', + 'browser.web_vital.inp.target': '