Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b188e0a
feat(react-router): Add support for React Router instrumentation API
onurtemizkan Dec 18, 2025
95721fc
Move instrumentation API functions to serverGlobals not to break hydr…
onurtemizkan Dec 19, 2025
fa71e6b
Update hydrogen server transaction tests with better parameterization
onurtemizkan Dec 19, 2025
75374f3
Address copilot review
onurtemizkan Dec 22, 2025
8281d63
Improve E2E test coverage
onurtemizkan Dec 23, 2025
7aa2d67
Use snake_case for span ops
onurtemizkan Dec 23, 2025
e3064e2
Move navigate hook flag inside client check
onurtemizkan Dec 23, 2025
23514b7
Move data inside `mechanism` object
onurtemizkan Dec 23, 2025
9b756ae
Merge remote-tracking branch 'origin/develop' into react-router-8-ins…
onurtemizkan Dec 29, 2025
0c84709
Prevent Framework Mode navigation span regression
onurtemizkan Dec 29, 2025
681eb3e
Enhance navigation with popstate listener and numeric navigation hand…
onurtemizkan Dec 30, 2025
1571025
Lint
onurtemizkan Dec 30, 2025
7c9148e
Set span status on request handler errors
onurtemizkan Dec 30, 2025
3ad323c
Merge branch 'develop' into react-router-8-instrumentation-api
onurtemizkan Dec 30, 2025
f1aab54
Clean up
onurtemizkan Jan 8, 2026
7ab179a
Move `captureInstrumentationError` calls inside error check blocks
onurtemizkan Jan 8, 2026
4cc4af8
Move error capture inside check
onurtemizkan Jan 8, 2026
46aee15
Merge branch 'develop' into react-router-8-instrumentation-api
onurtemizkan Jan 8, 2026
9cf02d8
Merge branch 'develop' into react-router-8-instrumentation-api
chargome Jan 26, 2026
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
Prev Previous commit
Next Next commit
Use snake_case for span ops
  • Loading branch information
