@sentry/react-router 9.38.0 → 9.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/build/cjs/client/hydratedRouter.js +39 -26
  2. package/build/cjs/client/hydratedRouter.js.map +1 -1
  3. package/build/cjs/server/instrumentation/reactRouter.js +9 -4
  4. package/build/cjs/server/instrumentation/reactRouter.js.map +1 -1
  5. package/build/cjs/server/instrumentation/util.js +0 -3
  6. package/build/cjs/server/instrumentation/util.js.map +1 -1
  7. package/build/cjs/server/integration/reactRouterServer.js +24 -1
  8. package/build/cjs/server/integration/reactRouterServer.js.map +1 -1
  9. package/build/cjs/server/sdk.js +5 -37
  10. package/build/cjs/server/sdk.js.map +1 -1
  11. package/build/cjs/server/wrapSentryHandleRequest.js +15 -16
  12. package/build/cjs/server/wrapSentryHandleRequest.js.map +1 -1
  13. package/build/cjs/server/wrapServerAction.js +12 -7
  14. package/build/cjs/server/wrapServerAction.js.map +1 -1
  15. package/build/cjs/server/wrapServerLoader.js +12 -7
  16. package/build/cjs/server/wrapServerLoader.js.map +1 -1
  17. package/build/esm/client/hydratedRouter.js +40 -27
  18. package/build/esm/client/hydratedRouter.js.map +1 -1
  19. package/build/esm/package.json +1 -1
  20. package/build/esm/server/instrumentation/reactRouter.js +11 -6
  21. package/build/esm/server/instrumentation/reactRouter.js.map +1 -1
  22. package/build/esm/server/instrumentation/util.js +1 -3
  23. package/build/esm/server/instrumentation/util.js.map +1 -1
  24. package/build/esm/server/integration/reactRouterServer.js +25 -2
  25. package/build/esm/server/integration/reactRouterServer.js.map +1 -1
  26. package/build/esm/server/sdk.js +7 -39
  27. package/build/esm/server/sdk.js.map +1 -1
  28. package/build/esm/server/wrapSentryHandleRequest.js +16 -17
  29. package/build/esm/server/wrapSentryHandleRequest.js.map +1 -1
  30. package/build/esm/server/wrapServerAction.js +13 -8
  31. package/build/esm/server/wrapServerAction.js.map +1 -1
  32. package/build/esm/server/wrapServerLoader.js +13 -8
  33. package/build/esm/server/wrapServerLoader.js.map +1 -1
  34. package/build/types/client/hydratedRouter.d.ts.map +1 -1
  35. package/build/types/server/instrumentation/reactRouter.d.ts.map +1 -1
  36. package/build/types/server/integration/reactRouterServer.d.ts.map +1 -1
  37. package/build/types/server/sdk.d.ts.map +1 -1
  38. package/build/types/server/wrapSentryHandleRequest.d.ts.map +1 -1
  39. package/build/types/server/wrapServerAction.d.ts.map +1 -1
  40. package/build/types/server/wrapServerLoader.d.ts.map +1 -1
  41. package/package.json +5 -5
@@ -1,5 +1,5 @@
1
1
  import { startBrowserTracingNavigationSpan } from '@sentry/browser';
2
- import { GLOBAL_OBJ, spanToJSON, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, consoleSandbox, getActiveSpan, getRootSpan, getClient, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_OP } from '@sentry/core';
2
+ import { GLOBAL_OBJ, spanToJSON, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, consoleSandbox, getActiveSpan, getRootSpan, getClient, SEMANTIC_ATTRIBUTE_SENTRY_OP } from '@sentry/core';
3
3
  import { DEBUG_BUILD } from '../common/debug-build.js';
4
4
 
5
5
  const GLOBAL_OBJ_WITH_DATA_ROUTER = GLOBAL_OBJ
