diff --git a/.size-limit.js b/.size-limit.js index 883156b803ce..9107bc637491 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -79,7 +79,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/index.js', import: createImport('init', 'browserTracingIntegration', 'replayIntegration', 'replayCanvasIntegration'), gzip: true, - limit: '80.5 KB', + limit: '81 KB', }, { name: '@sentry/browser (incl. Tracing, Replay, Feedback)', diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts index 62201a72ceab..dbdc54dca512 100644 --- a/packages/browser-utils/src/metrics/browserMetrics.ts +++ b/packages/browser-utils/src/metrics/browserMetrics.ts @@ -460,8 +460,11 @@ export function _addMeasureSpans( } } -/** Instrument navigation entries */ -function _addNavigationSpans(span: Span, entry: PerformanceNavigationTiming, timeOrigin: number): void { +/** + * Instrument navigation entries + * exported only for tests + */ +export function _addNavigationSpans(span: Span, entry: PerformanceNavigationTiming, timeOrigin: number): void { (['unloadEvent', 'redirect', 'domContentLoadedEvent', 'loadEvent', 'connect'] as const).forEach(event => { _addPerformanceNavigationTiming(span, entry, event, timeOrigin); }); @@ -511,6 +514,7 @@ function _addPerformanceNavigationTiming( name: entry.name, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', + ...(event === 'redirect' && entry.redirectCount != null ? { 'http.redirect_count': entry.redirectCount } : {}), }, }); } diff --git a/packages/browser-utils/test/browser/browserMetrics.test.ts b/packages/browser-utils/test/browser/browserMetrics.test.ts index 69169e01af67..685f7e2c41ed 100644 --- a/packages/browser-utils/test/browser/browserMetrics.test.ts +++ b/packages/browser-utils/test/browser/browserMetrics.test.ts @@ -11,7 +11,7 @@ import { import type { Span } from '@sentry/core'; import { describe, beforeEach, it, expect, beforeAll, afterAll } from 'vitest'; -import { _addMeasureSpans, _addResourceSpans } from '../../src/metrics/browserMetrics'; +import { _addMeasureSpans, _addNavigationSpans, _addResourceSpans } from '../../src/metrics/browserMetrics'; import { WINDOW } from '../../src/types'; import { TestClient, getDefaultClientOptions } from '../utils/TestClient'; @@ -416,6 +416,188 @@ describe('_addResourceSpans', () => { ); }); +describe('_addNavigationSpans', () => { + const pageloadSpan = new SentrySpan({ op: 'pageload', name: '/', sampled: true }); + + beforeAll(() => { + setGlobalLocation(mockWindowLocation); + }); + + afterAll(() => { + resetGlobalLocation(); + }); + + beforeEach(() => { + getCurrentScope().clear(); + getIsolationScope().clear(); + + const client = new TestClient( + getDefaultClientOptions({ + tracesSampleRate: 1, + }), + ); + setCurrentClient(client); + client.init(); + }); + + it('adds navigation spans based on the navigation performance entry', () => { + // entry taken from a real entry via browser dev tools + const entry: PerformanceNavigationTiming = { + name: 'https://santry.com/test', + entryType: 'navigation', + startTime: 0, + duration: 546.1000000014901, + initiatorType: 'navigation', + nextHopProtocol: 'h2', + workerStart: 0, + redirectStart: 7.5, + redirectEnd: 20.5, + redirectCount: 2, + fetchStart: 4.9000000059604645, + domainLookupStart: 4.9000000059604645, + domainLookupEnd: 4.9000000059604645, + connectStart: 4.9000000059604645, + secureConnectionStart: 4.9000000059604645, + connectEnd: 4.9000000059604645, + requestStart: 7.9000000059604645, + responseStart: 396.80000000447035, + responseEnd: 416.40000000596046, + transferSize: 14726, + encodedBodySize: 14426, + decodedBodySize: 67232, + responseStatus: 200, + serverTiming: [], + unloadEventStart: 0, + unloadEventEnd: 0, + domInteractive: 473.20000000298023, + domContentLoadedEventStart: 480.1000000014901, + domContentLoadedEventEnd: 480.30000000447035, + domComplete: 546, + loadEventStart: 546, + loadEventEnd: 546.1000000014901, + type: 'navigate', + activationStart: 0, + toJSON: () => ({}), + }; + const spans: Span[] = []; + + getClient()?.on('spanEnd', span => { + spans.push(span); + }); + + _addNavigationSpans(pageloadSpan, entry, 999); + + const trace_id = pageloadSpan.spanContext().traceId; + const parent_span_id = pageloadSpan.spanContext().spanId; + + expect(spans).toHaveLength(9); + expect(spans.map(spanToJSON)).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + data: { + 'sentry.op': 'browser.domContentLoadedEvent', + 'sentry.origin': 'auto.ui.browser.metrics', + }, + description: 'https://santry.com/test', + op: 'browser.domContentLoadedEvent', + origin: 'auto.ui.browser.metrics', + parent_span_id, + trace_id, + }), + expect.objectContaining({ + data: { + 'sentry.op': 'browser.loadEvent', + 'sentry.origin': 'auto.ui.browser.metrics', + }, + description: 'https://santry.com/test', + op: 'browser.loadEvent', + origin: 'auto.ui.browser.metrics', + parent_span_id, + trace_id, + }), + expect.objectContaining({ + data: { + 'sentry.op': 'browser.connect', + 'sentry.origin': 'auto.ui.browser.metrics', + }, + description: 'https://santry.com/test', + op: 'browser.connect', + origin: 'auto.ui.browser.metrics', + parent_span_id, + trace_id, + }), + expect.objectContaining({ + data: { + 'sentry.op': 'browser.TLS/SSL', + 'sentry.origin': 'auto.ui.browser.metrics', + }, + description: 'https://santry.com/test', + op: 'browser.TLS/SSL', + origin: 'auto.ui.browser.metrics', + parent_span_id, + trace_id, + }), + expect.objectContaining({ + data: { + 'sentry.op': 'browser.cache', + 'sentry.origin': 'auto.ui.browser.metrics', + }, + description: 'https://santry.com/test', + op: 'browser.cache', + origin: 'auto.ui.browser.metrics', + parent_span_id, + trace_id, + }), + expect.objectContaining({ + data: { + 'sentry.op': 'browser.DNS', + 'sentry.origin': 'auto.ui.browser.metrics', + }, + description: 'https://santry.com/test', + op: 'browser.DNS', + origin: 'auto.ui.browser.metrics', + parent_span_id, + trace_id, + }), + expect.objectContaining({ + data: { + 'sentry.op': 'browser.request', + 'sentry.origin': 'auto.ui.browser.metrics', + }, + description: 'https://santry.com/test', + op: 'browser.request', + origin: 'auto.ui.browser.metrics', + parent_span_id, + trace_id, + }), + expect.objectContaining({ + data: { + 'sentry.op': 'browser.response', + 'sentry.origin': 'auto.ui.browser.metrics', + }, + description: 'https://santry.com/test', + op: 'browser.response', + origin: 'auto.ui.browser.metrics', + parent_span_id, + trace_id, + }), + expect.objectContaining({ + data: { + 'http.redirect_count': 2, + 'sentry.op': 'browser.redirect', + 'sentry.origin': 'auto.ui.browser.metrics', + }, + description: 'https://santry.com/test', + op: 'browser.redirect', + origin: 'auto.ui.browser.metrics', + parent_span_id, + trace_id, + }), + ]), + ); + }); +}); + const setGlobalLocation = (location: Location) => { // @ts-expect-error need to delete this in order to set to new value delete WINDOW.location;