onurtemizkan committed Dec 23, 2025
commit 7aa2d6748ad556df5ac08e0605c14e4f2d0831d9
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,15 @@ test.describe('server - instrumentation API error capture', () => {

// Find the loader span
const loaderSpan = transaction?.spans?.find(
(span: { data?: { 'sentry.op'?: string } }) => span.data?.['sentry.op'] === 'function.react-router.loader',
(span: { data?: { 'sentry.op'?: string } }) => span.data?.['sentry.op'] === 'function.react_router.loader',
);

expect(loaderSpan).toMatchObject({
data: {
'sentry.origin': 'auto.function.react_router.instrumentation_api',
'sentry.op': 'function.react-router.loader',
'sentry.op': 'function.react_router.loader',
},
op: 'function.react-router.loader',
op: 'function.react_router.loader',
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ test.describe('client - instrumentation API fetcher (upstream limitation)', () =
// Wait for the fetcher action transaction
const fetcherTxPromise = waitForTransaction(APP_NAME, async transactionEvent => {
return (
transactionEvent.contexts?.trace?.op === 'function.react-router.fetcher' &&
transactionEvent.contexts?.trace?.op === 'function.react_router.fetcher' &&
transactionEvent.contexts?.trace?.data?.['sentry.origin'] === 'auto.function.react_router.instrumentation_api'
);
});
Expand All @@ -34,7 +34,7 @@ test.describe('client - instrumentation API fetcher (upstream limitation)', () =
expect(fetcherTx).toMatchObject({
contexts: {
trace: {
op: 'function.react-router.fetcher',
op: 'function.react_router.fetcher',
origin: 'auto.function.react_router.instrumentation_api',
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,21 @@ test.describe('server - instrumentation API lazy loading', () => {

// Find the lazy span
const lazySpan = transaction?.spans?.find(
(span: { data?: { 'sentry.op'?: string } }) => span.data?.['sentry.op'] === 'function.react-router.lazy',
(span: { data?: { 'sentry.op'?: string } }) => span.data?.['sentry.op'] === 'function.react_router.lazy',
);

expect(lazySpan).toMatchObject({
span_id: expect.any(String),
trace_id: expect.any(String),
data: {
'sentry.origin': 'auto.function.react_router.instrumentation_api',
'sentry.op': 'function.react-router.lazy',
'sentry.op': 'function.react_router.lazy',
},
description: 'Lazy Route Load',
parent_span_id: expect.any(String),
start_timestamp: expect.any(Number),
timestamp: expect.any(Number),
op: 'function.react-router.lazy',
op: 'function.react_router.lazy',
origin: 'auto.function.react_router.instrumentation_api',
});
});
Expand All @@ -72,18 +72,18 @@ test.describe('server - instrumentation API lazy loading', () => {

// Find the loader span that runs after lazy loading
const loaderSpan = transaction?.spans?.find(
(span: { data?: { 'sentry.op'?: string } }) => span.data?.['sentry.op'] === 'function.react-router.loader',
(span: { data?: { 'sentry.op'?: string } }) => span.data?.['sentry.op'] === 'function.react_router.loader',
);

expect(loaderSpan).toMatchObject({
span_id: expect.any(String),
trace_id: expect.any(String),
data: {
'sentry.origin': 'auto.function.react_router.instrumentation_api',
'sentry.op': 'function.react-router.loader',
'sentry.op': 'function.react_router.loader',
},
description: '/performance/lazy-route',
op: 'function.react-router.loader',
op: 'function.react_router.loader',
origin: 'auto.function.react_router.instrumentation_api',
});
});
Expand All @@ -98,11 +98,11 @@ test.describe('server - instrumentation API lazy loading', () => {
const transaction = await txPromise;

const lazySpan = transaction?.spans?.find(
(span: { data?: { 'sentry.op'?: string } }) => span.data?.['sentry.op'] === 'function.react-router.lazy',
(span: { data?: { 'sentry.op'?: string } }) => span.data?.['sentry.op'] === 'function.react_router.lazy',
);

const loaderSpan = transaction?.spans?.find(
(span: { data?: { 'sentry.op'?: string } }) => span.data?.['sentry.op'] === 'function.react-router.loader',
(span: { data?: { 'sentry.op'?: string } }) => span.data?.['sentry.op'] === 'function.react_router.loader',
);

expect(lazySpan).toBeDefined();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,21 @@ test.describe('server - instrumentation API middleware', () => {

// Find the middleware span
const middlewareSpan = transaction?.spans?.find(
(span: { data?: { 'sentry.op'?: string } }) => span.data?.['sentry.op'] === 'function.react-router.middleware',
(span: { data?: { 'sentry.op'?: string } }) => span.data?.['sentry.op'] === 'function.react_router.middleware',
);

expect(middlewareSpan).toMatchObject({
span_id: expect.any(String),
trace_id: expect.any(String),
data: {
'sentry.origin': 'auto.function.react_router.instrumentation_api',
'sentry.op': 'function.react-router.middleware',
'sentry.op': 'function.react_router.middleware',
},
description: '/performance/with-middleware',
parent_span_id: expect.any(String),
start_timestamp: expect.any(Number),
timestamp: expect.any(Number),
op: 'function.react-router.middleware',
op: 'function.react_router.middleware',
origin: 'auto.function.react_router.instrumentation_api',
});
});
Expand All @@ -69,11 +69,11 @@ test.describe('server - instrumentation API middleware', () => {
const transaction = await txPromise;

const middlewareSpan = transaction?.spans?.find(
(span: { data?: { 'sentry.op'?: string } }) => span.data?.['sentry.op'] === 'function.react-router.middleware',
(span: { data?: { 'sentry.op'?: string } }) => span.data?.['sentry.op'] === 'function.react_router.middleware',
);

const loaderSpan = transaction?.spans?.find(
(span: { data?: { 'sentry.op'?: string } }) => span.data?.['sentry.op'] === 'function.react-router.loader',
(span: { data?: { 'sentry.op'?: string } }) => span.data?.['sentry.op'] === 'function.react_router.loader',
);

expect(middlewareSpan).toBeDefined();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,21 +103,21 @@ test.describe('server - instrumentation API performance', () => {
const transaction = await txPromise;

// Find the loader span
const loaderSpan = transaction?.spans?.find(span => span.data?.['sentry.op'] === 'function.react-router.loader');
const loaderSpan = transaction?.spans?.find(span => span.data?.['sentry.op'] === 'function.react_router.loader');

expect(loaderSpan).toMatchObject({
span_id: expect.any(String),
trace_id: expect.any(String),
data: {
'sentry.origin': 'auto.function.react_router.instrumentation_api',
'sentry.op': 'function.react-router.loader',
'sentry.op': 'function.react_router.loader',
},
description: '/performance/server-loader',
parent_span_id: expect.any(String),
start_timestamp: expect.any(Number),
timestamp: expect.any(Number),
status: 'ok',
op: 'function.react-router.loader',
op: 'function.react_router.loader',
origin: 'auto.function.react_router.instrumentation_api',
});
});
Expand All @@ -133,21 +133,21 @@ test.describe('server - instrumentation API performance', () => {
const transaction = await txPromise;

// Find the action span
const actionSpan = transaction?.spans?.find(span => span.data?.['sentry.op'] === 'function.react-router.action');
const actionSpan = transaction?.spans?.find(span => span.data?.['sentry.op'] === 'function.react_router.action');

expect(actionSpan).toMatchObject({
span_id: expect.any(String),
trace_id: expect.any(String),
data: {
'sentry.origin': 'auto.function.react_router.instrumentation_api',
'sentry.op': 'function.react-router.action',
'sentry.op': 'function.react_router.action',
},
description: '/performance/server-action',
parent_span_id: expect.any(String),
start_timestamp: expect.any(Number),
timestamp: expect.any(Number),
status: 'ok',
op: 'function.react-router.action',
op: 'function.react_router.action',
origin: 'auto.function.react_router.instrumentation_api',
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export function createSentryClientInstrumentation(
{
name: `Fetcher ${info.fetcherKey}`,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react-router.fetcher',
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.fetcher',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',
},
},
Expand All @@ -110,7 +110,7 @@ export function createSentryClientInstrumentation(
{
name: routePattern,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react-router.client-loader',
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.client_loader',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',
},
},
Expand All @@ -131,7 +131,7 @@ export function createSentryClientInstrumentation(
{
name: routePattern,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react-router.client-action',
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.client_action',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',
},
},
Expand All @@ -152,7 +152,7 @@ export function createSentryClientInstrumentation(
{
name: routePattern,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react-router.client-middleware',
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.client_middleware',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',
},
},
Expand All @@ -170,7 +170,7 @@ export function createSentryClientInstrumentation(
{
name: 'Lazy Route Load',
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react-router.client-lazy',
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.client_lazy',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export function createSentryServerInstrumentation(
{
name: routePattern,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react-router.loader',
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.loader',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',
},
},
Expand All @@ -136,7 +136,7 @@ export function createSentryServerInstrumentation(
{
name: routePattern,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react-router.action',
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.action',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',
},
},
Expand All @@ -162,7 +162,7 @@ export function createSentryServerInstrumentation(
{
name: routePattern,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react-router.middleware',
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.middleware',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',
},
},
Expand All @@ -181,7 +181,7 @@ export function createSentryServerInstrumentation(
{
name: 'Lazy Route Load',
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react-router.lazy',
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.lazy',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',
},
},
Expand Down
1 change: 1 addition & 0 deletions packages/react-router/src/server/wrapServerAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export function wrapServerAction<T>(
...options,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react_router.action',
// TODO: Fix span op to use snake_case ('function.react_router.action') per span op spec
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react-router.action',
...options.attributes,
},
Expand Down
1 change: 1 addition & 0 deletions packages/react-router/src/server/wrapServerLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export function wrapServerLoader<T>(
...options,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react_router.loader',
// TODO: Fix span op to use snake_case ('function.react_router.loader') per span op spec
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react-router.loader',
...options.attributes,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ describe('createSentryClientInstrumentation', () => {
expect.objectContaining({
name: 'Fetcher fetcher-1',
attributes: expect.objectContaining({
'sentry.op': 'function.react-router.fetcher',
'sentry.op': 'function.react_router.fetcher',
'sentry.origin': 'auto.function.react_router.instrumentation_api',
}),
}),
Expand Down Expand Up @@ -143,7 +143,7 @@ describe('createSentryClientInstrumentation', () => {
expect.objectContaining({
name: '/users/:id',
attributes: expect.objectContaining({
'sentry.op': 'function.react-router.client-loader',
'sentry.op': 'function.react_router.client_loader',
'sentry.origin': 'auto.function.react_router.instrumentation_api',
}),
}),
Expand Down Expand Up @@ -180,7 +180,7 @@ describe('createSentryClientInstrumentation', () => {
expect.objectContaining({
name: '/users/:id',
attributes: expect.objectContaining({
'sentry.op': 'function.react-router.client-action',
'sentry.op': 'function.react_router.client_action',
'sentry.origin': 'auto.function.react_router.instrumentation_api',
}),
}),
Expand Down Expand Up @@ -334,7 +334,7 @@ describe('createSentryClientInstrumentation', () => {
expect.objectContaining({
name: '/users/:id',
attributes: expect.objectContaining({
'sentry.op': 'function.react-router.client-middleware',
'sentry.op': 'function.react_router.client_middleware',
'sentry.origin': 'auto.function.react_router.instrumentation_api',
}),
}),
Expand Down Expand Up @@ -364,7 +364,7 @@ describe('createSentryClientInstrumentation', () => {
expect.objectContaining({
name: 'Lazy Route Load',
attributes: expect.objectContaining({
'sentry.op': 'function.react-router.client-lazy',
'sentry.op': 'function.react_router.client_lazy',
'sentry.origin': 'auto.function.react_router.instrumentation_api',
}),
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ describe('createSentryServerInstrumentation', () => {
expect.objectContaining({
name: '/users/:id',
attributes: expect.objectContaining({
'sentry.op': 'function.react-router.loader',
'sentry.op': 'function.react_router.loader',
'sentry.origin': 'auto.function.react_router.instrumentation_api',
}),
}),
Expand Down Expand Up @@ -249,7 +249,7 @@ describe('createSentryServerInstrumentation', () => {
expect.objectContaining({
name: '/users/:id',
attributes: expect.objectContaining({
'sentry.op': 'function.react-router.action',
'sentry.op': 'function.react_router.action',
'sentry.origin': 'auto.function.react_router.instrumentation_api',
}),
}),
Expand Down Expand Up @@ -289,7 +289,7 @@ describe('createSentryServerInstrumentation', () => {
expect.objectContaining({
name: '/users/:id',
attributes: expect.objectContaining({
'sentry.op': 'function.react-router.middleware',
'sentry.op': 'function.react_router.middleware',
'sentry.origin': 'auto.function.react_router.instrumentation_api',
}),
}),
Expand Down Expand Up @@ -330,7 +330,7 @@ describe('createSentryServerInstrumentation', () => {
expect.objectContaining({
name: 'Lazy Route Load',
attributes: expect.objectContaining({
'sentry.op': 'function.react-router.lazy',
'sentry.op': 'function.react_router.lazy',
'sentry.origin': 'auto.function.react_router.instrumentation_api',
}),
}),
Expand Down
Loading