Skip to content

feat(browser): Add http.redirect_count attribute to browser.redirect span #15943

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 2, 2025
Merged
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
2 changes: 1 addition & 1 deletion .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)',
Expand Down
8 changes: 6 additions & 2 deletions packages/browser-utils/src/metrics/browserMetrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down Expand Up @@ -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 } : {}),
},
});
}
Expand Down
184 changes: 183 additions & 1 deletion packages/browser-utils/test/browser/browserMetrics.test.ts
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 decided to only unit-test this because I couldn't find a way to easily simulate redirects in our playwright integration test setup. If reviewers prefer, I can add a redirection in an e2e test app where this might be easier.

Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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;
Expand Down
Loading