Skip to content

Commit 52525ed

Browse files
committed
add sentryHandleRequest
1 parent 50d2514 commit 52525ed

File tree

5 files changed

+68
-1
lines changed

5 files changed

+68
-1
lines changed

packages/react-router/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"@sentry/core": "9.8.0",
4040
"@sentry/node": "9.8.0",
4141
"@sentry/vite-plugin": "^3.2.0",
42+
"@opentelemetry/semantic-conventions": "^1.30.0",
4243
"glob": "11.0.1"
4344
},
4445
"devDependencies": {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const SENTRY_PARAMETERIZED_ROUTE = 'sentry.parameterized_route';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from '@sentry/node';
22

33
export { init } from './sdk';
4+
export { sentryHandleRequest } from './sentryHandleRequest';

packages/react-router/src/server/sdk.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
import { applySdkMetadata, setTag } from '@sentry/core';
1+
import { applySdkMetadata, logger, SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD, setTag } from '@sentry/core';
22
import type { NodeClient, NodeOptions } from '@sentry/node';
33
import { init as initNodeSdk } from '@sentry/node';
4+
import { DEBUG_BUILD } from '../common/debug-build';
5+
import { SENTRY_PARAMETERIZED_ROUTE } from './attributes';
6+
import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions';
47

58
/**
69
* Initializes the server side of the React Router SDK
@@ -10,11 +13,34 @@ export function init(options: NodeOptions): NodeClient | undefined {
1013
...options,
1114
};
1215

16+
DEBUG_BUILD && logger.log('Initializing SDK...');
17+
1318
applySdkMetadata(opts, 'react-router', ['react-router', 'node']);
1419

1520
const client = initNodeSdk(opts);
1621

1722
setTag('runtime', 'node');
1823

24+
client?.on('preprocessEvent', event => {
25+
if (event.type === 'transaction' && event.transaction) {
26+
// Check if the transaction name matches an HTTP method with a wildcard route (e.g. "GET *")
27+
if (event.transaction.match(/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|TRACE|CONNECT) \*$/)) {
28+
const traceData = event.contexts?.trace?.data;
29+
if (traceData) {
30+
// Get the parameterized route that was stored earlier by our wrapped handler (e.g. "/users/:id")
31+
const paramRoute = traceData[SENTRY_PARAMETERIZED_ROUTE];
32+
if (paramRoute) {
33+
traceData[ATTR_HTTP_ROUTE] = paramRoute;
34+
const method = traceData[SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD] || traceData['http.method'];
35+
if (method) {
36+
event.transaction = `${method} ${paramRoute}`;
37+
}
38+
}
39+
}
40+
}
41+
}
42+
});
43+
44+
DEBUG_BUILD && logger.log('SDK successfully initialized');
1945
return client;
2046
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import type { AppLoadContext, EntryContext } from 'react-router';
2+
import { SENTRY_PARAMETERIZED_ROUTE } from './attributes';
3+
import { getRootSpan, getActiveSpan } from '@sentry/core';
4+
5+
type OriginalHandleRequest = (
6+
request: Request,
7+
responseStatusCode: number,
8+
responseHeaders: Headers,
9+
routerContext: EntryContext,
10+
loadContext: AppLoadContext,
11+
) => Promise<unknown>;
12+
13+
/**
14+
* Wraps the original handleRequest function to add Sentry instrumentation.
15+
*
16+
* @param originalHandle - The original handleRequest function to wrap
17+
* @returns A wrapped version of the handle request function with Sentry instrumentation
18+
*/
19+
export function sentryHandleRequest(originalHandle: OriginalHandleRequest): OriginalHandleRequest {
20+
return async function sentryInstrumentedHandleRequest(
21+
request: Request,
22+
responseStatusCode: number,
23+
responseHeaders: Headers,
24+
routerContext: EntryContext,
25+
loadContext: AppLoadContext,
26+
) {
27+
const parameterizedPath =
28+
routerContext?.staticHandlerContext?.matches?.[routerContext.staticHandlerContext.matches.length - 1]?.route.path;
29+
if (parameterizedPath) {
30+
const activeSpan = getActiveSpan();
31+
if (activeSpan) {
32+
const rootSpan = getRootSpan(activeSpan);
33+
rootSpan.setAttribute(SENTRY_PARAMETERIZED_ROUTE, parameterizedPath);
34+
}
35+
}
36+
return originalHandle(request, responseStatusCode, responseHeaders, routerContext, loadContext);
37+
};
38+
}

0 commit comments

Comments
 (0)