Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from '@sentry/core';
import { sentryTest } from '../../../../../utils/fixtures';
import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers';
import { URL_FULL, URL_PATH } from '@sentry/conventions/attributes';

sentryTest(
'creates a pageload and navigation root spans each with multiple navigation.redirect childspans',
Expand Down Expand Up @@ -57,6 +58,8 @@ sentryTest(
'sentry.op': 'navigation.redirect',
'sentry.origin': 'auto.navigation.browser',
'sentry.source': 'url',
[URL_FULL]: expect.any(String),
[URL_PATH]: expect.any(String),
},
description: expect.stringContaining('/sub-page-redirect-'),
op: 'navigation.redirect',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from '@sentry/core';
import { sentryTest } from '../../../../../utils/fixtures';
import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers';
import { URL_FULL, URL_PATH } from '@sentry/conventions/attributes';

sentryTest('creates a pageload root span with navigation.redirect childspan', async ({ getLocalTestUrl, page }) => {
if (shouldSkipTracingTest()) {
Expand Down Expand Up @@ -53,6 +54,8 @@ sentryTest('creates a pageload root span with navigation.redirect childspan', as
'sentry.op': 'navigation.redirect',
'sentry.origin': 'auto.navigation.browser',
'sentry.source': 'url',
[URL_FULL]: 'http://sentry-test.io/sub-page',
[URL_PATH]: '/sub-page',
},
description: '/sub-page',
op: 'navigation.redirect',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
SEMANTIC_ATTRIBUTE_SENTRY_SDK_INTEGRATIONS,
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
} from '@sentry/core';
import { SENTRY_TRACE_LIFECYCLE } from '@sentry/conventions/attributes';
import { SENTRY_TRACE_LIFECYCLE, URL_FULL, URL_PATH } from '@sentry/conventions/attributes';
import { sentryTest } from '../../../../utils/fixtures';
import { shouldSkipTracingTest } from '../../../../utils/helpers';
import {
Expand Down Expand Up @@ -92,7 +92,11 @@ sentryTest('starts a streamed navigation span on page navigation', async ({ brow
type: 'string',
value: expect.any(String),
},
'url.full': {
[URL_FULL]: {
type: 'string',
value: expect.any(String),
},
[URL_PATH]: {
type: 'string',
value: expect.any(String),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME,
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
} from '@sentry/core';
import { SENTRY_TRACE_LIFECYCLE } from '@sentry/conventions/attributes';
import { SENTRY_TRACE_LIFECYCLE, URL_FULL, URL_PATH } from '@sentry/conventions/attributes';
import { sentryTest } from '../../../../utils/fixtures';
import { shouldSkipTracingTest } from '../../../../utils/helpers';
import { getSpanOp, getSpansFromEnvelope, waitForStreamedSpanEnvelope } from '../../../../utils/spanUtils';
Expand Down Expand Up @@ -81,7 +81,11 @@ sentryTest(
type: 'string',
value: expect.any(String),
},
'url.full': {
[URL_FULL]: {
type: 'string',
value: expect.any(String),
},
[URL_PATH]: {
type: 'string',
value: expect.any(String),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ test('captures a pageload transaction', async ({ page }) => {
'sentry.origin': 'auto.pageload.browser',
'sentry.sample_rate': 1,
'sentry.source': 'url',
'url.full': 'http://localhost:3030/',
'url.path': '/',
}),
op: 'pageload',
origin: 'auto.pageload.browser',
Expand Down Expand Up @@ -83,6 +85,10 @@ test('captures a navigation transaction', async ({ page }) => {
trace: {
op: 'navigation',
origin: 'auto.navigation.browser',
data: expect.objectContaining({
'url.full': 'http://localhost:3030/#navigation-target',
'url.path': '/',
}),
},
},
transaction: '/',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ test('Captures a pageload transaction', async ({ page }) => {
'performance.activationStart': expect.any(Number),
'lcp.renderTime': expect.any(Number),
'lcp.loadTime': expect.any(Number),
'url.full': 'http://localhost:3030/',
'url.path': '/',
},
op: 'pageload',
span_id: expect.stringMatching(/[a-f0-9]{16}/),
Expand Down
1 change: 1 addition & 0 deletions packages/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
},
"dependencies": {
"@sentry/browser-utils": "10.64.0",
"@sentry/conventions": "^0.15.1",
"@sentry/feedback": "10.64.0",
"@sentry/replay": "10.64.0",
"@sentry/replay-canvas": "10.64.0",
Expand Down
41 changes: 29 additions & 12 deletions packages/browser/src/tracing/browserTracingIntegration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
GLOBAL_OBJ,
hasSpansEnabled,
hasSpanStreamingEnabled,
isURLObjectRelative,
parseStringToURLObject,
propagationContextFromHeaders,
registerSpanErrorInstrumentation,
Expand Down Expand Up @@ -51,6 +52,7 @@ import { WEB_VITALS_INTEGRATION_NAME, webVitalsIntegration } from '../integratio
import { registerBackgroundTabDetection } from './backgroundtab';
import { linkTraces } from './linkedTraces';
import { defaultRequestInstrumentationOptions, instrumentOutgoingRequests } from './request';
import { URL_FULL, URL_PATH } from '@sentry/conventions/attributes';

export const BROWSER_TRACING_INTEGRATION_ID = 'BrowserTracing';

Expand Down Expand Up @@ -416,23 +418,32 @@ export const browserTracingIntegration = ((options: Partial<BrowserTracingOption
let _pageloadSpan: Span | undefined;

/** Create routing idle transaction. */
function _createRouteSpan(client: Client, startSpanOptions: StartSpanOptions, makeActive = true): void {
function _createRouteSpan(client: Client, startSpanOptions: StartSpanOptions, makeActive = true, url?: string): void {
const isPageloadSpan = startSpanOptions.op === 'pageload';
Comment thread
sentry[bot] marked this conversation as resolved.

const initialSpanName = startSpanOptions.name;
const finalStartSpanOptions: StartSpanOptions = beforeStartSpan
? beforeStartSpan(startSpanOptions)
: startSpanOptions;

const attributes = finalStartSpanOptions.attributes || {};
// For navigations, `url` is the destination URL, so we use it to reflect the post-navigation location.
// For pageloads (and manual navigation spans without a URL) we fall back to the current location.
const urlObject = parseStringToURLObject(url || getLocationHref());

const attributes = {
...(urlObject?.pathname && { [URL_PATH]: urlObject.pathname }),
...(urlObject && !isURLObjectRelative(urlObject) && { [URL_FULL]: urlObject.href }),
...finalStartSpanOptions.attributes,
};
Comment thread
Lms24 marked this conversation as resolved.

// If `finalStartSpanOptions.name` is different than `startSpanOptions.name`
// it is because `beforeStartSpan` set a custom name. Therefore we set the source to 'custom'.
if (initialSpanName !== finalStartSpanOptions.name) {
attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] = 'custom';
finalStartSpanOptions.attributes = attributes;
}

finalStartSpanOptions.attributes = attributes;

if (!makeActive) {
// We want to ensure this has 0s duration
const now = dateTimestampInSeconds();
Expand Down Expand Up @@ -563,6 +574,7 @@ export const browserTracingIntegration = ((options: Partial<BrowserTracingOption
...startSpanOptions,
},
false,
navigationOptions.url,
);
return;
}
Expand Down Expand Up @@ -593,13 +605,18 @@ export const browserTracingIntegration = ((options: Partial<BrowserTracingOption
normalizedRequest: undefined,
});

_createRouteSpan(client, {
op: 'navigation',
...startSpanOptions,
// Navigation starts a new trace and is NOT parented under any active interaction (e.g. ui.action.click)
parentSpan: null,
forceTransaction: true,
});
_createRouteSpan(
client,
{
op: 'navigation',
...startSpanOptions,
// Navigation starts a new trace and is NOT parented under any active interaction (e.g. ui.action.click)
parentSpan: null,
forceTransaction: true,
},
true,
navigationOptions?.url,
);
});

client.on('startPageLoadSpan', (startSpanOptions, traceOptions = {}) => {
Expand Down Expand Up @@ -779,8 +796,8 @@ export function startBrowserTracingNavigationSpan(
options?: { url?: string; isRedirect?: boolean },
): Span | undefined {
const { url, isRedirect } = options || {};
client.emit('beforeStartNavigationSpan', spanOptions, { isRedirect });
client.emit('startNavigationSpan', spanOptions, { isRedirect });
client.emit('beforeStartNavigationSpan', spanOptions, { isRedirect, url });
client.emit('startNavigationSpan', spanOptions, { isRedirect, url });

const scope = getCurrentScope();
scope.setTransactionName(spanOptions.name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
} from '../../src/tracing/browserTracingIntegration';
import { PREVIOUS_TRACE_TMP_SPAN_ATTRIBUTE } from '../../src/tracing/linkedTraces';
import { getDefaultBrowserClientOptions } from '../helper/browser-client-options';
import { URL_FULL, URL_PATH } from '@sentry/conventions/attributes';

const oldTextEncoder = global.window.TextEncoder;
const oldTextDecoder = global.window.TextDecoder;
Expand Down Expand Up @@ -65,11 +66,17 @@ describe('browserTracingIntegration', () => {
getCurrentScope().clear();
getIsolationScope().clear();
getCurrentScope().setClient(undefined);
document.head.innerHTML = '';

// Reset document and location to a fresh JSDOM for every test. `getLocationHref()` reads
// `WINDOW.document.location.href`, so leaving `document` bound to a shared instance leaks URL state
// (e.g. `/test`) from `pushState` in earlier tests into later ones. `history` must stay bound to the
// shared instance because the history instrumentation patches it once globally on `client.init()`.
const dom = new JSDOM(undefined, { url: 'https://example.com/' });
Object.defineProperty(global, 'document', { value: dom.window.document, writable: true });
Object.defineProperty(global, 'location', { value: dom.window.document.location, writable: true });

document.head.innerHTML = '';

// We want to suppress the "Multiple browserTracingIntegration instances are not supported." warnings
vi.spyOn(console, 'warn').mockImplementation(() => {});
});
Expand Down Expand Up @@ -174,6 +181,8 @@ describe('browserTracingIntegration', () => {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.browser',
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
[URL_FULL]: 'https://example.com/',
[URL_PATH]: '/',
},
span_id: expect.stringMatching(/[a-f0-9]{16}/),
start_timestamp: expect.any(Number),
Expand Down Expand Up @@ -258,6 +267,8 @@ describe('browserTracingIntegration', () => {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.browser',
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
[URL_FULL]: 'https://example.com/',
[URL_PATH]: '/',
},
span_id: expect.stringMatching(/[a-f0-9]{16}/),
start_timestamp: expect.any(Number),
Expand Down Expand Up @@ -286,6 +297,8 @@ describe('browserTracingIntegration', () => {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.browser',
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
[URL_FULL]: 'https://example.com/test',
[URL_PATH]: '/test',
[PREVIOUS_TRACE_TMP_SPAN_ATTRIBUTE]: `${span?.spanContext().traceId}-${span?.spanContext().spanId}-1`,
},
links: [
Expand Down Expand Up @@ -325,6 +338,8 @@ describe('browserTracingIntegration', () => {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.browser',
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
[URL_FULL]: 'https://example.com/test2',
[URL_PATH]: '/test2',
[PREVIOUS_TRACE_TMP_SPAN_ATTRIBUTE]: `${span2?.spanContext().traceId}-${span2?.spanContext().spanId}-1`,
},
links: [
Expand Down Expand Up @@ -366,6 +381,8 @@ describe('browserTracingIntegration', () => {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.browser',
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
[URL_FULL]: 'https://example.com/',
[URL_PATH]: '/',
},
span_id: expect.stringMatching(/[a-f0-9]{16}/),
start_timestamp: expect.any(Number),
Expand Down Expand Up @@ -394,6 +411,8 @@ describe('browserTracingIntegration', () => {
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation.redirect',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.browser',
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
[URL_FULL]: 'https://example.com/test',
[URL_PATH]: '/test',
},
description: '/test',
op: 'navigation.redirect',
Expand Down Expand Up @@ -456,6 +475,8 @@ describe('browserTracingIntegration', () => {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'manual',
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom',
[URL_FULL]: 'https://example.com/',
[URL_PATH]: '/',
},
span_id: expect.stringMatching(/[a-f0-9]{16}/),
start_timestamp: expect.any(Number),
Expand Down Expand Up @@ -492,6 +513,8 @@ describe('browserTracingIntegration', () => {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.test',
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom',
[URL_FULL]: 'https://example.com/',
[URL_PATH]: '/',
testy: 'yes',
},
span_id: expect.stringMatching(/[a-f0-9]{16}/),
Expand Down Expand Up @@ -733,6 +756,8 @@ describe('browserTracingIntegration', () => {
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom',
[PREVIOUS_TRACE_TMP_SPAN_ATTRIBUTE]: expect.stringMatching(/[a-f0-9]{32}-[a-f0-9]{16}-1/),
[URL_FULL]: 'https://example.com/',
[URL_PATH]: '/',
},
links: [
{
Expand Down Expand Up @@ -785,6 +810,8 @@ describe('browserTracingIntegration', () => {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.test',
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom',
[URL_FULL]: 'https://example.com/',
[URL_PATH]: '/',
Comment thread
cursor[bot] marked this conversation as resolved.
testy: 'yes',
},
span_id: expect.stringMatching(/[a-f0-9]{16}/),
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -815,7 +815,7 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
*/
public on(
hook: 'beforeStartNavigationSpan',
callback: (options: StartSpanOptions, navigationOptions?: { isRedirect?: boolean }) => void,
callback: (options: StartSpanOptions, navigationOptions?: { isRedirect?: boolean; url?: string }) => void,
): () => void;

/**
Expand All @@ -824,7 +824,7 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
*/
public on(
hook: 'startNavigationSpan',
callback: (options: StartSpanOptions, navigationOptions?: { isRedirect?: boolean }) => void,
callback: (options: StartSpanOptions, navigationOptions?: { isRedirect?: boolean; url?: string }) => void,
): () => void;

/**
Expand Down Expand Up @@ -1100,7 +1100,7 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
public emit(
hook: 'beforeStartNavigationSpan',
options: StartSpanOptions,
navigationOptions?: { isRedirect?: boolean },
navigationOptions?: { isRedirect?: boolean; url?: string },
): void;

/**
Expand All @@ -1109,7 +1109,7 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
public emit(
hook: 'startNavigationSpan',
options: StartSpanOptions,
navigationOptions?: { isRedirect?: boolean },
navigationOptions?: { isRedirect?: boolean; url?: string },
): void;

/**
Expand Down
Loading