@@ -24,43 +24,56 @@ function instrumentHydratedRouter() {
24
24
  // The first time we hit the router, we try to update the pageload transaction
25
25
  // todo: update pageload tx here
26
26
  const pageloadSpan = getActiveRootSpan();
27
- const pageloadName = pageloadSpan ? spanToJSON(pageloadSpan).description : undefined;
28
- const parameterizePageloadRoute = getParameterizedRoute(router.state);
29
- if (
30
- pageloadName &&
31
- normalizePathname(router.state.location.pathname) === normalizePathname(pageloadName) && // this event is for the currently active pageload
32
- normalizePathname(parameterizePageloadRoute) !== normalizePathname(pageloadName) // route is not parameterized yet
33
- ) {
34
- pageloadSpan?.updateName(parameterizePageloadRoute);
35
- pageloadSpan?.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
36
- }
37
27
 
38
- // Patching navigate for creating accurate navigation transactions
39
- if (typeof router.navigate === 'function') {
40
- const originalNav = router.navigate.bind(router);
41
- router.navigate = function sentryPatchedNavigate(...args) {
42
- maybeCreateNavigationTransaction(
43
- String(args[0]) || '<unknown route>', // will be updated anyway
44
- 'url', // this also will be updated once we have the parameterized route
45
- );
46
- return originalNav(...args);
47
- };
28
+ if (pageloadSpan) {
29
+ const pageloadName = spanToJSON(pageloadSpan).description;
30
+ const parameterizePageloadRoute = getParameterizedRoute(router.state);
31
+ if (
32
+ pageloadName &&
33
+ // this event is for the currently active pageload
34
+ normalizePathname(router.state.location.pathname) === normalizePathname(pageloadName)
35
+ ) {
36
+ pageloadSpan.updateName(parameterizePageloadRoute);
37
+ pageloadSpan.setAttributes({
38
+ [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
39
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react-router',
40
+ });
41
+ }
42
+
43
+ // Patching navigate for creating accurate navigation transactions
44
+ if (typeof router.navigate === 'function') {
45
+ const originalNav = router.navigate.bind(router);
46
+ router.navigate = function sentryPatchedNavigate(...args) {
47
+ maybeCreateNavigationTransaction(
48
+ String(args[0]) || '<unknown route>', // will be updated anyway
49
+ 'url', // this also will be updated once we have the parameterized route
50
+ );
51
+ return originalNav(...args);
52
+ };
53
+ }
48
54
  }
49
55
 
50
56
  // Subscribe to router state changes to update navigation transactions with parameterized routes
51
57
  router.subscribe(newState => {
52
58
  const navigationSpan = getActiveRootSpan();
53
- const navigationSpanName = navigationSpan ? spanToJSON(navigationSpan).description : undefined;
59
+
60
+ if (!navigationSpan) {
61
+ return;
62
+ }
63
+
64
+ const navigationSpanName = spanToJSON(navigationSpan).description;
54
65
  const parameterizedNavRoute = getParameterizedRoute(newState);
55
66
 
56
67
  if (
57
- navigationSpanName && // we have an active pageload tx
68
+ navigationSpanName &&
58
69
  newState.navigation.state === 'idle' && // navigation has completed
59
- normalizePathname(newState.location.pathname) === normalizePathname(navigationSpanName) && // this event is for the currently active navigation
60
- normalizePathname(parameterizedNavRoute) !== normalizePathname(navigationSpanName) // route is not parameterized yet
70
+ normalizePathname(newState.location.pathname) === normalizePathname(navigationSpanName) // this event is for the currently active navigation
61
71
  ) {
62
- navigationSpan?.updateName(parameterizedNavRoute);
63
- navigationSpan?.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
72
+ navigationSpan.updateName(parameterizedNavRoute);
73
+ navigationSpan.setAttributes({
74
+ [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
75
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react-router',
76
+ });
64
77
  }
65
78
  });
66
79
  return true;
@@ -1 +1 @@
1
- {"version":3,"file":"hydratedRouter.js","sources":["../../../src/client/hydratedRouter.ts"],"sourcesContent":["import { startBrowserTracingNavigationSpan } from '@sentry/browser';\nimport type { Span } from '@sentry/core';\nimport {\n consoleSandbox,\n getActiveSpan,\n getClient,\n getRootSpan,\n GLOBAL_OBJ,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,\n spanToJSON,\n} from '@sentry/core';\nimport type { DataRouter, RouterState } from 'react-router';\nimport { DEBUG_BUILD } from '../common/debug-build';\n\nconst GLOBAL_OBJ_WITH_DATA_ROUTER = GLOBAL_OBJ as typeof GLOBAL_OBJ & {\n __reactRouterDataRouter?: DataRouter;\n};\n\nconst MAX_RETRIES = 40; // 2 seconds at 50ms interval\n\n/**\n * Instruments the React Router Data Router for pageloads and navigation.\n *\n * This function waits for the router to be available after hydration, then:\n * 1. Updates the pageload transaction with parameterized route info\n * 2. Patches router.navigate() to create navigation transactions\n * 3. Subscribes to router state changes to update navigation transactions with parameterized routes\n */\nexport function instrumentHydratedRouter(): void {\n function trySubscribe(): boolean {\n const router = GLOBAL_OBJ_WITH_DATA_ROUTER.__reactRouterDataRouter;\n\n if (router) {\n // The first time we hit the router, we try to update the pageload transaction\n // todo: update pageload tx here\n const pageloadSpan = getActiveRootSpan();\n const pageloadName = pageloadSpan ? spanToJSON(pageloadSpan).description : undefined;\n const parameterizePageloadRoute = getParameterizedRoute(router.state);\n if (\n pageloadName &&\n normalizePathname(router.state.location.pathname) === normalizePathname(pageloadName) && // this event is for the currently active pageload\n normalizePathname(parameterizePageloadRoute) !== normalizePathname(pageloadName) // route is not parameterized yet\n ) {\n pageloadSpan?.updateName(parameterizePageloadRoute);\n pageloadSpan?.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');\n }\n\n // Patching navigate for creating accurate navigation transactions\n if (typeof router.navigate === 'function') {\n const originalNav = router.navigate.bind(router);\n router.navigate = function sentryPatchedNavigate(...args) {\n maybeCreateNavigationTransaction(\n String(args[0]) || '<unknown route>', // will be updated anyway\n 'url', // this also will be updated once we have the parameterized route\n );\n return originalNav(...args);\n };\n }\n\n // Subscribe to router state changes to update navigation transactions with parameterized routes\n router.subscribe(newState => {\n const navigationSpan = getActiveRootSpan();\n const navigationSpanName = navigationSpan ? spanToJSON(navigationSpan).description : undefined;\n const parameterizedNavRoute = getParameterizedRoute(newState);\n\n if (\n navigationSpanName && // we have an active pageload tx\n newState.navigation.state === 'idle' && // navigation has completed\n normalizePathname(newState.location.pathname) === normalizePathname(navigationSpanName) && // this event is for the currently active navigation\n normalizePathname(parameterizedNavRoute) !== normalizePathname(navigationSpanName) // route is not parameterized yet\n ) {\n navigationSpan?.updateName(parameterizedNavRoute);\n navigationSpan?.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');\n }\n });\n return true;\n }\n return false;\n }\n\n // Wait until the router is available (since the SDK loads before hydration)\n if (!trySubscribe()) {\n let retryCount = 0;\n // Retry until the router is available or max retries reached\n const interval = setInterval(() => {\n if (trySubscribe() || retryCount >= MAX_RETRIES) {\n if (retryCount >= MAX_RETRIES) {\n DEBUG_BUILD &&\n consoleSandbox(() => {\n // eslint-disable-next-line no-console\n console.warn('Unable to instrument React Router: router not found after hydration.');\n });\n }\n clearInterval(interval);\n }\n retryCount++;\n }, 50);\n }\n}\n\nfunction maybeCreateNavigationTransaction(name: string, source: 'url' | 'route'): Span | undefined {\n const client = getClient();\n\n if (!client) {\n return undefined;\n }\n\n return startBrowserTracingNavigationSpan(client, {\n name,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react-router',\n },\n });\n}\n\nfunction getActiveRootSpan(): Span | undefined {\n const activeSpan = getActiveSpan();\n if (!activeSpan) {\n return undefined;\n }\n\n const rootSpan = getRootSpan(activeSpan);\n\n const op = spanToJSON(rootSpan).op;\n\n // Only use this root span if it is a pageload or navigation span\n return op === 'navigation' || op === 'pageload' ? rootSpan : undefined;\n}\n\nfunction getParameterizedRoute(routerState: RouterState): string {\n const lastMatch = routerState.matches[routerState.matches.length - 1];\n return normalizePathname(lastMatch?.route.path ?? routerState.location.pathname);\n}\n\nfunction normalizePathname(pathname: string): string {\n // Ensure it starts with a single slash\n let normalized = pathname.startsWith('/') ? pathname : `/${pathname}`;\n // Remove trailing slash unless it's the root\n if (normalized.length > 1 && normalized.endsWith('/')) {\n normalized = normalized.slice(0, -1);\n }\n return normalized;\n}\n"],"names":[],"mappings":";;;;AAgBA,MAAM,2BAAA,GAA8B;;AAEpC;;AAEA,MAAM,WAAA,GAAc,EAAE,CAAA;;AAEtB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,wBAAwB,GAAS;AACjD,EAAE,SAAS,YAAY,GAAY;AACnC,IAAI,MAAM,MAAA,GAAS,2BAA2B,CAAC,uBAAuB;;AAEtE,IAAI,IAAI,MAAM,EAAE;AAChB;AACA;AACA,MAAM,MAAM,YAAA,GAAe,iBAAiB,EAAE;AAC9C,MAAM,MAAM,YAAA,GAAe,YAAA,GAAe,UAAU,CAAC,YAAY,CAAC,CAAC,WAAA,GAAc,SAAS;AAC1F,MAAM,MAAM,4BAA4B,qBAAqB,CAAC,MAAM,CAAC,KAAK,CAAC;AAC3E,MAAM;AACN,QAAQ,YAAA;AACR,QAAQ,iBAAiB,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAA,KAAM,iBAAiB,CAAC,YAAY,CAAA;AAC5F,QAAQ,iBAAiB,CAAC,yBAAyB,MAAM,iBAAiB,CAAC,YAAY,CAAA;AACvF,QAAQ;AACR,QAAQ,YAAY,EAAE,UAAU,CAAC,yBAAyB,CAAC;AAC3D,QAAQ,YAAY,EAAE,YAAY,CAAC,gCAAgC,EAAE,OAAO,CAAC;AAC7E;;AAEA;AACA,MAAM,IAAI,OAAO,MAAM,CAAC,QAAA,KAAa,UAAU,EAAE;AACjD,QAAQ,MAAM,WAAA,GAAc,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC;AACxD,QAAQ,MAAM,CAAC,QAAA,GAAW,SAAS,qBAAqB,CAAC,GAAG,IAAI,EAAE;AAClE,UAAU,gCAAgC;AAC1C,YAAY,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA,IAAK,iBAAiB;AAChD,YAAY,KAAK;AACjB,WAAW;AACX,UAAU,OAAO,WAAW,CAAC,GAAG,IAAI,CAAC;AACrC,SAAS;AACT;;AAEA;AACA,MAAM,MAAM,CAAC,SAAS,CAAC,YAAY;AACnC,QAAQ,MAAM,cAAA,GAAiB,iBAAiB,EAAE;AAClD,QAAQ,MAAM,kBAAA,GAAqB,cAAA,GAAiB,UAAU,CAAC,cAAc,CAAC,CAAC,WAAA,GAAc,SAAS;AACtG,QAAQ,MAAM,qBAAA,GAAwB,qBAAqB,CAAC,QAAQ,CAAC;;AAErE,QAAQ;AACR,UAAU,kBAAA;AACV,UAAU,QAAQ,CAAC,UAAU,CAAC,KAAA,KAAU,MAAA;AACxC,UAAU,iBAAiB,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAA,KAAM,iBAAiB,CAAC,kBAAkB,CAAA;AAChG,UAAU,iBAAiB,CAAC,qBAAqB,MAAM,iBAAiB,CAAC,kBAAkB,CAAA;AAC3F,UAAU;AACV,UAAU,cAAc,EAAE,UAAU,CAAC,qBAAqB,CAAC;AAC3D,UAAU,cAAc,EAAE,YAAY,CAAC,gCAAgC,EAAE,OAAO,CAAC;AACjF;AACA,OAAO,CAAC;AACR,MAAM,OAAO,IAAI;AACjB;AACA,IAAI,OAAO,KAAK;AAChB;;AAEA;AACA,EAAE,IAAI,CAAC,YAAY,EAAE,EAAE;AACvB,IAAI,IAAI,UAAA,GAAa,CAAC;AACtB;AACA,IAAI,MAAM,QAAA,GAAW,WAAW,CAAC,MAAM;AACvC,MAAM,IAAI,YAAY,MAAM,UAAA,IAAc,WAAW,EAAE;AACvD,QAAQ,IAAI,UAAA,IAAc,WAAW,EAAE;AACvC,UAAU,WAAA;AACV,YAAY,cAAc,CAAC,MAAM;AACjC;AACA,cAAc,OAAO,CAAC,IAAI,CAAC,sEAAsE,CAAC;AAClG,aAAa,CAAC;AACd;AACA,QAAQ,aAAa,CAAC,QAAQ,CAAC;AAC/B;AACA,MAAM,UAAU,EAAE;AAClB,KAAK,EAAE,EAAE,CAAC;AACV;AACA;;AAEA,SAAS,gCAAgC,CAAC,IAAI,EAAU,MAAM,EAAqC;AACnG,EAAE,MAAM,MAAA,GAAS,SAAS,EAAE;;AAE5B,EAAE,IAAI,CAAC,MAAM,EAAE;AACf,IAAI,OAAO,SAAS;AACpB;;AAEA,EAAE,OAAO,iCAAiC,CAAC,MAAM,EAAE;AACnD,IAAI,IAAI;AACR,IAAI,UAAU,EAAE;AAChB,MAAM,CAAC,gCAAgC,GAAG,MAAM;AAChD,MAAM,CAAC,4BAA4B,GAAG,YAAY;AAClD,MAAM,CAAC,gCAAgC,GAAG,8BAA8B;AACxE,KAAK;AACL,GAAG,CAAC;AACJ;;AAEA,SAAS,iBAAiB,GAAqB;AAC/C,EAAE,MAAM,UAAA,GAAa,aAAa,EAAE;AACpC,EAAE,IAAI,CAAC,UAAU,EAAE;AACnB,IAAI,OAAO,SAAS;AACpB;;AAEA,EAAE,MAAM,QAAA,GAAW,WAAW,CAAC,UAAU,CAAC;;AAE1C,EAAE,MAAM,KAAK,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE;;AAEpC;AACA,EAAE,OAAO,EAAA,KAAO,YAAA,IAAgB,EAAA,KAAO,UAAA,GAAa,QAAA,GAAW,SAAS;AACxE;;AAEA,SAAS,qBAAqB,CAAC,WAAW,EAAuB;AACjE,EAAE,MAAM,SAAA,GAAY,WAAW,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,MAAA,GAAS,CAAC,CAAC;AACvE,EAAE,OAAO,iBAAiB,CAAC,SAAS,EAAE,KAAK,CAAC,IAAA,IAAQ,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC;AAClF;;AAEA,SAAS,iBAAiB,CAAC,QAAQ,EAAkB;AACrD;AACA,EAAE,IAAI,UAAA,GAAa,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAA,GAAI,WAAW,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAA;AACA;AACA,EAAA,IAAA,UAAA,CAAA,MAAA,GAAA,CAAA,IAAA,UAAA,CAAA,QAAA,CAAA,GAAA,CAAA,EAAA;AACA,IAAA,UAAA,GAAA,UAAA,CAAA,KAAA,CAAA,CAAA,EAAA,EAAA,CAAA;AACA;AACA,EAAA,OAAA,UAAA;AACA;;;;"}
1
+ {"version":3,"file":"hydratedRouter.js","sources":["../../../src/client/hydratedRouter.ts"],"sourcesContent":["import { startBrowserTracingNavigationSpan } from '@sentry/browser';\nimport type { Span } from '@sentry/core';\nimport {\n consoleSandbox,\n getActiveSpan,\n getClient,\n getRootSpan,\n GLOBAL_OBJ,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,\n spanToJSON,\n} from '@sentry/core';\nimport type { DataRouter, RouterState } from 'react-router';\nimport { DEBUG_BUILD } from '../common/debug-build';\n\nconst GLOBAL_OBJ_WITH_DATA_ROUTER = GLOBAL_OBJ as typeof GLOBAL_OBJ & {\n __reactRouterDataRouter?: DataRouter;\n};\n\nconst MAX_RETRIES = 40; // 2 seconds at 50ms interval\n\n/**\n * Instruments the React Router Data Router for pageloads and navigation.\n *\n * This function waits for the router to be available after hydration, then:\n * 1. Updates the pageload transaction with parameterized route info\n * 2. Patches router.navigate() to create navigation transactions\n * 3. Subscribes to router state changes to update navigation transactions with parameterized routes\n */\nexport function instrumentHydratedRouter(): void {\n function trySubscribe(): boolean {\n const router = GLOBAL_OBJ_WITH_DATA_ROUTER.__reactRouterDataRouter;\n\n if (router) {\n // The first time we hit the router, we try to update the pageload transaction\n // todo: update pageload tx here\n const pageloadSpan = getActiveRootSpan();\n\n if (pageloadSpan) {\n const pageloadName = spanToJSON(pageloadSpan).description;\n const parameterizePageloadRoute = getParameterizedRoute(router.state);\n if (\n pageloadName &&\n // this event is for the currently active pageload\n normalizePathname(router.state.location.pathname) === normalizePathname(pageloadName)\n ) {\n pageloadSpan.updateName(parameterizePageloadRoute);\n pageloadSpan.setAttributes({\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react-router',\n });\n }\n\n // Patching navigate for creating accurate navigation transactions\n if (typeof router.navigate === 'function') {\n const originalNav = router.navigate.bind(router);\n router.navigate = function sentryPatchedNavigate(...args) {\n maybeCreateNavigationTransaction(\n String(args[0]) || '<unknown route>', // will be updated anyway\n 'url', // this also will be updated once we have the parameterized route\n );\n return originalNav(...args);\n };\n }\n }\n\n // Subscribe to router state changes to update navigation transactions with parameterized routes\n router.subscribe(newState => {\n const navigationSpan = getActiveRootSpan();\n\n if (!navigationSpan) {\n return;\n }\n\n const navigationSpanName = spanToJSON(navigationSpan).description;\n const parameterizedNavRoute = getParameterizedRoute(newState);\n\n if (\n navigationSpanName &&\n newState.navigation.state === 'idle' && // navigation has completed\n normalizePathname(newState.location.pathname) === normalizePathname(navigationSpanName) // this event is for the currently active navigation\n ) {\n navigationSpan.updateName(parameterizedNavRoute);\n navigationSpan.setAttributes({\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react-router',\n });\n }\n });\n return true;\n }\n return false;\n }\n\n // Wait until the router is available (since the SDK loads before hydration)\n if (!trySubscribe()) {\n let retryCount = 0;\n // Retry until the router is available or max retries reached\n const interval = setInterval(() => {\n if (trySubscribe() || retryCount >= MAX_RETRIES) {\n if (retryCount >= MAX_RETRIES) {\n DEBUG_BUILD &&\n consoleSandbox(() => {\n // eslint-disable-next-line no-console\n console.warn('Unable to instrument React Router: router not found after hydration.');\n });\n }\n clearInterval(interval);\n }\n retryCount++;\n }, 50);\n }\n}\n\nfunction maybeCreateNavigationTransaction(name: string, source: 'url' | 'route'): Span | undefined {\n const client = getClient();\n\n if (!client) {\n return undefined;\n }\n\n return startBrowserTracingNavigationSpan(client, {\n name,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react-router',\n },\n });\n}\n\nfunction getActiveRootSpan(): Span | undefined {\n const activeSpan = getActiveSpan();\n if (!activeSpan) {\n return undefined;\n }\n\n const rootSpan = getRootSpan(activeSpan);\n\n const op = spanToJSON(rootSpan).op;\n\n // Only use this root span if it is a pageload or navigation span\n return op === 'navigation' || op === 'pageload' ? rootSpan : undefined;\n}\n\nfunction getParameterizedRoute(routerState: RouterState): string {\n const lastMatch = routerState.matches[routerState.matches.length - 1];\n return normalizePathname(lastMatch?.route.path ?? routerState.location.pathname);\n}\n\nfunction normalizePathname(pathname: string): string {\n // Ensure it starts with a single slash\n let normalized = pathname.startsWith('/') ? pathname : `/${pathname}`;\n // Remove trailing slash unless it's the root\n if (normalized.length > 1 && normalized.endsWith('/')) {\n normalized = normalized.slice(0, -1);\n }\n return normalized;\n}\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"type":"module","version":"9.38.0"}
1
+ {"type":"module","version":"9.39.0"}
@@ -1,7 +1,8 @@
1
1
  import { InstrumentationBase, InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation';
2
- import { SDK_VERSION, getActiveSpan, getRootSpan, logger, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, startSpan, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
2
+ import { SEMATTRS_HTTP_TARGET } from '@opentelemetry/semantic-conventions';
3
+ import { SDK_VERSION, getActiveSpan, getRootSpan, logger, spanToJSON, updateSpanName, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, startSpan, SEMANTIC_ATTRIBUTE_SENTRY_OP } from '@sentry/core';
3
4
  import { DEBUG_BUILD } from '../../common/debug-build.js';
4
- import { isDataRequest, SEMANTIC_ATTRIBUTE_SENTRY_OVERWRITE, getOpName, getSpanName } from './util.js';
5
+ import { isDataRequest, getOpName, getSpanName } from './util.js';
5
6
 
6
7
  const supportedVersions = ['>=7.0.0'];
7
8
  const COMPONENT = 'react-router';
@@ -68,19 +69,23 @@ class ReactRouterInstrumentation extends InstrumentationBase {
68
69
  return originalRequestHandler(request, initialContext);
69
70
  }
70
71
 
71
- // Set the source and overwrite attributes on the root span to ensure the transaction name
72
- // is derived from the raw URL pathname rather than any parameterized route that may be set later
72
+ // We cannot rely on the regular span name inferral here, as the express instrumentation sets `*` as the route
73
+ // So we force this to be a more sensible name here
73
74
  // TODO: try to set derived parameterized route from build here (args[0])
75
+ const spanData = spanToJSON(rootSpan);
76
+ // eslint-disable-next-line deprecation/deprecation
77
+ const target = spanData.data[SEMATTRS_HTTP_TARGET] || url.pathname;
78
+ updateSpanName(rootSpan, `${request.method} ${target}`);
74
79
  rootSpan.setAttributes({
75
80
  [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
76
- [SEMANTIC_ATTRIBUTE_SENTRY_OVERWRITE]: `${request.method} ${url.pathname}`,
81
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react-router.server',
77
82
  });
78
83
 
79
84
  return startSpan(
80
85
  {
81
86
  name: getSpanName(url.pathname, request.method),
82
87
  attributes: {
83
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react-router',
88
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react-router.server',
84
89
  [SEMANTIC_ATTRIBUTE_SENTRY_OP]: getOpName(url.pathname, request.method),
85
90
  },
86
91
  },
@@ -1 +1 @@
1
- {"version":3,"file":"reactRouter.js","sources":["../../../../src/server/instrumentation/reactRouter.ts"],"sourcesContent":["import type { InstrumentationConfig } from '@opentelemetry/instrumentation';\nimport { InstrumentationBase, InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation';\nimport {\n getActiveSpan,\n getRootSpan,\n logger,\n SDK_VERSION,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,\n startSpan,\n} from '@sentry/core';\nimport type * as reactRouter from 'react-router';\nimport { DEBUG_BUILD } from '../../common/debug-build';\nimport { getOpName, getSpanName, isDataRequest, SEMANTIC_ATTRIBUTE_SENTRY_OVERWRITE } from './util';\n\ntype ReactRouterModuleExports = typeof reactRouter;\n\nconst supportedVersions = ['>=7.0.0'];\nconst COMPONENT = 'react-router';\n\n/**\n * Instrumentation for React Router's server request handler.\n * This patches the requestHandler function to add Sentry performance monitoring for data loaders.\n */\nexport class ReactRouterInstrumentation extends InstrumentationBase<InstrumentationConfig> {\n public constructor(config: InstrumentationConfig = {}) {\n super('ReactRouterInstrumentation', SDK_VERSION, config);\n }\n\n /**\n * Initializes the instrumentation by defining the React Router server modules to be patched.\n */\n // eslint-disable-next-line @typescript-eslint/naming-convention\n protected init(): InstrumentationNodeModuleDefinition {\n const reactRouterServerModule = new InstrumentationNodeModuleDefinition(\n COMPONENT,\n supportedVersions,\n (moduleExports: ReactRouterModuleExports) => {\n return this._createPatchedModuleProxy(moduleExports);\n },\n (_moduleExports: unknown) => {\n // nothing to unwrap here\n return _moduleExports;\n },\n );\n\n return reactRouterServerModule;\n }\n\n /**\n * Creates a proxy around the React Router module exports that patches the createRequestHandler function.\n * This allows us to wrap the request handler to add performance monitoring for data loaders and actions.\n */\n private _createPatchedModuleProxy(moduleExports: ReactRouterModuleExports): ReactRouterModuleExports {\n return new Proxy(moduleExports, {\n get(target, prop, receiver) {\n if (prop === 'createRequestHandler') {\n const original = target[prop];\n return function sentryWrappedCreateRequestHandler(this: unknown, ...args: unknown[]) {\n const originalRequestHandler = original.apply(this, args);\n\n return async function sentryWrappedRequestHandler(request: Request, initialContext?: unknown) {\n let url: URL;\n try {\n url = new URL(request.url);\n } catch (error) {\n return originalRequestHandler(request, initialContext);\n }\n\n // We currently just want to trace loaders and actions\n if (!isDataRequest(url.pathname)) {\n return originalRequestHandler(request, initialContext);\n }\n\n const activeSpan = getActiveSpan();\n const rootSpan = activeSpan && getRootSpan(activeSpan);\n\n if (!rootSpan) {\n DEBUG_BUILD && logger.debug('No active root span found, skipping tracing for data request');\n return originalRequestHandler(request, initialContext);\n }\n\n // Set the source and overwrite attributes on the root span to ensure the transaction name\n // is derived from the raw URL pathname rather than any parameterized route that may be set later\n // TODO: try to set derived parameterized route from build here (args[0])\n rootSpan.setAttributes({\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',\n [SEMANTIC_ATTRIBUTE_SENTRY_OVERWRITE]: `${request.method} ${url.pathname}`,\n });\n\n return startSpan(\n {\n name: getSpanName(url.pathname, request.method),\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react-router',\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: getOpName(url.pathname, request.method),\n },\n },\n () => {\n return originalRequestHandler(request, initialContext);\n },\n );\n };\n };\n }\n return Reflect.get(target, prop, receiver);\n },\n });\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"reactRouter.js","sources":["../../../../src/server/instrumentation/reactRouter.ts"],"sourcesContent":["import type { InstrumentationConfig } from '@opentelemetry/instrumentation';\nimport { InstrumentationBase, InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation';\nimport { SEMATTRS_HTTP_TARGET } from '@opentelemetry/semantic-conventions';\nimport {\n getActiveSpan,\n getRootSpan,\n logger,\n SDK_VERSION,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,\n spanToJSON,\n startSpan,\n updateSpanName,\n} from '@sentry/core';\nimport type * as reactRouter from 'react-router';\nimport { DEBUG_BUILD } from '../../common/debug-build';\nimport { getOpName, getSpanName, isDataRequest } from './util';\n\ntype ReactRouterModuleExports = typeof reactRouter;\n\nconst supportedVersions = ['>=7.0.0'];\nconst COMPONENT = 'react-router';\n\n/**\n * Instrumentation for React Router's server request handler.\n * This patches the requestHandler function to add Sentry performance monitoring for data loaders.\n */\nexport class ReactRouterInstrumentation extends InstrumentationBase<InstrumentationConfig> {\n public constructor(config: InstrumentationConfig = {}) {\n super('ReactRouterInstrumentation', SDK_VERSION, config);\n }\n\n /**\n * Initializes the instrumentation by defining the React Router server modules to be patched.\n */\n // eslint-disable-next-line @typescript-eslint/naming-convention\n protected init(): InstrumentationNodeModuleDefinition {\n const reactRouterServerModule = new InstrumentationNodeModuleDefinition(\n COMPONENT,\n supportedVersions,\n (moduleExports: ReactRouterModuleExports) => {\n return this._createPatchedModuleProxy(moduleExports);\n },\n (_moduleExports: unknown) => {\n // nothing to unwrap here\n return _moduleExports;\n },\n );\n\n return reactRouterServerModule;\n }\n\n /**\n * Creates a proxy around the React Router module exports that patches the createRequestHandler function.\n * This allows us to wrap the request handler to add performance monitoring for data loaders and actions.\n */\n private _createPatchedModuleProxy(moduleExports: ReactRouterModuleExports): ReactRouterModuleExports {\n return new Proxy(moduleExports, {\n get(target, prop, receiver) {\n if (prop === 'createRequestHandler') {\n const original = target[prop];\n return function sentryWrappedCreateRequestHandler(this: unknown, ...args: unknown[]) {\n const originalRequestHandler = original.apply(this, args);\n\n return async function sentryWrappedRequestHandler(request: Request, initialContext?: unknown) {\n let url: URL;\n try {\n url = new URL(request.url);\n } catch (error) {\n return originalRequestHandler(request, initialContext);\n }\n\n // We currently just want to trace loaders and actions\n if (!isDataRequest(url.pathname)) {\n return originalRequestHandler(request, initialContext);\n }\n\n const activeSpan = getActiveSpan();\n const rootSpan = activeSpan && getRootSpan(activeSpan);\n\n if (!rootSpan) {\n DEBUG_BUILD && logger.debug('No active root span found, skipping tracing for data request');\n return originalRequestHandler(request, initialContext);\n }\n\n // We cannot rely on the regular span name inferral here, as the express instrumentation sets `*` as the route\n // So we force this to be a more sensible name here\n // TODO: try to set derived parameterized route from build here (args[0])\n const spanData = spanToJSON(rootSpan);\n // eslint-disable-next-line deprecation/deprecation\n const target = spanData.data[SEMATTRS_HTTP_TARGET] || url.pathname;\n updateSpanName(rootSpan, `${request.method} ${target}`);\n rootSpan.setAttributes({\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react-router.server',\n });\n\n return startSpan(\n {\n name: getSpanName(url.pathname, request.method),\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react-router.server',\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: getOpName(url.pathname, request.method),\n },\n },\n () => {\n return originalRequestHandler(request, initialContext);\n },\n );\n };\n };\n }\n return Reflect.get(target, prop, receiver);\n },\n });\n }\n}\n"],"names":[],"mappings":"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"}
@@ -50,7 +50,5 @@ function isDataRequest(pathname) {
50
50
  return pathname.endsWith('.data');
51
51
  }
52
52
 
53
- const SEMANTIC_ATTRIBUTE_SENTRY_OVERWRITE = 'sentry.overwrite-route';
54
-
55
- export { SEMANTIC_ATTRIBUTE_SENTRY_OVERWRITE, getOpName, getSpanName, isActionRequest, isDataRequest, isLoaderRequest };
53
+ export { getOpName, getSpanName, isActionRequest, isDataRequest, isLoaderRequest };
56
54
  //# sourceMappingURL=util.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"util.js","sources":["../../../../src/server/instrumentation/util.ts"],"sourcesContent":["/**\n * Gets the op name for a request based on whether it's a loader or action request.\n * @param pathName The URL pathname to check\n * @param requestMethod The HTTP request method\n */\nexport function getOpName(pathName: string, requestMethod: string): string {\n return isLoaderRequest(pathName, requestMethod)\n ? 'function.react-router.loader'\n : isActionRequest(pathName, requestMethod)\n ? 'function.react-router.action'\n : 'function.react-router';\n}\n\n/**\n * Gets the span name for a request based on whether it's a loader or action request.\n * @param pathName The URL pathname to check\n * @param requestMethod The HTTP request method\n */\nexport function getSpanName(pathName: string, requestMethod: string): string {\n return isLoaderRequest(pathName, requestMethod)\n ? 'Executing Server Loader'\n : isActionRequest(pathName, requestMethod)\n ? 'Executing Server Action'\n : 'Unknown Data Request';\n}\n\n/**\n * Checks if the request is a server loader request\n * @param pathname The URL pathname to check\n * @param requestMethod The HTTP request method\n */\nexport function isLoaderRequest(pathname: string, requestMethod: string): boolean {\n return isDataRequest(pathname) && requestMethod === 'GET';\n}\n\n/**\n * Checks if the request is a server action request\n * @param pathname The URL pathname to check\n * @param requestMethod The HTTP request method\n */\nexport function isActionRequest(pathname: string, requestMethod: string): boolean {\n return isDataRequest(pathname) && requestMethod === 'POST';\n}\n\n/**\n * Checks if the request is a react-router data request\n * @param pathname The URL pathname to check\n */\nexport function isDataRequest(pathname: string): boolean {\n return pathname.endsWith('.data');\n}\n\nexport const SEMANTIC_ATTRIBUTE_SENTRY_OVERWRITE = 'sentry.overwrite-route';\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACO,SAAS,SAAS,CAAC,QAAQ,EAAU,aAAa,EAAkB;AAC3E,EAAE,OAAO,eAAe,CAAC,QAAQ,EAAE,aAAa;AAChD,MAAM;AACN,MAAM,eAAe,CAAC,QAAQ,EAAE,aAAa;AAC7C,QAAQ;AACR,QAAQ,uBAAuB;AAC/B;;AAEA;AACA;AACA;AACA;AACA;AACO,SAAS,WAAW,CAAC,QAAQ,EAAU,aAAa,EAAkB;AAC7E,EAAE,OAAO,eAAe,CAAC,QAAQ,EAAE,aAAa;AAChD,MAAM;AACN,MAAM,eAAe,CAAC,QAAQ,EAAE,aAAa;AAC7C,QAAQ;AACR,QAAQ,sBAAsB;AAC9B;;AAEA;AACA;AACA;AACA;AACA;AACO,SAAS,eAAe,CAAC,QAAQ,EAAU,aAAa,EAAmB;AAClF,EAAE,OAAO,aAAa,CAAC,QAAQ,KAAK,aAAA,KAAkB,KAAK;AAC3D;;AAEA;AACA;AACA;AACA;AACA;AACO,SAAS,eAAe,CAAC,QAAQ,EAAU,aAAa,EAAmB;AAClF,EAAE,OAAO,aAAa,CAAC,QAAQ,KAAK,aAAA,KAAkB,MAAM;AAC5D;;AAEA;AACA;AACA;AACA;AACO,SAAS,aAAa,CAAC,QAAQ,EAAmB;AACzD,EAAE,OAAO,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC;AACnC;;AAEO,MAAM,mCAAA,GAAsC;;;;"}
1
+ {"version":3,"file":"util.js","sources":["../../../../src/server/instrumentation/util.ts"],"sourcesContent":["/**\n * Gets the op name for a request based on whether it's a loader or action request.\n * @param pathName The URL pathname to check\n * @param requestMethod The HTTP request method\n */\nexport function getOpName(pathName: string, requestMethod: string): string {\n return isLoaderRequest(pathName, requestMethod)\n ? 'function.react-router.loader'\n : isActionRequest(pathName, requestMethod)\n ? 'function.react-router.action'\n : 'function.react-router';\n}\n\n/**\n * Gets the span name for a request based on whether it's a loader or action request.\n * @param pathName The URL pathname to check\n * @param requestMethod The HTTP request method\n */\nexport function getSpanName(pathName: string, requestMethod: string): string {\n return isLoaderRequest(pathName, requestMethod)\n ? 'Executing Server Loader'\n : isActionRequest(pathName, requestMethod)\n ? 'Executing Server Action'\n : 'Unknown Data Request';\n}\n\n/**\n * Checks if the request is a server loader request\n * @param pathname The URL pathname to check\n * @param requestMethod The HTTP request method\n */\nexport function isLoaderRequest(pathname: string, requestMethod: string): boolean {\n return isDataRequest(pathname) && requestMethod === 'GET';\n}\n\n/**\n * Checks if the request is a server action request\n * @param pathname The URL pathname to check\n * @param requestMethod The HTTP request method\n */\nexport function isActionRequest(pathname: string, requestMethod: string): boolean {\n return isDataRequest(pathname) && requestMethod === 'POST';\n}\n\n/**\n * Checks if the request is a react-router data request\n * @param pathname The URL pathname to check\n */\nexport function isDataRequest(pathname: string): boolean {\n return pathname.endsWith('.data');\n}\n\nexport const SEMANTIC_ATTRIBUTE_SENTRY_OVERWRITE = 'sentry.overwrite-route';\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACO,SAAS,SAAS,CAAC,QAAQ,EAAU,aAAa,EAAkB;AAC3E,EAAE,OAAO,eAAe,CAAC,QAAQ,EAAE,aAAa;AAChD,MAAM;AACN,MAAM,eAAe,CAAC,QAAQ,EAAE,aAAa;AAC7C,QAAQ;AACR,QAAQ,uBAAuB;AAC/B;;AAEA;AACA;AACA;AACA;AACA;AACO,SAAS,WAAW,CAAC,QAAQ,EAAU,aAAa,EAAkB;AAC7E,EAAE,OAAO,eAAe,CAAC,QAAQ,EAAE,aAAa;AAChD,MAAM;AACN,MAAM,eAAe,CAAC,QAAQ,EAAE,aAAa;AAC7C,QAAQ;AACR,QAAQ,sBAAsB;AAC9B;;AAEA;AACA;AACA;AACA;AACA;AACO,SAAS,eAAe,CAAC,QAAQ,EAAU,aAAa,EAAmB;AAClF,EAAE,OAAO,aAAa,CAAC,QAAQ,KAAK,aAAA,KAAkB,KAAK;AAC3D;;AAEA;AACA;AACA;AACA;AACA;AACO,SAAS,eAAe,CAAC,QAAQ,EAAU,aAAa,EAAmB;AAClF,EAAE,OAAO,aAAa,CAAC,QAAQ,KAAK,aAAA,KAAkB,MAAM;AAC5D;;AAEA;AACA;AACA;AACA;AACO,SAAS,aAAa,CAAC,QAAQ,EAAmB;AACzD,EAAE,OAAO,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC;AACnC;;;;"}
@@ -1,5 +1,6 @@
1
+ import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions';
1
2
  import { defineIntegration } from '@sentry/core';
2
- import { generateInstrumentOnce } from '@sentry/node';
3
+ import { generateInstrumentOnce, NODE_VERSION } from '@sentry/node';
3
4
  import { ReactRouterInstrumentation } from '../instrumentation/reactRouter.js';
4
5
 
5
6
  const INTEGRATION_NAME = 'ReactRouterServer';
@@ -22,7 +23,29 @@ const reactRouterServerIntegration = defineIntegration(() => {
22
23
  return {
23
24
  name: INTEGRATION_NAME,
24
25
  setupOnce() {
25
- instrumentReactRouterServer();
26
+ if (
27
+ (NODE_VERSION.major === 20 && NODE_VERSION.minor < 19) || // https://nodejs.org/en/blog/release/v20.19.0
28
+ (NODE_VERSION.major === 22 && NODE_VERSION.minor < 12) // https://nodejs.org/en/blog/release/v22.12.0
29
+ ) {
30
+ instrumentReactRouterServer();
31
+ }
32
+ },
33
+ processEvent(event) {
34
+ // Express generates bogus `*` routes for data loaders, which we want to remove here
35
+ // we cannot do this earlier because some OTEL instrumentation adds this at some unexpected point
36
+ if (
37
+ event.type === 'transaction' &&
38
+ event.contexts?.trace?.data &&
39
+ event.contexts.trace.data[ATTR_HTTP_ROUTE] === '*' &&
40
+ // This means the name has been adjusted before, but the http.route remains, so we need to remove it
41
+ event.transaction !== 'GET *' &&
42
+ event.transaction !== 'POST *'
43
+ ) {
44
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
45
+ delete event.contexts.trace.data[ATTR_HTTP_ROUTE];
46
+ }
47
+
48
+ return event;
26
49
  },
27
50
  };
28
51
  });
@@ -1 +1 @@
1
- {"version":3,"file":"reactRouterServer.js","sources":["../../../../src/server/integration/reactRouterServer.ts"],"sourcesContent":["import { defineIntegration } from '@sentry/core';\nimport { generateInstrumentOnce } from '@sentry/node';\nimport { ReactRouterInstrumentation } from '../instrumentation/reactRouter';\n\nconst INTEGRATION_NAME = 'ReactRouterServer';\n\nconst instrumentReactRouter = generateInstrumentOnce('React-Router-Server', () => {\n return new ReactRouterInstrumentation();\n});\n\nexport const instrumentReactRouterServer = Object.assign(\n (): void => {\n instrumentReactRouter();\n },\n { id: INTEGRATION_NAME },\n);\n\n/**\n * Integration capturing tracing data for React Router server functions.\n */\nexport const reactRouterServerIntegration = defineIntegration(() => {\n return {\n name: INTEGRATION_NAME,\n setupOnce() {\n instrumentReactRouterServer();\n },\n };\n});\n"],"names":[],"mappings":";;;;AAIA,MAAM,gBAAA,GAAmB,mBAAmB;;AAE5C,MAAM,qBAAA,GAAwB,sBAAsB,CAAC,qBAAqB,EAAE,MAAM;AAClF,EAAE,OAAO,IAAI,0BAA0B,EAAE;AACzC,CAAC,CAAC;;AAEK,MAAM,2BAAA,GAA8B,MAAM,CAAC,MAAM;AACxD,EAAE,MAAY;AACd,IAAI,qBAAqB,EAAE;AAC3B,GAAG;AACH,EAAE,EAAE,EAAE,EAAE,gBAAA,EAAkB;AAC1B;;AAEA;AACA;AACA;MACa,4BAAA,GAA+B,iBAAiB,CAAC,MAAM;AACpE,EAAE,OAAO;AACT,IAAI,IAAI,EAAE,gBAAgB;AAC1B,IAAI,SAAS,GAAG;AAChB,MAAM,2BAA2B,EAAE;AACnC,KAAK;AACL,GAAG;AACH,CAAC;;;;"}
1
+ {"version":3,"file":"reactRouterServer.js","sources":["../../../../src/server/integration/reactRouterServer.ts"],"sourcesContent":["import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions';\nimport { defineIntegration } from '@sentry/core';\nimport { generateInstrumentOnce, NODE_VERSION } from '@sentry/node';\nimport { ReactRouterInstrumentation } from '../instrumentation/reactRouter';\n\nconst INTEGRATION_NAME = 'ReactRouterServer';\n\nconst instrumentReactRouter = generateInstrumentOnce('React-Router-Server', () => {\n return new ReactRouterInstrumentation();\n});\n\nexport const instrumentReactRouterServer = Object.assign(\n (): void => {\n instrumentReactRouter();\n },\n { id: INTEGRATION_NAME },\n);\n\n/**\n * Integration capturing tracing data for React Router server functions.\n */\nexport const reactRouterServerIntegration = defineIntegration(() => {\n return {\n name: INTEGRATION_NAME,\n setupOnce() {\n if (\n (NODE_VERSION.major === 20 && NODE_VERSION.minor < 19) || // https://nodejs.org/en/blog/release/v20.19.0\n (NODE_VERSION.major === 22 && NODE_VERSION.minor < 12) // https://nodejs.org/en/blog/release/v22.12.0\n ) {\n instrumentReactRouterServer();\n }\n },\n processEvent(event) {\n // Express generates bogus `*` routes for data loaders, which we want to remove here\n // we cannot do this earlier because some OTEL instrumentation adds this at some unexpected point\n if (\n event.type === 'transaction' &&\n event.contexts?.trace?.data &&\n event.contexts.trace.data[ATTR_HTTP_ROUTE] === '*' &&\n // This means the name has been adjusted before, but the http.route remains, so we need to remove it\n event.transaction !== 'GET *' &&\n event.transaction !== 'POST *'\n ) {\n // eslint-disable-next-line @typescript-eslint/no-dynamic-delete\n delete event.contexts.trace.data[ATTR_HTTP_ROUTE];\n }\n\n return event;\n },\n };\n});\n"],"names":[],"mappings":";;;;;AAKA,MAAM,gBAAA,GAAmB,mBAAmB;;AAE5C,MAAM,qBAAA,GAAwB,sBAAsB,CAAC,qBAAqB,EAAE,MAAM;AAClF,EAAE,OAAO,IAAI,0BAA0B,EAAE;AACzC,CAAC,CAAC;;AAEK,MAAM,2BAAA,GAA8B,MAAM,CAAC,MAAM;AACxD,EAAE,MAAY;AACd,IAAI,qBAAqB,EAAE;AAC3B,GAAG;AACH,EAAE,EAAE,EAAE,EAAE,gBAAA,EAAkB;AAC1B;;AAEA;AACA;AACA;MACa,4BAAA,GAA+B,iBAAiB,CAAC,MAAM;AACpE,EAAE,OAAO;AACT,IAAI,IAAI,EAAE,gBAAgB;AAC1B,IAAI,SAAS,GAAG;AAChB,MAAM;AACN,QAAQ,CAAC,YAAY,CAAC,KAAA,KAAU,EAAA,IAAM,YAAY,CAAC,KAAA,GAAQ,EAAE;AAC7D,SAAS,YAAY,CAAC,KAAA,KAAU,EAAA,IAAM,YAAY,CAAC,KAAA,GAAQ,EAAE,CAAA;AAC7D,QAAQ;AACR,QAAQ,2BAA2B,EAAE;AACrC;AACA,KAAK;AACL,IAAI,YAAY,CAAC,KAAK,EAAE;AACxB;AACA;AACA,MAAM;AACN,QAAQ,KAAK,CAAC,IAAA,KAAS,aAAA;AACvB,QAAQ,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAA;AAC/B,QAAQ,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,CAAA,KAAM,GAAA;AACvD;AACA,QAAQ,KAAK,CAAC,WAAA,KAAgB,OAAA;AAC9B,QAAQ,KAAK,CAAC,WAAA,KAAgB;AAC9B,QAAQ;AACR;AACA,QAAQ,OAAO,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC;AACzD;;AAEA,MAAM,OAAO,KAAK;AAClB,KAAK;AACL,GAAG;AACH,CAAC;;;;"}
@@ -1,8 +1,6 @@
1
- import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions';
2
- import { logger, applySdkMetadata, setTag, getGlobalScope } from '@sentry/core';
3
- import { init as init$1, getDefaultIntegrations, NODE_VERSION } from '@sentry/node';
1
+ import { logger, applySdkMetadata, setTag } from '@sentry/core';
2
+ import { init as init$1, getDefaultIntegrations } from '@sentry/node';
4
3
  import { DEBUG_BUILD } from '../common/debug-build.js';
5
- import { SEMANTIC_ATTRIBUTE_SENTRY_OVERWRITE } from './instrumentation/util.js';
6
4
  import { lowQualityTransactionsFilterIntegration } from './integration/lowQualityTransactionsFilterIntegration.js';
7
5
  import { reactRouterServerIntegration } from './integration/reactRouterServer.js';
8
6
 
@@ -11,16 +9,11 @@ import { reactRouterServerIntegration } from './integration/reactRouterServer.js
11
9
  * @param options The options for the SDK.
12
10
  */
13
11
  function getDefaultReactRouterServerIntegrations(options) {
14
- const integrations = [...getDefaultIntegrations(options), lowQualityTransactionsFilterIntegration(options)];
15
-
16
- if (
17
- (NODE_VERSION.major === 20 && NODE_VERSION.minor < 19) || // https://nodejs.org/en/blog/release/v20.19.0
18
- (NODE_VERSION.major === 22 && NODE_VERSION.minor < 12) // https://nodejs.org/en/blog/release/v22.12.0
19
- ) {
20
- integrations.push(reactRouterServerIntegration());
21
- }
22
-
23
- return integrations;
12
+ return [
13
+ ...getDefaultIntegrations(options),
14
+ lowQualityTransactionsFilterIntegration(options),
15
+ reactRouterServerIntegration(),
16
+ ];
24
17
  }
25
18
 
26
19
  /**
@@ -40,31 +33,6 @@ function init(options) {
40
33
 
41
34
  setTag('runtime', 'node');
42
35
 
43
- // Overwrite the transaction name for instrumented data loaders because the trace data gets overwritten at a later point.
44
- // We only update the tx in case SEMANTIC_ATTRIBUTE_SENTRY_OVERWRITE got set in our instrumentation before.
45
- getGlobalScope().addEventProcessor(
46
- Object.assign(
47
- (event => {
48
- const overwrite = event.contexts?.trace?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_OVERWRITE];
49
- if (
50
- event.type === 'transaction' &&
51
- (event.transaction === 'GET *' || event.transaction === 'POST *') &&
52
- event.contexts?.trace?.data?.[ATTR_HTTP_ROUTE] === '*' &&
53
- overwrite
54
- ) {
55
- event.transaction = overwrite;
56
- event.contexts.trace.data[ATTR_HTTP_ROUTE] = 'url';
57
- }
58
-
59
- // always yeet this attribute into the void, as this should not reach the server
60
- delete event.contexts?.trace?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_OVERWRITE];
61
-
62
- return event;
63
- }) ,
64
- { id: 'ReactRouterTransactionEnhancer' },
65
- ),
66
- );
67
-
68
36
  DEBUG_BUILD && logger.log('SDK successfully initialized');
69
37
 
70
38
  return client;
@@ -1 +1 @@
1
- {"version":3,"file":"sdk.js","sources":["../../../src/server/sdk.ts"],"sourcesContent":["import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions';\nimport type { EventProcessor, Integration } from '@sentry/core';\nimport { applySdkMetadata, getGlobalScope, logger, setTag } from '@sentry/core';\nimport type { NodeClient, NodeOptions } from '@sentry/node';\nimport { getDefaultIntegrations as getNodeDefaultIntegrations, init as initNodeSdk, NODE_VERSION } from '@sentry/node';\nimport { DEBUG_BUILD } from '../common/debug-build';\nimport { SEMANTIC_ATTRIBUTE_SENTRY_OVERWRITE } from './instrumentation/util';\nimport { lowQualityTransactionsFilterIntegration } from './integration/lowQualityTransactionsFilterIntegration';\nimport { reactRouterServerIntegration } from './integration/reactRouterServer';\n\n/**\n * Returns the default integrations for the React Router SDK.\n * @param options The options for the SDK.\n */\nexport function getDefaultReactRouterServerIntegrations(options: NodeOptions): Integration[] {\n const integrations = [...getNodeDefaultIntegrations(options), lowQualityTransactionsFilterIntegration(options)];\n\n if (\n (NODE_VERSION.major === 20 && NODE_VERSION.minor < 19) || // https://nodejs.org/en/blog/release/v20.19.0\n (NODE_VERSION.major === 22 && NODE_VERSION.minor < 12) // https://nodejs.org/en/blog/release/v22.12.0\n ) {\n integrations.push(reactRouterServerIntegration());\n }\n\n return integrations;\n}\n\n/**\n * Initializes the server side of the React Router SDK\n */\nexport function init(options: NodeOptions): NodeClient | undefined {\n const opts: NodeOptions = {\n ...options,\n defaultIntegrations: getDefaultReactRouterServerIntegrations(options),\n };\n\n DEBUG_BUILD && logger.log('Initializing SDK...');\n\n applySdkMetadata(opts, 'react-router', ['react-router', 'node']);\n\n const client = initNodeSdk(opts);\n\n setTag('runtime', 'node');\n\n // Overwrite the transaction name for instrumented data loaders because the trace data gets overwritten at a later point.\n // We only update the tx in case SEMANTIC_ATTRIBUTE_SENTRY_OVERWRITE got set in our instrumentation before.\n getGlobalScope().addEventProcessor(\n Object.assign(\n (event => {\n const overwrite = event.contexts?.trace?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_OVERWRITE];\n if (\n event.type === 'transaction' &&\n (event.transaction === 'GET *' || event.transaction === 'POST *') &&\n event.contexts?.trace?.data?.[ATTR_HTTP_ROUTE] === '*' &&\n overwrite\n ) {\n event.transaction = overwrite;\n event.contexts.trace.data[ATTR_HTTP_ROUTE] = 'url';\n }\n\n // always yeet this attribute into the void, as this should not reach the server\n delete event.contexts?.trace?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_OVERWRITE];\n\n return event;\n }) satisfies EventProcessor,\n { id: 'ReactRouterTransactionEnhancer' },\n ),\n );\n\n DEBUG_BUILD && logger.log('SDK successfully initialized');\n\n return client;\n}\n"],"names":["getNodeDefaultIntegrations","initNodeSdk"],"mappings":";;;;;;;;AAUA;AACA;AACA;AACA;AACO,SAAS,uCAAuC,CAAC,OAAO,EAA8B;AAC7F,EAAE,MAAM,YAAA,GAAe,CAAC,GAAGA,sBAA0B,CAAC,OAAO,CAAC,EAAE,uCAAuC,CAAC,OAAO,CAAC,CAAC;;AAEjH,EAAE;AACF,IAAI,CAAC,YAAY,CAAC,KAAA,KAAU,EAAA,IAAM,YAAY,CAAC,KAAA,GAAQ,EAAE;AACzD,KAAK,YAAY,CAAC,KAAA,KAAU,EAAA,IAAM,YAAY,CAAC,KAAA,GAAQ,EAAE,CAAA;AACzD,IAAI;AACJ,IAAI,YAAY,CAAC,IAAI,CAAC,4BAA4B,EAAE,CAAC;AACrD;;AAEA,EAAE,OAAO,YAAY;AACrB;;AAEA;AACA;AACA;AACO,SAAS,IAAI,CAAC,OAAO,EAAuC;AACnE,EAAE,MAAM,IAAI,GAAgB;AAC5B,IAAI,GAAG,OAAO;AACd,IAAI,mBAAmB,EAAE,uCAAuC,CAAC,OAAO,CAAC;AACzE,GAAG;;AAEH,EAAE,eAAe,MAAM,CAAC,GAAG,CAAC,qBAAqB,CAAC;;AAElD,EAAE,gBAAgB,CAAC,IAAI,EAAE,cAAc,EAAE,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;;AAElE,EAAE,MAAM,MAAA,GAASC,MAAW,CAAC,IAAI,CAAC;;AAElC,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC;;AAE3B;AACA;AACA,EAAE,cAAc,EAAE,CAAC,iBAAiB;AACpC,IAAI,MAAM,CAAC,MAAM;AACjB,OAAO,SAAS;AAChB,QAAQ,MAAM,SAAA,GAAY,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,GAAG,mCAAmC,CAAC;AAC5F,QAAQ;AACR,UAAU,KAAK,CAAC,IAAA,KAAS,aAAA;AACzB,WAAW,KAAK,CAAC,WAAA,KAAgB,OAAA,IAAW,KAAK,CAAC,WAAA,KAAgB,QAAQ,CAAA;AAC1E,UAAU,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,GAAG,eAAe,CAAA,KAAM,GAAA;AAC7D,UAAU;AACV,UAAU;AACV,UAAU,KAAK,CAAC,WAAA,GAAc,SAAS;AACvC,UAAU,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,CAAA,GAAI,KAAK;AAC5D;;AAEA;AACA,QAAQ,OAAO,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,GAAG,mCAAmC,CAAC;;AAEjF,QAAQ,OAAO,KAAK;AACpB,OAAO;AACP,MAAM,EAAE,EAAE,EAAE,gCAAA,EAAkC;AAC9C,KAAK;AACL,GAAG;;AAEH,EAAE,eAAe,MAAM,CAAC,GAAG,CAAC,8BAA8B,CAAC;;AAE3D,EAAE,OAAO,MAAM;AACf;;;;"}
1
+ {"version":3,"file":"sdk.js","sources":["../../../src/server/sdk.ts"],"sourcesContent":["import type { Integration } from '@sentry/core';\nimport { applySdkMetadata, logger, setTag } from '@sentry/core';\nimport type { NodeClient, NodeOptions } from '@sentry/node';\nimport { getDefaultIntegrations as getNodeDefaultIntegrations, init as initNodeSdk } from '@sentry/node';\nimport { DEBUG_BUILD } from '../common/debug-build';\nimport { lowQualityTransactionsFilterIntegration } from './integration/lowQualityTransactionsFilterIntegration';\nimport { reactRouterServerIntegration } from './integration/reactRouterServer';\n\n/**\n * Returns the default integrations for the React Router SDK.\n * @param options The options for the SDK.\n */\nexport function getDefaultReactRouterServerIntegrations(options: NodeOptions): Integration[] {\n return [\n ...getNodeDefaultIntegrations(options),\n lowQualityTransactionsFilterIntegration(options),\n reactRouterServerIntegration(),\n ];\n}\n\n/**\n * Initializes the server side of the React Router SDK\n */\nexport function init(options: NodeOptions): NodeClient | undefined {\n const opts: NodeOptions = {\n ...options,\n defaultIntegrations: getDefaultReactRouterServerIntegrations(options),\n };\n\n DEBUG_BUILD && logger.log('Initializing SDK...');\n\n applySdkMetadata(opts, 'react-router', ['react-router', 'node']);\n\n const client = initNodeSdk(opts);\n\n setTag('runtime', 'node');\n\n DEBUG_BUILD && logger.log('SDK successfully initialized');\n\n return client;\n}\n"],"names":["getNodeDefaultIntegrations","initNodeSdk"],"mappings":";;;;;;AAQA;AACA;AACA;AACA;AACO,SAAS,uCAAuC,CAAC,OAAO,EAA8B;AAC7F,EAAE,OAAO;AACT,IAAI,GAAGA,sBAA0B,CAAC,OAAO,CAAC;AAC1C,IAAI,uCAAuC,CAAC,OAAO,CAAC;AACpD,IAAI,4BAA4B,EAAE;AAClC,GAAG;AACH;;AAEA;AACA;AACA;AACO,SAAS,IAAI,CAAC,OAAO,EAAuC;AACnE,EAAE,MAAM,IAAI,GAAgB;AAC5B,IAAI,GAAG,OAAO;AACd,IAAI,mBAAmB,EAAE,uCAAuC,CAAC,OAAO,CAAC;AACzE,GAAG;;AAEH,EAAE,eAAe,MAAM,CAAC,GAAG,CAAC,qBAAqB,CAAC;;AAElD,EAAE,gBAAgB,CAAC,IAAI,EAAE,cAAc,EAAE,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;;AAElE,EAAE,MAAM,MAAA,GAASC,MAAW,CAAC,IAAI,CAAC;;AAElC,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC;;AAE3B,EAAE,eAAe,MAAM,CAAC,GAAG,CAAC,8BAA8B,CAAC;;AAE3D,EAAE,OAAO,MAAM;AACf;;;;"}
@@ -1,7 +1,7 @@
1
1
  import { context } from '@opentelemetry/api';
2
2
  import { getRPCMetadata, RPCType } from '@opentelemetry/core';
3
3
  import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions';
4
- import { getActiveSpan, getRootSpan, SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, getTraceMetaTags } from '@sentry/core';
4
+ import { getActiveSpan, getRootSpan, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, getTraceMetaTags } from '@sentry/core';
5
5
  import { Transform } from 'stream';
6
6
 
7
7
  /**
@@ -21,26 +21,25 @@ function wrapSentryHandleRequest(originalHandle) {
21
21
  const parameterizedPath =
22
22
  routerContext?.staticHandlerContext?.matches?.[routerContext.staticHandlerContext.matches.length - 1]?.route.path;
23
23
 
24
- if (parameterizedPath) {
25
- const activeSpan = getActiveSpan();
26
- if (activeSpan) {
27
- const rootSpan = getRootSpan(activeSpan);
28
- const routeName = `/${parameterizedPath}`;
24
+ const activeSpan = getActiveSpan();
25
+ const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined;
29
26
 
30
- // The express instrumentation writes on the rpcMetadata and that ends up stomping on the `http.route` attribute.
31
- const rpcMetadata = getRPCMetadata(context.active());
27
+ if (parameterizedPath && rootSpan) {
28
+ const routeName = `/${parameterizedPath}`;
32
29
 
33
- if (rpcMetadata?.type === RPCType.HTTP) {
34
- rpcMetadata.route = routeName;
35
- }
30
+ // The express instrumentation writes on the rpcMetadata and that ends up stomping on the `http.route` attribute.
31
+ const rpcMetadata = getRPCMetadata(context.active());
36
32
 
37
- // The span exporter picks up the `http.route` (ATTR_HTTP_ROUTE) attribute to set the transaction name
38
- rootSpan.setAttributes({
39
- [ATTR_HTTP_ROUTE]: routeName,
40
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
41
- [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: `${request.method} ${routeName}`,
42
- });
33
+ if (rpcMetadata?.type === RPCType.HTTP) {
34
+ rpcMetadata.route = routeName;
43
35
  }
36
+
37
+ // The span exporter picks up the `http.route` (ATTR_HTTP_ROUTE) attribute to set the transaction name
38
+ rootSpan.setAttributes({
39
+ [ATTR_HTTP_ROUTE]: routeName,
40
+ [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
41
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react-router.request-handler',
42
+ });
44
43
  }
45
44
 
46
45
  return originalHandle(request, responseStatusCode, responseHeaders, routerContext, loadContext);
@@ -1 +1 @@
1
- {"version":3,"file":"wrapSentryHandleRequest.js","sources":["../../../src/server/wrapSentryHandleRequest.ts"],"sourcesContent":["import { context } from '@opentelemetry/api';\nimport { getRPCMetadata, RPCType } from '@opentelemetry/core';\nimport { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions';\nimport {\n getActiveSpan,\n getRootSpan,\n getTraceMetaTags,\n SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME,\n SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,\n} from '@sentry/core';\nimport type { AppLoadContext, EntryContext } from 'react-router';\nimport type { PassThrough } from 'stream';\nimport { Transform } from 'stream';\n\ntype OriginalHandleRequest = (\n request: Request,\n responseStatusCode: number,\n responseHeaders: Headers,\n routerContext: EntryContext,\n loadContext: AppLoadContext,\n) => Promise<unknown>;\n\n/**\n * Wraps the original handleRequest function to add Sentry instrumentation.\n *\n * @param originalHandle - The original handleRequest function to wrap\n * @returns A wrapped version of the handle request function with Sentry instrumentation\n */\nexport function wrapSentryHandleRequest(originalHandle: OriginalHandleRequest): OriginalHandleRequest {\n return async function sentryInstrumentedHandleRequest(\n request: Request,\n responseStatusCode: number,\n responseHeaders: Headers,\n routerContext: EntryContext,\n loadContext: AppLoadContext,\n ) {\n const parameterizedPath =\n routerContext?.staticHandlerContext?.matches?.[routerContext.staticHandlerContext.matches.length - 1]?.route.path;\n\n if (parameterizedPath) {\n const activeSpan = getActiveSpan();\n if (activeSpan) {\n const rootSpan = getRootSpan(activeSpan);\n const routeName = `/${parameterizedPath}`;\n\n // The express instrumentation writes on the rpcMetadata and that ends up stomping on the `http.route` attribute.\n const rpcMetadata = getRPCMetadata(context.active());\n\n if (rpcMetadata?.type === RPCType.HTTP) {\n rpcMetadata.route = routeName;\n }\n\n // The span exporter picks up the `http.route` (ATTR_HTTP_ROUTE) attribute to set the transaction name\n rootSpan.setAttributes({\n [ATTR_HTTP_ROUTE]: routeName,\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',\n [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: `${request.method} ${routeName}`,\n });\n }\n }\n\n return originalHandle(request, responseStatusCode, responseHeaders, routerContext, loadContext);\n };\n}\n\n/** @deprecated Use `wrapSentryHandleRequest` instead. */\nexport const sentryHandleRequest = wrapSentryHandleRequest;\n\n/**\n * Injects Sentry trace meta tags into the HTML response by piping through a transform stream.\n * This enables distributed tracing by adding trace context to the HTML document head.\n *\n * @param body - PassThrough stream containing the HTML response body to modify\n */\nexport function getMetaTagTransformer(body: PassThrough): Transform {\n const headClosingTag = '</head>';\n const htmlMetaTagTransformer = new Transform({\n transform(chunk, _encoding, callback) {\n const html = Buffer.isBuffer(chunk) ? chunk.toString() : String(chunk);\n if (html.includes(headClosingTag)) {\n const modifiedHtml = html.replace(headClosingTag, `${getTraceMetaTags()}${headClosingTag}`);\n callback(null, modifiedHtml);\n return;\n }\n callback(null, chunk);\n },\n });\n htmlMetaTagTransformer.pipe(body);\n return htmlMetaTagTransformer;\n}\n"],"names":[],"mappings":";;;;;;AAsBA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,uBAAuB,CAAC,cAAc,EAAgD;AACtG,EAAE,OAAO,eAAe,+BAA+B;AACvD,IAAI,OAAO;AACX,IAAI,kBAAkB;AACtB,IAAI,eAAe;AACnB,IAAI,aAAa;AACjB,IAAI,WAAW;AACf,IAAI;AACJ,IAAI,MAAM,iBAAA;AACV,MAAM,aAAa,EAAE,oBAAoB,EAAE,OAAO,GAAG,aAAa,CAAC,oBAAoB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,IAAI;;AAEvH,IAAI,IAAI,iBAAiB,EAAE;AAC3B,MAAM,MAAM,UAAA,GAAa,aAAa,EAAE;AACxC,MAAM,IAAI,UAAU,EAAE;AACtB,QAAQ,MAAM,QAAA,GAAW,WAAW,CAAC,UAAU,CAAC;AAChD,QAAQ,MAAM,YAAY,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAAA;;AAEA;AACA,QAAA,MAAA,WAAA,GAAA,cAAA,CAAA,OAAA,CAAA,MAAA,EAAA,CAAA;;AAEA,QAAA,IAAA,WAAA,EAAA,IAAA,KAAA,OAAA,CAAA,IAAA,EAAA;AACA,UAAA,WAAA,CAAA,KAAA,GAAA,SAAA;AACA;;AAEA;AACA,QAAA,QAAA,CAAA,aAAA,CAAA;AACA,UAAA,CAAA,eAAA,GAAA,SAAA;AACA,UAAA,CAAA,gCAAA,GAAA,OAAA;AACA,UAAA,CAAA,0CAAA,GAAA,CAAA,EAAA,OAAA,CAAA,MAAA,CAAA,CAAA,EAAA,SAAA,CAAA,CAAA;AACA,SAAA,CAAA;AACA;AACA;;AAEA,IAAA,OAAA,cAAA,CAAA,OAAA,EAAA,kBAAA,EAAA,eAAA,EAAA,aAAA,EAAA,WAAA,CAAA;AACA,GAAA;AACA;;AAEA;AACA,MAAA,mBAAA,GAAA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,SAAA,qBAAA,CAAA,IAAA,EAAA;AACA,EAAA,MAAA,cAAA,GAAA,SAAA;AACA,EAAA,MAAA,sBAAA,GAAA,IAAA,SAAA,CAAA;AACA,IAAA,SAAA,CAAA,KAAA,EAAA,SAAA,EAAA,QAAA,EAAA;AACA,MAAA,MAAA,IAAA,GAAA,MAAA,CAAA,QAAA,CAAA,KAAA,CAAA,GAAA,KAAA,CAAA,QAAA,EAAA,GAAA,MAAA,CAAA,KAAA,CAAA;AACA,MAAA,IAAA,IAAA,CAAA,QAAA,CAAA,cAAA,CAAA,EAAA;AACA,QAAA,MAAA,YAAA,GAAA,IAAA,CAAA,OAAA,CAAA,cAAA,EAAA,CAAA,EAAA,gBAAA,EAAA,CAAA,EAAA,cAAA,CAAA,CAAA,CAAA;AACA,QAAA,QAAA,CAAA,IAAA,EAAA,YAAA,CAAA;AACA,QAAA;AACA;AACA,MAAA,QAAA,CAAA,IAAA,EAAA,KAAA,CAAA;AACA,KAAA;AACA,GAAA,CAAA;AACA,EAAA,sBAAA,CAAA,IAAA,CAAA,IAAA,CAAA;AACA,EAAA,OAAA,sBAAA;AACA;;;;"}
1
+ {"version":3,"file":"wrapSentryHandleRequest.js","sources":["../../../src/server/wrapSentryHandleRequest.ts"],"sourcesContent":["import { context } from '@opentelemetry/api';\nimport { getRPCMetadata, RPCType } from '@opentelemetry/core';\nimport { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions';\nimport {\n getActiveSpan,\n getRootSpan,\n getTraceMetaTags,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,\n} from '@sentry/core';\nimport type { AppLoadContext, EntryContext } from 'react-router';\nimport type { PassThrough } from 'stream';\nimport { Transform } from 'stream';\n\ntype OriginalHandleRequest = (\n request: Request,\n responseStatusCode: number,\n responseHeaders: Headers,\n routerContext: EntryContext,\n loadContext: AppLoadContext,\n) => Promise<unknown>;\n\n/**\n * Wraps the original handleRequest function to add Sentry instrumentation.\n *\n * @param originalHandle - The original handleRequest function to wrap\n * @returns A wrapped version of the handle request function with Sentry instrumentation\n */\nexport function wrapSentryHandleRequest(originalHandle: OriginalHandleRequest): OriginalHandleRequest {\n return async function sentryInstrumentedHandleRequest(\n request: Request,\n responseStatusCode: number,\n responseHeaders: Headers,\n routerContext: EntryContext,\n loadContext: AppLoadContext,\n ) {\n const parameterizedPath =\n routerContext?.staticHandlerContext?.matches?.[routerContext.staticHandlerContext.matches.length - 1]?.route.path;\n\n const activeSpan = getActiveSpan();\n const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined;\n\n if (parameterizedPath && rootSpan) {\n const routeName = `/${parameterizedPath}`;\n\n // The express instrumentation writes on the rpcMetadata and that ends up stomping on the `http.route` attribute.\n const rpcMetadata = getRPCMetadata(context.active());\n\n if (rpcMetadata?.type === RPCType.HTTP) {\n rpcMetadata.route = routeName;\n }\n\n // The span exporter picks up the `http.route` (ATTR_HTTP_ROUTE) attribute to set the transaction name\n rootSpan.setAttributes({\n [ATTR_HTTP_ROUTE]: routeName,\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react-router.request-handler',\n });\n }\n\n return originalHandle(request, responseStatusCode, responseHeaders, routerContext, loadContext);\n };\n}\n\n/** @deprecated Use `wrapSentryHandleRequest` instead. */\nexport const sentryHandleRequest = wrapSentryHandleRequest;\n\n/**\n * Injects Sentry trace meta tags into the HTML response by piping through a transform stream.\n * This enables distributed tracing by adding trace context to the HTML document head.\n *\n * @param body - PassThrough stream containing the HTML response body to modify\n */\nexport function getMetaTagTransformer(body: PassThrough): Transform {\n const headClosingTag = '</head>';\n const htmlMetaTagTransformer = new Transform({\n transform(chunk, _encoding, callback) {\n const html = Buffer.isBuffer(chunk) ? chunk.toString() : String(chunk);\n if (html.includes(headClosingTag)) {\n const modifiedHtml = html.replace(headClosingTag, `${getTraceMetaTags()}${headClosingTag}`);\n callback(null, modifiedHtml);\n return;\n }\n callback(null, chunk);\n },\n });\n htmlMetaTagTransformer.pipe(body);\n return htmlMetaTagTransformer;\n}\n"],"names":[],"mappings":";;;;;;AAsBA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,uBAAuB,CAAC,cAAc,EAAgD;AACtG,EAAE,OAAO,eAAe,+BAA+B;AACvD,IAAI,OAAO;AACX,IAAI,kBAAkB;AACtB,IAAI,eAAe;AACnB,IAAI,aAAa;AACjB,IAAI,WAAW;AACf,IAAI;AACJ,IAAI,MAAM,iBAAA;AACV,MAAM,aAAa,EAAE,oBAAoB,EAAE,OAAO,GAAG,aAAa,CAAC,oBAAoB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,IAAI;;AAEvH,IAAI,MAAM,UAAA,GAAa,aAAa,EAAE;AACtC,IAAI,MAAM,QAAA,GAAW,UAAA,GAAa,WAAW,CAAC,UAAU,CAAA,GAAI,SAAS;;AAErE,IAAI,IAAI,iBAAA,IAAqB,QAAQ,EAAE;AACvC,MAAM,MAAM,YAAY,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAAA;;AAEA;AACA,MAAA,MAAA,WAAA,GAAA,cAAA,CAAA,OAAA,CAAA,MAAA,EAAA,CAAA;;AAEA,MAAA,IAAA,WAAA,EAAA,IAAA,KAAA,OAAA,CAAA,IAAA,EAAA;AACA,QAAA,WAAA,CAAA,KAAA,GAAA,SAAA;AACA;;AAEA;AACA,MAAA,QAAA,CAAA,aAAA,CAAA;AACA,QAAA,CAAA,eAAA,GAAA,SAAA;AACA,QAAA,CAAA,gCAAA,GAAA,OAAA;AACA,QAAA,CAAA,gCAAA,GAAA,wCAAA;AACA,OAAA,CAAA;AACA;;AAEA,IAAA,OAAA,cAAA,CAAA,OAAA,EAAA,kBAAA,EAAA,eAAA,EAAA,aAAA,EAAA,WAAA,CAAA;AACA,GAAA;AACA;;AAEA;AACA,MAAA,mBAAA,GAAA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,SAAA,qBAAA,CAAA,IAAA,EAAA;AACA,EAAA,MAAA,cAAA,GAAA,SAAA;AACA,EAAA,MAAA,sBAAA,GAAA,IAAA,SAAA,CAAA;AACA,IAAA,SAAA,CAAA,KAAA,EAAA,SAAA,EAAA,QAAA,EAAA;AACA,MAAA,MAAA,IAAA,GAAA,MAAA,CAAA,QAAA,CAAA,KAAA,CAAA,GAAA,KAAA,CAAA,QAAA,EAAA,GAAA,MAAA,CAAA,KAAA,CAAA;AACA,MAAA,IAAA,IAAA,CAAA,QAAA,CAAA,cAAA,CAAA,EAAA;AACA,QAAA,MAAA,YAAA,GAAA,IAAA,CAAA,OAAA,CAAA,cAAA,EAAA,CAAA,EAAA,gBAAA,EAAA,CAAA,EAAA,cAAA,CAAA,CAAA,CAAA;AACA,QAAA,QAAA,CAAA,IAAA,EAAA,YAAA,CAAA;AACA,QAAA;AACA;AACA,MAAA,QAAA,CAAA,IAAA,EAAA,KAAA,CAAA;AACA,KAAA;AACA,GAAA,CAAA;AACA,EAAA,sBAAA,CAAA,IAAA,CAAA,IAAA,CAAA;AACA,EAAA,OAAA,sBAAA;AACA;;;;"}
@@ -1,5 +1,5 @@
1
- import { getActiveSpan, getRootSpan, spanToJSON, parseStringToURLObject, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, startSpan, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
2
- import { SEMANTIC_ATTRIBUTE_SENTRY_OVERWRITE } from './instrumentation/util.js';
1
+ import { SEMATTRS_HTTP_TARGET } from '@opentelemetry/semantic-conventions';
2
+ import { getActiveSpan, getRootSpan, spanToJSON, updateSpanName, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, startSpan, SEMANTIC_ATTRIBUTE_SENTRY_OP } from '@sentry/core';
3
3
 
4
4
  /**
5
5
  * Wraps a React Router server action function with Sentry performance monitoring.
@@ -26,13 +26,18 @@ function wrapServerAction(options = {}, actionFn) {
26
26
  const active = getActiveSpan();
27
27
  if (active) {
28
28
  const root = getRootSpan(active);
29
- // coming from auto.http.otel.http
30
- if (spanToJSON(root).description === 'POST') {
31
- const url = parseStringToURLObject(args.request.url);
32
- if (url?.pathname) {
29
+ const spanData = spanToJSON(root);
30
+ if (spanData.origin === 'auto.http.otel.http') {
31
+ // eslint-disable-next-line deprecation/deprecation
32
+ const target = spanData.data[SEMATTRS_HTTP_TARGET];
33
+
34
+ if (target) {
35
+ // We cannot rely on the regular span name inferral here, as the express instrumentation sets `*` as the route
36
+ // So we force this to be a more sensible name here
37
+ updateSpanName(root, `${args.request.method} ${target}`);
33
38
  root.setAttributes({
34
39
  [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
35
- [SEMANTIC_ATTRIBUTE_SENTRY_OVERWRITE]: `${args.request.method} ${url.pathname}`,
40
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react-router.action',
36
41
  });
37
42
  }
38
43
  }
@@ -43,7 +48,7 @@ function wrapServerAction(options = {}, actionFn) {
43
48
  name,
44
49
  ...options,
45
50
  attributes: {
46
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react-router',
51
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react-router.action',
47
52
  [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react-router.action',
48
53
  ...options.attributes,
49
54
  },
@@ -1 +1 @@
1
- {"version":3,"file":"wrapServerAction.js","sources":["../../../src/server/wrapServerAction.ts"],"sourcesContent":["import type { SpanAttributes } from '@sentry/core';\nimport {\n getActiveSpan,\n getRootSpan,\n parseStringToURLObject,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,\n spanToJSON,\n startSpan,\n} from '@sentry/core';\nimport type { ActionFunctionArgs } from 'react-router';\nimport { SEMANTIC_ATTRIBUTE_SENTRY_OVERWRITE } from './instrumentation/util';\n\ntype SpanOptions = {\n name?: string;\n attributes?: SpanAttributes;\n};\n\n/**\n * Wraps a React Router server action function with Sentry performance monitoring.\n * @param options - Optional span configuration options including name, operation, description and attributes\n * @param actionFn - The server action function to wrap\n *\n * @example\n * ```ts\n * // Wrap an action function with custom span options\n * export const action = wrapServerAction(\n * {\n * name: 'Submit Form Data',\n * description: 'Processes form submission data',\n * },\n * async ({ request }) => {\n * // ... your action logic\n * }\n * );\n * ```\n */\nexport function wrapServerAction<T>(options: SpanOptions = {}, actionFn: (args: ActionFunctionArgs) => Promise<T>) {\n return async function (args: ActionFunctionArgs) {\n const name = options.name || 'Executing Server Action';\n const active = getActiveSpan();\n if (active) {\n const root = getRootSpan(active);\n // coming from auto.http.otel.http\n if (spanToJSON(root).description === 'POST') {\n const url = parseStringToURLObject(args.request.url);\n if (url?.pathname) {\n root.setAttributes({\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',\n [SEMANTIC_ATTRIBUTE_SENTRY_OVERWRITE]: `${args.request.method} ${url.pathname}`,\n });\n }\n }\n }\n\n return startSpan(\n {\n name,\n ...options,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react-router',\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react-router.action',\n ...options.attributes,\n },\n },\n () => actionFn(args),\n );\n };\n}\n"],"names":[],"mappings":";;;AAmBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,gBAAgB,CAAI,OAAO,GAAgB,EAAE,EAAE,QAAQ,EAA4C;AACnH,EAAE,OAAO,gBAAgB,IAAI,EAAsB;AACnD,IAAI,MAAM,IAAA,GAAO,OAAO,CAAC,IAAA,IAAQ,yBAAyB;AAC1D,IAAI,MAAM,MAAA,GAAS,aAAa,EAAE;AAClC,IAAI,IAAI,MAAM,EAAE;AAChB,MAAM,MAAM,IAAA,GAAO,WAAW,CAAC,MAAM,CAAC;AACtC;AACA,MAAM,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,WAAA,KAAgB,MAAM,EAAE;AACnD,QAAQ,MAAM,GAAA,GAAM,sBAAsB,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;AAC5D,QAAQ,IAAI,GAAG,EAAE,QAAQ,EAAE;AAC3B,UAAU,IAAI,CAAC,aAAa,CAAC;AAC7B,YAAY,CAAC,gCAAgC,GAAG,KAAK;AACrD,YAAY,CAAC,mCAAmC,GAAG,CAAC,EAAA,IAAA,CAAA,OAAA,CAAA,MAAA,CAAA,CAAA,EAAA,GAAA,CAAA,QAAA,CAAA,CAAA;AACA,WAAA,CAAA;AACA;AACA;AACA;;AAEA,IAAA,OAAA,SAAA;AACA,MAAA;AACA,QAAA,IAAA;AACA,QAAA,GAAA,OAAA;AACA,QAAA,UAAA,EAAA;AACA,UAAA,CAAA,gCAAA,GAAA,wBAAA;AACA,UAAA,CAAA,4BAAA,GAAA,8BAAA;AACA,UAAA,GAAA,OAAA,CAAA,UAAA;AACA,SAAA;AACA,OAAA;AACA,MAAA,MAAA,QAAA,CAAA,IAAA,CAAA;AACA,KAAA;AACA,GAAA;AACA;;;;"}
1
+ {"version":3,"file":"wrapServerAction.js","sources":["../../../src/server/wrapServerAction.ts"],"sourcesContent":["import { SEMATTRS_HTTP_TARGET } from '@opentelemetry/semantic-conventions';\nimport type { SpanAttributes } from '@sentry/core';\nimport {\n getActiveSpan,\n getRootSpan,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,\n spanToJSON,\n startSpan,\n updateSpanName,\n} from '@sentry/core';\nimport type { ActionFunctionArgs } from 'react-router';\n\ntype SpanOptions = {\n name?: string;\n attributes?: SpanAttributes;\n};\n\n/**\n * Wraps a React Router server action function with Sentry performance monitoring.\n * @param options - Optional span configuration options including name, operation, description and attributes\n * @param actionFn - The server action function to wrap\n *\n * @example\n * ```ts\n * // Wrap an action function with custom span options\n * export const action = wrapServerAction(\n * {\n * name: 'Submit Form Data',\n * description: 'Processes form submission data',\n * },\n * async ({ request }) => {\n * // ... your action logic\n * }\n * );\n * ```\n */\nexport function wrapServerAction<T>(options: SpanOptions = {}, actionFn: (args: ActionFunctionArgs) => Promise<T>) {\n return async function (args: ActionFunctionArgs) {\n const name = options.name || 'Executing Server Action';\n const active = getActiveSpan();\n if (active) {\n const root = getRootSpan(active);\n const spanData = spanToJSON(root);\n if (spanData.origin === 'auto.http.otel.http') {\n // eslint-disable-next-line deprecation/deprecation\n const target = spanData.data[SEMATTRS_HTTP_TARGET];\n\n if (target) {\n // We cannot rely on the regular span name inferral here, as the express instrumentation sets `*` as the route\n // So we force this to be a more sensible name here\n updateSpanName(root, `${args.request.method} ${target}`);\n root.setAttributes({\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react-router.action',\n });\n }\n }\n }\n\n return startSpan(\n {\n name,\n ...options,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react-router.action',\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react-router.action',\n ...options.attributes,\n },\n },\n () => actionFn(args),\n );\n };\n}\n"],"names":[],"mappings":";;;AAmBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,gBAAgB,CAAI,OAAO,GAAgB,EAAE,EAAE,QAAQ,EAA4C;AACnH,EAAE,OAAO,gBAAgB,IAAI,EAAsB;AACnD,IAAI,MAAM,IAAA,GAAO,OAAO,CAAC,IAAA,IAAQ,yBAAyB;AAC1D,IAAI,MAAM,MAAA,GAAS,aAAa,EAAE;AAClC,IAAI,IAAI,MAAM,EAAE;AAChB,MAAM,MAAM,IAAA,GAAO,WAAW,CAAC,MAAM,CAAC;AACtC,MAAM,MAAM,QAAA,GAAW,UAAU,CAAC,IAAI,CAAC;AACvC,MAAM,IAAI,QAAQ,CAAC,MAAA,KAAW,qBAAqB,EAAE;AACrD;AACA,QAAQ,MAAM,SAAS,QAAQ,CAAC,IAAI,CAAC,oBAAoB,CAAC;;AAE1D,QAAQ,IAAI,MAAM,EAAE;AACpB;AACA;AACA,UAAU,cAAc,CAAC,IAAI,EAAE,CAAC,EAAA,IAAA,CAAA,OAAA,CAAA,MAAA,CAAA,CAAA,EAAA,MAAA,CAAA,CAAA,CAAA;AACA,UAAA,IAAA,CAAA,aAAA,CAAA;AACA,YAAA,CAAA,gCAAA,GAAA,KAAA;AACA,YAAA,CAAA,gCAAA,GAAA,+BAAA;AACA,WAAA,CAAA;AACA;AACA;AACA;;AAEA,IAAA,OAAA,SAAA;AACA,MAAA;AACA,QAAA,IAAA;AACA,QAAA,GAAA,OAAA;AACA,QAAA,UAAA,EAAA;AACA,UAAA,CAAA,gCAAA,GAAA,+BAAA;AACA,UAAA,CAAA,4BAAA,GAAA,8BAAA;AACA,UAAA,GAAA,OAAA,CAAA,UAAA;AACA,SAAA;AACA,OAAA;AACA,MAAA,MAAA,QAAA,CAAA,IAAA,CAAA;AACA,KAAA;AACA,GAAA;AACA;;;;"}
@@ -1,5 +1,5 @@
1
- import { getActiveSpan, getRootSpan, spanToJSON, parseStringToURLObject, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, startSpan, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
2
- import { SEMANTIC_ATTRIBUTE_SENTRY_OVERWRITE } from './instrumentation/util.js';
1
+ import { SEMATTRS_HTTP_TARGET } from '@opentelemetry/semantic-conventions';
2
+ import { getActiveSpan, getRootSpan, spanToJSON, updateSpanName, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, startSpan, SEMANTIC_ATTRIBUTE_SENTRY_OP } from '@sentry/core';
3
3
 
4
4
  /**
5
5
  * Wraps a React Router server loader function with Sentry performance monitoring.
@@ -24,16 +24,21 @@ function wrapServerLoader(options = {}, loaderFn) {
24
24
  return async function (args) {
25
25
  const name = options.name || 'Executing Server Loader';
26
26
  const active = getActiveSpan();
27
+
27
28
  if (active) {
28
29
  const root = getRootSpan(active);
29
- // coming from auto.http.otel.http
30
- if (spanToJSON(root).description === 'GET') {
31
- const url = parseStringToURLObject(args.request.url);
30
+ const spanData = spanToJSON(root);
31
+ if (spanData.origin === 'auto.http.otel.http') {
32
+ // eslint-disable-next-line deprecation/deprecation
33
+ const target = spanData.data[SEMATTRS_HTTP_TARGET];
32
34
 
33
- if (url?.pathname) {
35
+ if (target) {
36
+ // We cannot rely on the regular span name inferral here, as the express instrumentation sets `*` as the route
37
+ // So we force this to be a more sensible name here
38
+ updateSpanName(root, `${args.request.method} ${target}`);
34
39
  root.setAttributes({
35
40
  [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
36
- [SEMANTIC_ATTRIBUTE_SENTRY_OVERWRITE]: `${args.request.method} ${url.pathname}`,
41
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react-router.loader',
37
42
  });
38
43
  }
39
44
  }
@@ -43,7 +48,7 @@ function wrapServerLoader(options = {}, loaderFn) {
43
48
  name,
44
49
  ...options,
45
50
  attributes: {
46
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react-router',
51
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react-router.loader',
47
52
  [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react-router.loader',
48
53
  ...options.attributes,
49
54
  },
@@ -1 +1 @@
1
- {"version":3,"file":"wrapServerLoader.js","sources":["../../../src/server/wrapServerLoader.ts"],"sourcesContent":["import type { SpanAttributes } from '@sentry/core';\nimport {\n getActiveSpan,\n getRootSpan,\n parseStringToURLObject,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,\n spanToJSON,\n startSpan,\n} from '@sentry/core';\nimport type { LoaderFunctionArgs } from 'react-router';\nimport { SEMANTIC_ATTRIBUTE_SENTRY_OVERWRITE } from './instrumentation/util';\n\ntype SpanOptions = {\n name?: string;\n attributes?: SpanAttributes;\n};\n\n/**\n * Wraps a React Router server loader function with Sentry performance monitoring.\n * @param options - Optional span configuration options including name, operation, description and attributes\n * @param loaderFn - The server loader function to wrap\n *\n * @example\n * ```ts\n * // Wrap a loader function with custom span options\n * export const loader = wrapServerLoader(\n * {\n * name: 'Load Some Data',\n * description: 'Loads some data from the db',\n * },\n * async ({ params }) => {\n * // ... your loader logic\n * }\n * );\n * ```\n */\nexport function wrapServerLoader<T>(options: SpanOptions = {}, loaderFn: (args: LoaderFunctionArgs) => Promise<T>) {\n return async function (args: LoaderFunctionArgs) {\n const name = options.name || 'Executing Server Loader';\n const active = getActiveSpan();\n if (active) {\n const root = getRootSpan(active);\n // coming from auto.http.otel.http\n if (spanToJSON(root).description === 'GET') {\n const url = parseStringToURLObject(args.request.url);\n\n if (url?.pathname) {\n root.setAttributes({\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',\n [SEMANTIC_ATTRIBUTE_SENTRY_OVERWRITE]: `${args.request.method} ${url.pathname}`,\n });\n }\n }\n }\n return startSpan(\n {\n name,\n ...options,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react-router',\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react-router.loader',\n ...options.attributes,\n },\n },\n () => loaderFn(args),\n );\n };\n}\n"],"names":[],"mappings":";;;AAmBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,gBAAgB,CAAI,OAAO,GAAgB,EAAE,EAAE,QAAQ,EAA4C;AACnH,EAAE,OAAO,gBAAgB,IAAI,EAAsB;AACnD,IAAI,MAAM,IAAA,GAAO,OAAO,CAAC,IAAA,IAAQ,yBAAyB;AAC1D,IAAI,MAAM,MAAA,GAAS,aAAa,EAAE;AAClC,IAAI,IAAI,MAAM,EAAE;AAChB,MAAM,MAAM,IAAA,GAAO,WAAW,CAAC,MAAM,CAAC;AACtC;AACA,MAAM,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,WAAA,KAAgB,KAAK,EAAE;AAClD,QAAQ,MAAM,GAAA,GAAM,sBAAsB,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;;AAE5D,QAAQ,IAAI,GAAG,EAAE,QAAQ,EAAE;AAC3B,UAAU,IAAI,CAAC,aAAa,CAAC;AAC7B,YAAY,CAAC,gCAAgC,GAAG,KAAK;AACrD,YAAY,CAAC,mCAAmC,GAAG,CAAC,EAAA,IAAA,CAAA,OAAA,CAAA,MAAA,CAAA,CAAA,EAAA,GAAA,CAAA,QAAA,CAAA,CAAA;AACA,WAAA,CAAA;AACA;AACA;AACA;AACA,IAAA,OAAA,SAAA;AACA,MAAA;AACA,QAAA,IAAA;AACA,QAAA,GAAA,OAAA;AACA,QAAA,UAAA,EAAA;AACA,UAAA,CAAA,gCAAA,GAAA,wBAAA;AACA,UAAA,CAAA,4BAAA,GAAA,8BAAA;AACA,UAAA,GAAA,OAAA,CAAA,UAAA;AACA,SAAA;AACA,OAAA;AACA,MAAA,MAAA,QAAA,CAAA,IAAA,CAAA;AACA,KAAA;AACA,GAAA;AACA;;;;"}
1
+ {"version":3,"file":"wrapServerLoader.js","sources":["../../../src/server/wrapServerLoader.ts"],"sourcesContent":["import { SEMATTRS_HTTP_TARGET } from '@opentelemetry/semantic-conventions';\nimport type { SpanAttributes } from '@sentry/core';\nimport {\n getActiveSpan,\n getRootSpan,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,\n spanToJSON,\n startSpan,\n updateSpanName,\n} from '@sentry/core';\nimport type { LoaderFunctionArgs } from 'react-router';\n\ntype SpanOptions = {\n name?: string;\n attributes?: SpanAttributes;\n};\n\n/**\n * Wraps a React Router server loader function with Sentry performance monitoring.\n * @param options - Optional span configuration options including name, operation, description and attributes\n * @param loaderFn - The server loader function to wrap\n *\n * @example\n * ```ts\n * // Wrap a loader function with custom span options\n * export const loader = wrapServerLoader(\n * {\n * name: 'Load Some Data',\n * description: 'Loads some data from the db',\n * },\n * async ({ params }) => {\n * // ... your loader logic\n * }\n * );\n * ```\n */\nexport function wrapServerLoader<T>(options: SpanOptions = {}, loaderFn: (args: LoaderFunctionArgs) => Promise<T>) {\n return async function (args: LoaderFunctionArgs) {\n const name = options.name || 'Executing Server Loader';\n const active = getActiveSpan();\n\n if (active) {\n const root = getRootSpan(active);\n const spanData = spanToJSON(root);\n if (spanData.origin === 'auto.http.otel.http') {\n // eslint-disable-next-line deprecation/deprecation\n const target = spanData.data[SEMATTRS_HTTP_TARGET];\n\n if (target) {\n // We cannot rely on the regular span name inferral here, as the express instrumentation sets `*` as the route\n // So we force this to be a more sensible name here\n updateSpanName(root, `${args.request.method} ${target}`);\n root.setAttributes({\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react-router.loader',\n });\n }\n }\n }\n return startSpan(\n {\n name,\n ...options,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react-router.loader',\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react-router.loader',\n ...options.attributes,\n },\n },\n () => loaderFn(args),\n );\n };\n}\n"],"names":[],"mappings":";;;AAmBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,gBAAgB,CAAI,OAAO,GAAgB,EAAE,EAAE,QAAQ,EAA4C;AACnH,EAAE,OAAO,gBAAgB,IAAI,EAAsB;AACnD,IAAI,MAAM,IAAA,GAAO,OAAO,CAAC,IAAA,IAAQ,yBAAyB;AAC1D,IAAI,MAAM,MAAA,GAAS,aAAa,EAAE;;AAElC,IAAI,IAAI,MAAM,EAAE;AAChB,MAAM,MAAM,IAAA,GAAO,WAAW,CAAC,MAAM,CAAC;AACtC,MAAM,MAAM,QAAA,GAAW,UAAU,CAAC,IAAI,CAAC;AACvC,MAAM,IAAI,QAAQ,CAAC,MAAA,KAAW,qBAAqB,EAAE;AACrD;AACA,QAAQ,MAAM,SAAS,QAAQ,CAAC,IAAI,CAAC,oBAAoB,CAAC;;AAE1D,QAAQ,IAAI,MAAM,EAAE;AACpB;AACA;AACA,UAAU,cAAc,CAAC,IAAI,EAAE,CAAC,EAAA,IAAA,CAAA,OAAA,CAAA,MAAA,CAAA,CAAA,EAAA,MAAA,CAAA,CAAA,CAAA;AACA,UAAA,IAAA,CAAA,aAAA,CAAA;AACA,YAAA,CAAA,gCAAA,GAAA,KAAA;AACA,YAAA,CAAA,gCAAA,GAAA,+BAAA;AACA,WAAA,CAAA;AACA;AACA;AACA;AACA,IAAA,OAAA,SAAA;AACA,MAAA;AACA,QAAA,IAAA;AACA,QAAA,GAAA,OAAA;AACA,QAAA,UAAA,EAAA;AACA,UAAA,CAAA,gCAAA,GAAA,+BAAA;AACA,UAAA,CAAA,4BAAA,GAAA,8BAAA;AACA,UAAA,GAAA,OAAA,CAAA,UAAA;AACA,SAAA;AACA,OAAA;AACA,MAAA,MAAA,QAAA,CAAA,IAAA,CAAA;AACA,KAAA;AACA,GAAA;AACA;;;;"}