From 52525edd838eaafcdc791e01c8980909a62b5da9 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Sun, 23 Mar 2025 21:20:00 +0100 Subject: [PATCH 01/10] add sentryHandleRequest --- packages/react-router/package.json | 1 + .../react-router/src/server/attributes.ts | 1 + packages/react-router/src/server/index.ts | 1 + packages/react-router/src/server/sdk.ts | 28 +++++++++++++- .../src/server/sentryHandleRequest.ts | 38 +++++++++++++++++++ 5 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 packages/react-router/src/server/attributes.ts create mode 100644 packages/react-router/src/server/sentryHandleRequest.ts diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 05604ea2d8d8..76dbfd91e251 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -39,6 +39,7 @@ "@sentry/core": "9.8.0", "@sentry/node": "9.8.0", "@sentry/vite-plugin": "^3.2.0", + "@opentelemetry/semantic-conventions": "^1.30.0", "glob": "11.0.1" }, "devDependencies": { diff --git a/packages/react-router/src/server/attributes.ts b/packages/react-router/src/server/attributes.ts new file mode 100644 index 000000000000..93a6edb56988 --- /dev/null +++ b/packages/react-router/src/server/attributes.ts @@ -0,0 +1 @@ +export const SENTRY_PARAMETERIZED_ROUTE = 'sentry.parameterized_route'; diff --git a/packages/react-router/src/server/index.ts b/packages/react-router/src/server/index.ts index 6ac8d97b4241..44acfec7d4f2 100644 --- a/packages/react-router/src/server/index.ts +++ b/packages/react-router/src/server/index.ts @@ -1,3 +1,4 @@ export * from '@sentry/node'; export { init } from './sdk'; +export { sentryHandleRequest } from './sentryHandleRequest'; diff --git a/packages/react-router/src/server/sdk.ts b/packages/react-router/src/server/sdk.ts index bae99dee4983..8094f76a2caa 100644 --- a/packages/react-router/src/server/sdk.ts +++ b/packages/react-router/src/server/sdk.ts @@ -1,6 +1,9 @@ -import { applySdkMetadata, setTag } from '@sentry/core'; +import { applySdkMetadata, logger, SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD, setTag } from '@sentry/core'; import type { NodeClient, NodeOptions } from '@sentry/node'; import { init as initNodeSdk } from '@sentry/node'; +import { DEBUG_BUILD } from '../common/debug-build'; +import { SENTRY_PARAMETERIZED_ROUTE } from './attributes'; +import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions'; /** * Initializes the server side of the React Router SDK @@ -10,11 +13,34 @@ export function init(options: NodeOptions): NodeClient | undefined { ...options, }; + DEBUG_BUILD && logger.log('Initializing SDK...'); + applySdkMetadata(opts, 'react-router', ['react-router', 'node']); const client = initNodeSdk(opts); setTag('runtime', 'node'); + client?.on('preprocessEvent', event => { + if (event.type === 'transaction' && event.transaction) { + // Check if the transaction name matches an HTTP method with a wildcard route (e.g. "GET *") + if (event.transaction.match(/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|TRACE|CONNECT) \*$/)) { + const traceData = event.contexts?.trace?.data; + if (traceData) { + // Get the parameterized route that was stored earlier by our wrapped handler (e.g. "/users/:id") + const paramRoute = traceData[SENTRY_PARAMETERIZED_ROUTE]; + if (paramRoute) { + traceData[ATTR_HTTP_ROUTE] = paramRoute; + const method = traceData[SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD] || traceData['http.method']; + if (method) { + event.transaction = `${method} ${paramRoute}`; + } + } + } + } + } + }); + + DEBUG_BUILD && logger.log('SDK successfully initialized'); return client; } diff --git a/packages/react-router/src/server/sentryHandleRequest.ts b/packages/react-router/src/server/sentryHandleRequest.ts new file mode 100644 index 000000000000..a93a7a2b56e5 --- /dev/null +++ b/packages/react-router/src/server/sentryHandleRequest.ts @@ -0,0 +1,38 @@ +import type { AppLoadContext, EntryContext } from 'react-router'; +import { SENTRY_PARAMETERIZED_ROUTE } from './attributes'; +import { getRootSpan, getActiveSpan } from '@sentry/core'; + +type OriginalHandleRequest = ( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + routerContext: EntryContext, + loadContext: AppLoadContext, +) => Promise; + +/** + * Wraps the original handleRequest function to add Sentry instrumentation. + * + * @param originalHandle - The original handleRequest function to wrap + * @returns A wrapped version of the handle request function with Sentry instrumentation + */ +export function sentryHandleRequest(originalHandle: OriginalHandleRequest): OriginalHandleRequest { + return async function sentryInstrumentedHandleRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + routerContext: EntryContext, + loadContext: AppLoadContext, + ) { + const parameterizedPath = + routerContext?.staticHandlerContext?.matches?.[routerContext.staticHandlerContext.matches.length - 1]?.route.path; + if (parameterizedPath) { + const activeSpan = getActiveSpan(); + if (activeSpan) { + const rootSpan = getRootSpan(activeSpan); + rootSpan.setAttribute(SENTRY_PARAMETERIZED_ROUTE, parameterizedPath); + } + } + return originalHandle(request, responseStatusCode, responseHeaders, routerContext, loadContext); + }; +} From 4ce7e388b83a3cf3d58b3a5259a8afad656118ae Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Tue, 25 Mar 2025 16:24:21 +0100 Subject: [PATCH 02/10] update tests --- .../react-router-7-framework/app/entry.server.tsx | 4 +++- .../tests/performance/performance.server.test.ts | 12 ++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework/app/entry.server.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework/app/entry.server.tsx index faa62bd97197..567edfe4e032 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework/app/entry.server.tsx +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework/app/entry.server.tsx @@ -9,7 +9,7 @@ import type { AppLoadContext, EntryContext } from 'react-router'; import { ServerRouter } from 'react-router'; const ABORT_DELAY = 5_000; -export default function handleRequest( +function handleRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, @@ -60,6 +60,8 @@ export default function handleRequest( }); } +export default Sentry.sentryHandleRequest(handleRequest); + import { type HandleErrorFunction } from 'react-router'; export const handleError: HandleErrorFunction = (error, { request }) => { diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/performance.server.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/performance.server.test.ts index f080d01064ea..d6c5a8e78c34 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/performance.server.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/performance.server.test.ts @@ -5,8 +5,7 @@ import { APP_NAME } from '../constants'; test.describe('servery - performance', () => { test('should send server transaction on pageload', async ({ page }) => { const txPromise = waitForTransaction(APP_NAME, async transactionEvent => { - // todo: should be GET /performance - return transactionEvent.transaction === 'GET *'; + return transactionEvent.transaction === 'GET performance'; }); await page.goto(`/performance`); @@ -30,8 +29,7 @@ test.describe('servery - performance', () => { spans: expect.any(Array), start_timestamp: expect.any(Number), timestamp: expect.any(Number), - // todo: should be GET /performance - transaction: 'GET *', + transaction: 'GET performance', type: 'transaction', transaction_info: { source: 'route' }, platform: 'node', @@ -58,8 +56,7 @@ test.describe('servery - performance', () => { test('should send server transaction on parameterized route', async ({ page }) => { const txPromise = waitForTransaction(APP_NAME, async transactionEvent => { - // todo: should be GET /performance/with/:param - return transactionEvent.transaction === 'GET *'; + return transactionEvent.transaction === 'GET performance/with/:param'; }); await page.goto(`/performance/with/some-param`); @@ -83,8 +80,7 @@ test.describe('servery - performance', () => { spans: expect.any(Array), start_timestamp: expect.any(Number), timestamp: expect.any(Number), - // todo: should be GET /performance/with/:param - transaction: 'GET *', + transaction: 'GET performance/with/:param', type: 'transaction', transaction_info: { source: 'route' }, platform: 'node', From b50b92d94de1045dea6865730d712960efc38848 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 31 Mar 2025 10:20:58 +0200 Subject: [PATCH 03/10] Remove unnecessary code --- .../performance/performance.server.test.ts | 8 +++---- .../react-router/src/server/attributes.ts | 1 - packages/react-router/src/server/sdk.ts | 24 +------------------ .../src/server/sentryHandleRequest.ts | 7 +++--- 4 files changed, 9 insertions(+), 31 deletions(-) delete mode 100644 packages/react-router/src/server/attributes.ts diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/performance.server.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/performance.server.test.ts index d6c5a8e78c34..4f570beca144 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/performance.server.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/performance.server.test.ts @@ -5,7 +5,7 @@ import { APP_NAME } from '../constants'; test.describe('servery - performance', () => { test('should send server transaction on pageload', async ({ page }) => { const txPromise = waitForTransaction(APP_NAME, async transactionEvent => { - return transactionEvent.transaction === 'GET performance'; + return transactionEvent.transaction === 'GET /performance'; }); await page.goto(`/performance`); @@ -29,7 +29,7 @@ test.describe('servery - performance', () => { spans: expect.any(Array), start_timestamp: expect.any(Number), timestamp: expect.any(Number), - transaction: 'GET performance', + transaction: 'GET /performance', type: 'transaction', transaction_info: { source: 'route' }, platform: 'node', @@ -56,7 +56,7 @@ test.describe('servery - performance', () => { test('should send server transaction on parameterized route', async ({ page }) => { const txPromise = waitForTransaction(APP_NAME, async transactionEvent => { - return transactionEvent.transaction === 'GET performance/with/:param'; + return transactionEvent.transaction === 'GET /performance/with/:param'; }); await page.goto(`/performance/with/some-param`); @@ -80,7 +80,7 @@ test.describe('servery - performance', () => { spans: expect.any(Array), start_timestamp: expect.any(Number), timestamp: expect.any(Number), - transaction: 'GET performance/with/:param', + transaction: 'GET /performance/with/:param', type: 'transaction', transaction_info: { source: 'route' }, platform: 'node', diff --git a/packages/react-router/src/server/attributes.ts b/packages/react-router/src/server/attributes.ts deleted file mode 100644 index 93a6edb56988..000000000000 --- a/packages/react-router/src/server/attributes.ts +++ /dev/null @@ -1 +0,0 @@ -export const SENTRY_PARAMETERIZED_ROUTE = 'sentry.parameterized_route'; diff --git a/packages/react-router/src/server/sdk.ts b/packages/react-router/src/server/sdk.ts index 8094f76a2caa..d1e6b32b1d96 100644 --- a/packages/react-router/src/server/sdk.ts +++ b/packages/react-router/src/server/sdk.ts @@ -1,9 +1,7 @@ -import { applySdkMetadata, logger, SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD, setTag } from '@sentry/core'; +import { applySdkMetadata, logger, setTag } from '@sentry/core'; import type { NodeClient, NodeOptions } from '@sentry/node'; import { init as initNodeSdk } from '@sentry/node'; import { DEBUG_BUILD } from '../common/debug-build'; -import { SENTRY_PARAMETERIZED_ROUTE } from './attributes'; -import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions'; /** * Initializes the server side of the React Router SDK @@ -21,26 +19,6 @@ export function init(options: NodeOptions): NodeClient | undefined { setTag('runtime', 'node'); - client?.on('preprocessEvent', event => { - if (event.type === 'transaction' && event.transaction) { - // Check if the transaction name matches an HTTP method with a wildcard route (e.g. "GET *") - if (event.transaction.match(/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|TRACE|CONNECT) \*$/)) { - const traceData = event.contexts?.trace?.data; - if (traceData) { - // Get the parameterized route that was stored earlier by our wrapped handler (e.g. "/users/:id") - const paramRoute = traceData[SENTRY_PARAMETERIZED_ROUTE]; - if (paramRoute) { - traceData[ATTR_HTTP_ROUTE] = paramRoute; - const method = traceData[SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD] || traceData['http.method']; - if (method) { - event.transaction = `${method} ${paramRoute}`; - } - } - } - } - } - }); - DEBUG_BUILD && logger.log('SDK successfully initialized'); return client; } diff --git a/packages/react-router/src/server/sentryHandleRequest.ts b/packages/react-router/src/server/sentryHandleRequest.ts index a93a7a2b56e5..b32c6093423b 100644 --- a/packages/react-router/src/server/sentryHandleRequest.ts +++ b/packages/react-router/src/server/sentryHandleRequest.ts @@ -1,6 +1,6 @@ +import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions'; +import { getActiveSpan, getRootSpan } from '@sentry/core'; import type { AppLoadContext, EntryContext } from 'react-router'; -import { SENTRY_PARAMETERIZED_ROUTE } from './attributes'; -import { getRootSpan, getActiveSpan } from '@sentry/core'; type OriginalHandleRequest = ( request: Request, @@ -30,7 +30,8 @@ export function sentryHandleRequest(originalHandle: OriginalHandleRequest): Orig const activeSpan = getActiveSpan(); if (activeSpan) { const rootSpan = getRootSpan(activeSpan); - rootSpan.setAttribute(SENTRY_PARAMETERIZED_ROUTE, parameterizedPath); + // The span exporter picks up the `http.route` (ATTR_HTTP_ROUTE) attribute to set the transaction name + rootSpan.setAttribute(ATTR_HTTP_ROUTE, `/${parameterizedPath}`); } } return originalHandle(request, responseStatusCode, responseHeaders, routerContext, loadContext); From 5f0b8fa5764433e2a8a05988cb36c0296c8e5c9d Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 31 Mar 2025 10:28:03 +0200 Subject: [PATCH 04/10] Set source route --- packages/react-router/src/server/sentryHandleRequest.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/react-router/src/server/sentryHandleRequest.ts b/packages/react-router/src/server/sentryHandleRequest.ts index b32c6093423b..f8aa03d60779 100644 --- a/packages/react-router/src/server/sentryHandleRequest.ts +++ b/packages/react-router/src/server/sentryHandleRequest.ts @@ -1,5 +1,5 @@ import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions'; -import { getActiveSpan, getRootSpan } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, getActiveSpan, getRootSpan } from '@sentry/core'; import type { AppLoadContext, EntryContext } from 'react-router'; type OriginalHandleRequest = ( @@ -31,7 +31,10 @@ export function sentryHandleRequest(originalHandle: OriginalHandleRequest): Orig if (activeSpan) { const rootSpan = getRootSpan(activeSpan); // The span exporter picks up the `http.route` (ATTR_HTTP_ROUTE) attribute to set the transaction name - rootSpan.setAttribute(ATTR_HTTP_ROUTE, `/${parameterizedPath}`); + rootSpan.setAttributes({ + [ATTR_HTTP_ROUTE]: `/${parameterizedPath}`, + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + }); } } return originalHandle(request, responseStatusCode, responseHeaders, routerContext, loadContext); From 5f118af7ad5464076407cbeeec4d5bc10f44d933 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 31 Mar 2025 12:15:39 +0200 Subject: [PATCH 05/10] log stuff --- .../tests/performance/performance.server.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/performance.server.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/performance.server.test.ts index 4f570beca144..b3c3fbbfaae6 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/performance.server.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/performance.server.test.ts @@ -5,6 +5,7 @@ import { APP_NAME } from '../constants'; test.describe('servery - performance', () => { test('should send server transaction on pageload', async ({ page }) => { const txPromise = waitForTransaction(APP_NAME, async transactionEvent => { + console.log('t', transactionEvent.transaction, transactionEvent.contexts?.trace?.data); return transactionEvent.transaction === 'GET /performance'; }); From ae9a866edc0745e644c463718578a078261eedf5 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 31 Mar 2025 12:41:00 +0200 Subject: [PATCH 06/10] log some more --- packages/react-router/src/server/sentryHandleRequest.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-router/src/server/sentryHandleRequest.ts b/packages/react-router/src/server/sentryHandleRequest.ts index f8aa03d60779..233b11bad545 100644 --- a/packages/react-router/src/server/sentryHandleRequest.ts +++ b/packages/react-router/src/server/sentryHandleRequest.ts @@ -30,6 +30,7 @@ export function sentryHandleRequest(originalHandle: OriginalHandleRequest): Orig const activeSpan = getActiveSpan(); if (activeSpan) { const rootSpan = getRootSpan(activeSpan); + console.log('Setting route', parameterizedPath); // The span exporter picks up the `http.route` (ATTR_HTTP_ROUTE) attribute to set the transaction name rootSpan.setAttributes({ [ATTR_HTTP_ROUTE]: `/${parameterizedPath}`, From 1dc8c352575d7ccb602982b692f3c3d43c15a6b3 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 31 Mar 2025 13:45:02 +0200 Subject: [PATCH 07/10] . --- packages/react-router/src/server/sentryHandleRequest.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-router/src/server/sentryHandleRequest.ts b/packages/react-router/src/server/sentryHandleRequest.ts index 233b11bad545..daa0eeb8bdb9 100644 --- a/packages/react-router/src/server/sentryHandleRequest.ts +++ b/packages/react-router/src/server/sentryHandleRequest.ts @@ -35,6 +35,7 @@ export function sentryHandleRequest(originalHandle: OriginalHandleRequest): Orig rootSpan.setAttributes({ [ATTR_HTTP_ROUTE]: `/${parameterizedPath}`, [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + foobar: 'baz', }); } } From 20a55ea14adb40cdf9b56b8da7bf1457bc637899 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 31 Mar 2025 14:34:33 +0200 Subject: [PATCH 08/10] fix deps --- .../react-router-7-framework/package.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework/package.json b/dev-packages/e2e-tests/test-applications/react-router-7-framework/package.json index cdd96f39569e..a9afbbfcd07b 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework/package.json +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework/package.json @@ -10,6 +10,15 @@ "@react-router/node": "^7.1.5", "@react-router/serve": "^7.1.5", "@sentry/react-router": "latest || *", + "@sentry-internal/feedback": "latest || *", + "@sentry-internal/replay-canvas": "latest || *", + "@sentry-internal/browser-utils": "latest || *", + "@sentry/browser": "latest || *", + "@sentry/core": "latest || *", + "@sentry/node": "latest || *", + "@sentry/opentelemetry": "latest || *", + "@sentry/react": "latest || *", + "@sentry-internal/replay": "latest || *", "isbot": "^5.1.17" }, "devDependencies": { From 089feed885de9ee13581337686f6ce50d94d2a99 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 31 Mar 2025 14:51:59 +0200 Subject: [PATCH 09/10] i am stomped --- packages/react-router/src/server/sentryHandleRequest.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-router/src/server/sentryHandleRequest.ts b/packages/react-router/src/server/sentryHandleRequest.ts index daa0eeb8bdb9..65be3128699c 100644 --- a/packages/react-router/src/server/sentryHandleRequest.ts +++ b/packages/react-router/src/server/sentryHandleRequest.ts @@ -30,12 +30,12 @@ export function sentryHandleRequest(originalHandle: OriginalHandleRequest): Orig const activeSpan = getActiveSpan(); if (activeSpan) { const rootSpan = getRootSpan(activeSpan); - console.log('Setting route', parameterizedPath); + + rootSpan.updateName(`${request.method} /${parameterizedPath}`); // The span exporter picks up the `http.route` (ATTR_HTTP_ROUTE) attribute to set the transaction name rootSpan.setAttributes({ [ATTR_HTTP_ROUTE]: `/${parameterizedPath}`, [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - foobar: 'baz', }); } } From 4ea05a528309b9bd7384294ebddda03783b4acb2 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Thu, 3 Apr 2025 12:04:56 +0200 Subject: [PATCH 10/10] holy shit I love this job sometimes why does software exist i will become a potato farmer --- .../tests/performance/performance.server.test.ts | 1 - packages/react-router/package.json | 2 ++ .../react-router/src/server/sentryHandleRequest.ts | 12 ++++++++++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/performance.server.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/performance.server.test.ts index b3c3fbbfaae6..4f570beca144 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/performance.server.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/performance.server.test.ts @@ -5,7 +5,6 @@ import { APP_NAME } from '../constants'; test.describe('servery - performance', () => { test('should send server transaction on pageload', async ({ page }) => { const txPromise = waitForTransaction(APP_NAME, async transactionEvent => { - console.log('t', transactionEvent.transaction, transactionEvent.contexts?.trace?.data); return transactionEvent.transaction === 'GET /performance'; }); diff --git a/packages/react-router/package.json b/packages/react-router/package.json index ad08c05e45ab..d943fab5dd76 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -40,6 +40,8 @@ "@sentry/node": "9.10.1", "@sentry/vite-plugin": "^3.2.4", "@opentelemetry/semantic-conventions": "^1.30.0", + "@opentelemetry/core": "^1.30.1", + "@opentelemetry/api": "^1.9.0", "glob": "11.0.1" }, "devDependencies": { diff --git a/packages/react-router/src/server/sentryHandleRequest.ts b/packages/react-router/src/server/sentryHandleRequest.ts index 65be3128699c..9c5f4abf72e8 100644 --- a/packages/react-router/src/server/sentryHandleRequest.ts +++ b/packages/react-router/src/server/sentryHandleRequest.ts @@ -1,3 +1,5 @@ +import { context } from '@opentelemetry/api'; +import { RPCType, getRPCMetadata } from '@opentelemetry/core'; import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions'; import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, getActiveSpan, getRootSpan } from '@sentry/core'; import type { AppLoadContext, EntryContext } from 'react-router'; @@ -30,11 +32,17 @@ export function sentryHandleRequest(originalHandle: OriginalHandleRequest): Orig const activeSpan = getActiveSpan(); if (activeSpan) { const rootSpan = getRootSpan(activeSpan); + const routeName = `/${parameterizedPath}`; + + // The express instrumentation writes on the rpcMetadata and that ends up stomping on the `http.route` attribute. + const rpcMetadata = getRPCMetadata(context.active()); + if (rpcMetadata?.type === RPCType.HTTP) { + rpcMetadata.route = routeName; + } - rootSpan.updateName(`${request.method} /${parameterizedPath}`); // The span exporter picks up the `http.route` (ATTR_HTTP_ROUTE) attribute to set the transaction name rootSpan.setAttributes({ - [ATTR_HTTP_ROUTE]: `/${parameterizedPath}`, + [ATTR_HTTP_ROUTE]: routeName, [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', }); }