@sentry/react-router 10.36.0 → 10.38.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.
- package/build/cjs/client/createClientInstrumentation.js +309 -0
- package/build/cjs/client/createClientInstrumentation.js.map +1 -0
- package/build/cjs/client/hydratedRouter.js +15 -18
- package/build/cjs/client/hydratedRouter.js.map +1 -1
- package/build/cjs/client/tracingIntegration.js +25 -2
- package/build/cjs/client/tracingIntegration.js.map +1 -1
- package/build/cjs/cloudflare/index.js +4 -0
- package/build/cjs/cloudflare/index.js.map +1 -1
- package/build/cjs/common/utils.js +68 -0
- package/build/cjs/common/utils.js.map +1 -0
- package/build/cjs/index.client.js +4 -0
- package/build/cjs/index.client.js.map +1 -1
- package/build/cjs/index.server.js +4 -0
- package/build/cjs/index.server.js.map +1 -1
- package/build/cjs/server/createSentryHandleError.js +1 -4
- package/build/cjs/server/createSentryHandleError.js.map +1 -1
- package/build/cjs/server/createServerInstrumentation.js +231 -0
- package/build/cjs/server/createServerInstrumentation.js.map +1 -0
- package/build/cjs/server/instrumentation/reactRouter.js +8 -0
- package/build/cjs/server/instrumentation/reactRouter.js.map +1 -1
- package/build/cjs/server/integration/reactRouterServer.js +16 -6
- package/build/cjs/server/integration/reactRouterServer.js.map +1 -1
- package/build/cjs/server/serverGlobals.js +25 -0
- package/build/cjs/server/serverGlobals.js.map +1 -0
- package/build/cjs/server/wrapSentryHandleRequest.js +22 -7
- package/build/cjs/server/wrapSentryHandleRequest.js.map +1 -1
- package/build/cjs/server/wrapServerAction.js +21 -1
- package/build/cjs/server/wrapServerAction.js.map +1 -1
- package/build/cjs/server/wrapServerLoader.js +21 -1
- package/build/cjs/server/wrapServerLoader.js.map +1 -1
- package/build/cjs/vite/makeCustomSentryVitePlugins.js +1 -1
- package/build/cjs/vite/makeCustomSentryVitePlugins.js.map +1 -1
- package/build/esm/client/createClientInstrumentation.js +305 -0
- package/build/esm/client/createClientInstrumentation.js.map +1 -0
- package/build/esm/client/hydratedRouter.js +16 -19
- package/build/esm/client/hydratedRouter.js.map +1 -1
- package/build/esm/client/tracingIntegration.js +25 -2
- package/build/esm/client/tracingIntegration.js.map +1 -1
- package/build/esm/cloudflare/index.js +1 -0
- package/build/esm/cloudflare/index.js.map +1 -1
- package/build/esm/common/utils.js +63 -0
- package/build/esm/common/utils.js.map +1 -0
- package/build/esm/index.client.js +1 -0
- package/build/esm/index.client.js.map +1 -1
- package/build/esm/index.server.js +2 -0
- package/build/esm/index.server.js.map +1 -1
- package/build/esm/package.json +1 -1
- package/build/esm/server/createSentryHandleError.js +1 -4
- package/build/esm/server/createSentryHandleError.js.map +1 -1
- package/build/esm/server/createServerInstrumentation.js +229 -0
- package/build/esm/server/createServerInstrumentation.js.map +1 -0
- package/build/esm/server/instrumentation/reactRouter.js +9 -1
- package/build/esm/server/instrumentation/reactRouter.js.map +1 -1
- package/build/esm/server/integration/reactRouterServer.js +16 -6
- package/build/esm/server/integration/reactRouterServer.js.map +1 -1
- package/build/esm/server/serverGlobals.js +22 -0
- package/build/esm/server/serverGlobals.js.map +1 -0
- package/build/esm/server/wrapSentryHandleRequest.js +23 -8
- package/build/esm/server/wrapSentryHandleRequest.js.map +1 -1
- package/build/esm/server/wrapServerAction.js +22 -2
- package/build/esm/server/wrapServerAction.js.map +1 -1
- package/build/esm/server/wrapServerLoader.js +22 -2
- package/build/esm/server/wrapServerLoader.js.map +1 -1
- package/build/esm/vite/makeCustomSentryVitePlugins.js +1 -1
- package/build/esm/vite/makeCustomSentryVitePlugins.js.map +1 -1
- package/build/types/client/createClientInstrumentation.d.ts +28 -0
- package/build/types/client/createClientInstrumentation.d.ts.map +1 -0
- package/build/types/client/hydratedRouter.d.ts.map +1 -1
- package/build/types/client/index.d.ts +2 -1
- package/build/types/client/index.d.ts.map +1 -1
- package/build/types/client/tracingIntegration.d.ts +32 -2
- package/build/types/client/tracingIntegration.d.ts.map +1 -1
- package/build/types/common/types.d.ts +88 -0
- package/build/types/common/types.d.ts.map +1 -0
- package/build/types/common/utils.d.ts +27 -0
- package/build/types/common/utils.d.ts.map +1 -0
- package/build/types/server/createSentryHandleError.d.ts.map +1 -1
- package/build/types/server/createServerInstrumentation.d.ts +18 -0
- package/build/types/server/createServerInstrumentation.d.ts.map +1 -0
- package/build/types/server/index.d.ts +1 -0
- package/build/types/server/index.d.ts.map +1 -1
- package/build/types/server/instrumentation/reactRouter.d.ts.map +1 -1
- package/build/types/server/integration/reactRouterServer.d.ts.map +1 -1
- package/build/types/server/serverGlobals.d.ts +11 -0
- package/build/types/server/serverGlobals.d.ts.map +1 -0
- package/build/types/server/wrapSentryHandleRequest.d.ts.map +1 -1
- package/build/types/server/wrapServerAction.d.ts.map +1 -1
- package/build/types/server/wrapServerLoader.d.ts.map +1 -1
- package/package.json +12 -12
|
@@ -1,16 +1,36 @@
|
|
|
1
1
|
import { browserTracingIntegration } from '@sentry/browser';
|
|
2
|
+
import { createSentryClientInstrumentation } from './createClientInstrumentation.js';
|
|
2
3
|
import { instrumentHydratedRouter } from './hydratedRouter.js';
|
|
3
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Options for the React Router tracing integration.
|
|
7
|
+
*/
|
|
8
|
+
|
|
4
9
|
/**
|
|
5
10
|
* Browser tracing integration for React Router (Framework) applications.
|
|
6
|
-
* This integration will create navigation spans and enhance
|
|
11
|
+
* This integration will create navigation spans and enhance transaction names with parameterized routes.
|
|
7
12
|
*/
|
|
8
|
-
function reactRouterTracingIntegration(
|
|
13
|
+
function reactRouterTracingIntegration(
|
|
14
|
+
options = {},
|
|
15
|
+
) {
|
|
9
16
|
const browserTracingIntegrationInstance = browserTracingIntegration({
|
|
10
17
|
// Navigation transactions are started within the hydrated router instrumentation
|
|
11
18
|
instrumentNavigation: false,
|
|
12
19
|
});
|
|
13
20
|
|
|
21
|
+
let clientInstrumentationInstance;
|
|
22
|
+
|
|
23
|
+
if (options.useInstrumentationAPI || options.instrumentationOptions) {
|
|
24
|
+
clientInstrumentationInstance = createSentryClientInstrumentation(options.instrumentationOptions);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const getClientInstrumentation = () => {
|
|
28
|
+
if (!clientInstrumentationInstance) {
|
|
29
|
+
clientInstrumentationInstance = createSentryClientInstrumentation(options.instrumentationOptions);
|
|
30
|
+
}
|
|
31
|
+
return clientInstrumentationInstance;
|
|
32
|
+
};
|
|
33
|
+
|
|
14
34
|
return {
|
|
15
35
|
...browserTracingIntegrationInstance,
|
|
16
36
|
name: 'ReactRouterTracingIntegration',
|
|
@@ -18,6 +38,9 @@ function reactRouterTracingIntegration() {
|
|
|
18
38
|
browserTracingIntegrationInstance.afterAllSetup(client);
|
|
19
39
|
instrumentHydratedRouter();
|
|
20
40
|
},
|
|
41
|
+
get clientInstrumentation() {
|
|
42
|
+
return getClientInstrumentation();
|
|
43
|
+
},
|
|
21
44
|
};
|
|
22
45
|
}
|
|
23
46
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracingIntegration.js","sources":["../../../src/client/tracingIntegration.ts"],"sourcesContent":["import { browserTracingIntegration as originalBrowserTracingIntegration } from '@sentry/browser';\nimport type { Integration } from '@sentry/core';\nimport { instrumentHydratedRouter } from './hydratedRouter';\n\n/**\n * Browser tracing integration for React Router (Framework) applications.\n * This integration will create navigation spans and enhance
|
|
1
|
+
{"version":3,"file":"tracingIntegration.js","sources":["../../../src/client/tracingIntegration.ts"],"sourcesContent":["import { browserTracingIntegration as originalBrowserTracingIntegration } from '@sentry/browser';\nimport type { Integration } from '@sentry/core';\nimport type { ClientInstrumentation } from '../common/types';\nimport {\n createSentryClientInstrumentation,\n type CreateSentryClientInstrumentationOptions,\n} from './createClientInstrumentation';\nimport { instrumentHydratedRouter } from './hydratedRouter';\n\n/**\n * Options for the React Router tracing integration.\n */\nexport interface ReactRouterTracingIntegrationOptions {\n /**\n * Options for React Router's instrumentation API.\n * @experimental\n */\n instrumentationOptions?: CreateSentryClientInstrumentationOptions;\n\n /**\n * Enable React Router's instrumentation API.\n * When true, prepares for use with HydratedRouter's `unstable_instrumentations` prop.\n * @experimental\n * @default false\n */\n useInstrumentationAPI?: boolean;\n}\n\n/**\n * React Router tracing integration with support for the instrumentation API.\n */\nexport interface ReactRouterTracingIntegration extends Integration {\n /**\n * Client instrumentation for React Router's instrumentation API.\n * Lazily initialized on first access.\n * @experimental HydratedRouter doesn't invoke these hooks in Framework Mode yet.\n */\n readonly clientInstrumentation: ClientInstrumentation;\n}\n\n/**\n * Browser tracing integration for React Router (Framework) applications.\n * This integration will create navigation spans and enhance transaction names with parameterized routes.\n */\nexport function reactRouterTracingIntegration(\n options: ReactRouterTracingIntegrationOptions = {},\n): ReactRouterTracingIntegration {\n const browserTracingIntegrationInstance = originalBrowserTracingIntegration({\n // Navigation transactions are started within the hydrated router instrumentation\n instrumentNavigation: false,\n });\n\n let clientInstrumentationInstance: ClientInstrumentation | undefined;\n\n if (options.useInstrumentationAPI || options.instrumentationOptions) {\n clientInstrumentationInstance = createSentryClientInstrumentation(options.instrumentationOptions);\n }\n\n const getClientInstrumentation = (): ClientInstrumentation => {\n if (!clientInstrumentationInstance) {\n clientInstrumentationInstance = createSentryClientInstrumentation(options.instrumentationOptions);\n }\n return clientInstrumentationInstance;\n };\n\n return {\n ...browserTracingIntegrationInstance,\n name: 'ReactRouterTracingIntegration',\n afterAllSetup(client) {\n browserTracingIntegrationInstance.afterAllSetup(client);\n instrumentHydratedRouter();\n },\n get clientInstrumentation(): ClientInstrumentation {\n return getClientInstrumentation();\n },\n };\n}\n"],"names":["originalBrowserTracingIntegration"],"mappings":";;;;AASA;AACA;AACA;;AA6BA;AACA;AACA;AACA;AACO,SAAS,6BAA6B;AAC7C,EAAE,OAAO,GAAyC,EAAE;AACpD,EAAiC;AACjC,EAAE,MAAM,iCAAA,GAAoCA,yBAAiC,CAAC;AAC9E;AACA,IAAI,oBAAoB,EAAE,KAAK;AAC/B,GAAG,CAAC;;AAEJ,EAAE,IAAI,6BAA6B;;AAEnC,EAAE,IAAI,OAAO,CAAC,yBAAyB,OAAO,CAAC,sBAAsB,EAAE;AACvE,IAAI,gCAAgC,iCAAiC,CAAC,OAAO,CAAC,sBAAsB,CAAC;AACrG,EAAE;;AAEF,EAAE,MAAM,wBAAA,GAA2B,MAA6B;AAChE,IAAI,IAAI,CAAC,6BAA6B,EAAE;AACxC,MAAM,gCAAgC,iCAAiC,CAAC,OAAO,CAAC,sBAAsB,CAAC;AACvG,IAAI;AACJ,IAAI,OAAO,6BAA6B;AACxC,EAAE,CAAC;;AAEH,EAAE,OAAO;AACT,IAAI,GAAG,iCAAiC;AACxC,IAAI,IAAI,EAAE,+BAA+B;AACzC,IAAI,aAAa,CAAC,MAAM,EAAE;AAC1B,MAAM,iCAAiC,CAAC,aAAa,CAAC,MAAM,CAAC;AAC7D,MAAM,wBAAwB,EAAE;AAChC,IAAI,CAAC;AACL,IAAI,IAAI,qBAAqB,GAA0B;AACvD,MAAM,OAAO,wBAAwB,EAAE;AACvC,IAAI,CAAC;AACL,GAAG;AACH;;;;"}
|
|
@@ -3,6 +3,7 @@ export * from '@sentry/browser';
|
|
|
3
3
|
export { init } from '../client/sdk.js';
|
|
4
4
|
export { reactRouterTracingIntegration } from '../client/tracingIntegration.js';
|
|
5
5
|
export { ErrorBoundary, Profiler, captureReactException, reactErrorHandler, useProfiler, withErrorBoundary, withProfiler } from '@sentry/react';
|
|
6
|
+
export { createSentryClientInstrumentation, isClientInstrumentationApiUsed, isNavigateHookInvoked } from '../client/createClientInstrumentation.js';
|
|
6
7
|
export { wrapSentryHandleRequest } from '../server/wrapSentryHandleRequest.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../src/cloudflare/index.ts"],"sourcesContent":["import { getTraceMetaTags } from '@sentry/core';\n\nexport * from '../client';\n\nexport { wrapSentryHandleRequest } from '../server/wrapSentryHandleRequest';\n\n/**\n * Injects Sentry trace meta tags into the HTML response by transforming the ReadableStream.\n * This enables distributed tracing by adding trace context to the HTML document head.\n * @param body - ReadableStream containing the HTML response body to modify\n * @returns A new ReadableStream with Sentry trace meta tags injected into the head section\n */\nexport function injectTraceMetaTags(body: ReadableStream): ReadableStream {\n const headClosingTag = '</head>';\n\n const reader = body.getReader();\n const stream = new ReadableStream({\n async pull(controller) {\n const { done, value } = await reader.read();\n\n if (done) {\n controller.close();\n return;\n }\n\n const encoder = new TextEncoder();\n const html = value instanceof Uint8Array ? new TextDecoder().decode(value) : String(value);\n\n if (html.includes(headClosingTag)) {\n const modifiedHtml = html.replace(headClosingTag, `${getTraceMetaTags()}${headClosingTag}`);\n\n controller.enqueue(encoder.encode(modifiedHtml));\n return;\n }\n\n controller.enqueue(encoder.encode(html));\n },\n });\n\n return stream;\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../src/cloudflare/index.ts"],"sourcesContent":["import { getTraceMetaTags } from '@sentry/core';\n\nexport * from '../client';\n\nexport { wrapSentryHandleRequest } from '../server/wrapSentryHandleRequest';\n\n/**\n * Injects Sentry trace meta tags into the HTML response by transforming the ReadableStream.\n * This enables distributed tracing by adding trace context to the HTML document head.\n * @param body - ReadableStream containing the HTML response body to modify\n * @returns A new ReadableStream with Sentry trace meta tags injected into the head section\n */\nexport function injectTraceMetaTags(body: ReadableStream): ReadableStream {\n const headClosingTag = '</head>';\n\n const reader = body.getReader();\n const stream = new ReadableStream({\n async pull(controller) {\n const { done, value } = await reader.read();\n\n if (done) {\n controller.close();\n return;\n }\n\n const encoder = new TextEncoder();\n const html = value instanceof Uint8Array ? new TextDecoder().decode(value) : String(value);\n\n if (html.includes(headClosingTag)) {\n const modifiedHtml = html.replace(headClosingTag, `${getTraceMetaTags()}${headClosingTag}`);\n\n controller.enqueue(encoder.encode(modifiedHtml));\n return;\n }\n\n controller.enqueue(encoder.encode(html));\n },\n });\n\n return stream;\n}\n"],"names":[],"mappings":";;;;;;;;AAMA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,mBAAmB,CAAC,IAAI,EAAkC;AAC1E,EAAE,MAAM,cAAA,GAAiB,SAAS;;AAElC,EAAE,MAAM,MAAA,GAAS,IAAI,CAAC,SAAS,EAAE;AACjC,EAAE,MAAM,MAAA,GAAS,IAAI,cAAc,CAAC;AACpC,IAAI,MAAM,IAAI,CAAC,UAAU,EAAE;AAC3B,MAAM,MAAM,EAAE,IAAI,EAAE,KAAA,EAAM,GAAI,MAAM,MAAM,CAAC,IAAI,EAAE;;AAEjD,MAAM,IAAI,IAAI,EAAE;AAChB,QAAQ,UAAU,CAAC,KAAK,EAAE;AAC1B,QAAQ;AACR,MAAM;;AAEN,MAAM,MAAM,OAAA,GAAU,IAAI,WAAW,EAAE;AACvC,MAAM,MAAM,OAAO,KAAA,YAAiB,UAAA,GAAa,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAA,GAAI,MAAM,CAAC,KAAK,CAAC;;AAEhG,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE;AACzC,QAAQ,MAAM,YAAA,GAAe,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,EAAA,gBAAA,EAAA,CAAA,EAAA,cAAA,CAAA,CAAA,CAAA;;AAEA,QAAA,UAAA,CAAA,OAAA,CAAA,OAAA,CAAA,MAAA,CAAA,YAAA,CAAA,CAAA;AACA,QAAA;AACA,MAAA;;AAEA,MAAA,UAAA,CAAA,OAAA,CAAA,OAAA,CAAA,MAAA,CAAA,IAAA,CAAA,CAAA;AACA,IAAA,CAAA;AACA,GAAA,CAAA;;AAEA,EAAA,OAAA,MAAA;AACA;;;;"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { captureException, debug } from '@sentry/core';
|
|
2
|
+
import { DEBUG_BUILD } from './debug-build.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extracts pathname from request URL.
|
|
6
|
+
* Falls back to '<unknown>' with DEBUG warning if URL cannot be parsed.
|
|
7
|
+
*/
|
|
8
|
+
function getPathFromRequest(request) {
|
|
9
|
+
try {
|
|
10
|
+
return new URL(request.url).pathname;
|
|
11
|
+
} catch {
|
|
12
|
+
try {
|
|
13
|
+
// Fallback: use a dummy base URL since we only care about the pathname
|
|
14
|
+
return new URL(request.url, 'http://example.com').pathname;
|
|
15
|
+
} catch (error) {
|
|
16
|
+
DEBUG_BUILD && debug.warn('Failed to parse URL from request:', request.url, error);
|
|
17
|
+
return '<unknown>';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Extracts route pattern from instrumentation info.
|
|
24
|
+
* Prefers `pattern` (planned for v8) over `unstable_pattern` (v7.x).
|
|
25
|
+
*/
|
|
26
|
+
function getPattern(info) {
|
|
27
|
+
return info.pattern ?? info.unstable_pattern;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Normalizes route path by ensuring it starts with a slash.
|
|
32
|
+
* Returns undefined if the input is falsy.
|
|
33
|
+
*/
|
|
34
|
+
function normalizeRoutePath(pattern) {
|
|
35
|
+
if (!pattern) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
return pattern.startsWith('/') ? pattern : `/${pattern}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Captures an error from instrumentation result.
|
|
43
|
+
* Caller must verify result contains an Error before calling.
|
|
44
|
+
*/
|
|
45
|
+
function captureInstrumentationError(
|
|
46
|
+
result,
|
|
47
|
+
captureErrors,
|
|
48
|
+
mechanismType,
|
|
49
|
+
data,
|
|
50
|
+
) {
|
|
51
|
+
if (captureErrors) {
|
|
52
|
+
captureException(result.error, {
|
|
53
|
+
mechanism: {
|
|
54
|
+
type: mechanismType,
|
|
55
|
+
handled: false,
|
|
56
|
+
data,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { captureInstrumentationError, getPathFromRequest, getPattern, normalizeRoutePath };
|
|
63
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../../../src/common/utils.ts"],"sourcesContent":["import { captureException, debug } from '@sentry/core';\nimport { DEBUG_BUILD } from './debug-build';\nimport type { InstrumentationResult } from './types';\n\n/**\n * Extracts pathname from request URL.\n * Falls back to '<unknown>' with DEBUG warning if URL cannot be parsed.\n */\nexport function getPathFromRequest(request: { url: string }): string {\n try {\n return new URL(request.url).pathname;\n } catch {\n try {\n // Fallback: use a dummy base URL since we only care about the pathname\n return new URL(request.url, 'http://example.com').pathname;\n } catch (error) {\n DEBUG_BUILD && debug.warn('Failed to parse URL from request:', request.url, error);\n return '<unknown>';\n }\n }\n}\n\n/**\n * Extracts route pattern from instrumentation info.\n * Prefers `pattern` (planned for v8) over `unstable_pattern` (v7.x).\n */\nexport function getPattern(info: { pattern?: string; unstable_pattern?: string }): string | undefined {\n return info.pattern ?? info.unstable_pattern;\n}\n\n/**\n * Normalizes route path by ensuring it starts with a slash.\n * Returns undefined if the input is falsy.\n */\nexport function normalizeRoutePath(pattern?: string): string | undefined {\n if (!pattern) {\n return undefined;\n }\n return pattern.startsWith('/') ? pattern : `/${pattern}`;\n}\n\n/**\n * Captures an error from instrumentation result.\n * Caller must verify result contains an Error before calling.\n */\nexport function captureInstrumentationError(\n result: InstrumentationResult,\n captureErrors: boolean,\n mechanismType: string,\n data: Record<string, string | boolean>,\n): void {\n if (captureErrors) {\n captureException(result.error, {\n mechanism: {\n type: mechanismType,\n handled: false,\n data,\n },\n });\n }\n}\n"],"names":[],"mappings":";;;AAIA;AACA;AACA;AACA;AACO,SAAS,kBAAkB,CAAC,OAAO,EAA2B;AACrE,EAAE,IAAI;AACN,IAAI,OAAO,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,QAAQ;AACxC,EAAE,EAAE,MAAM;AACV,IAAI,IAAI;AACR;AACA,MAAM,OAAO,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,oBAAoB,CAAC,CAAC,QAAQ;AAChE,IAAI,CAAA,CAAE,OAAO,KAAK,EAAE;AACpB,MAAM,WAAA,IAAe,KAAK,CAAC,IAAI,CAAC,mCAAmC,EAAE,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC;AACxF,MAAM,OAAO,WAAW;AACxB,IAAI;AACJ,EAAE;AACF;;AAEA;AACA;AACA;AACA;AACO,SAAS,UAAU,CAAC,IAAI,EAAuE;AACtG,EAAE,OAAO,IAAI,CAAC,WAAW,IAAI,CAAC,gBAAgB;AAC9C;;AAEA;AACA;AACA;AACA;AACO,SAAS,kBAAkB,CAAC,OAAO,EAA+B;AACzE,EAAE,IAAI,CAAC,OAAO,EAAE;AAChB,IAAI,OAAO,SAAS;AACpB,EAAE;AACF,EAAE,OAAO,OAAO,CAAC,UAAU,CAAC,GAAG,CAAA,GAAI,OAAA,GAAU,CAAC,CAAC,EAAE,OAAO,CAAC,CAAA;AACA;;AAEA;AACA;AACA;AACA;AACA,SAAA,2BAAA;AACA,EAAA,MAAA;AACA,EAAA,aAAA;AACA,EAAA,aAAA;AACA,EAAA,IAAA;AACA,EAAA;AACA,EAAA,IAAA,aAAA,EAAA;AACA,IAAA,gBAAA,CAAA,MAAA,CAAA,KAAA,EAAA;AACA,MAAA,SAAA,EAAA;AACA,QAAA,IAAA,EAAA,aAAA;AACA,QAAA,OAAA,EAAA,KAAA;AACA,QAAA,IAAA;AACA,OAAA;AACA,KAAA,CAAA;AACA,EAAA;AACA;;;;"}
|
|
@@ -2,4 +2,5 @@ export * from '@sentry/browser';
|
|
|
2
2
|
export { init } from './client/sdk.js';
|
|
3
3
|
export { reactRouterTracingIntegration } from './client/tracingIntegration.js';
|
|
4
4
|
export { ErrorBoundary, Profiler, captureReactException, reactErrorHandler, useProfiler, withErrorBoundary, withProfiler } from '@sentry/react';
|
|
5
|
+
export { createSentryClientInstrumentation, isClientInstrumentationApiUsed, isNavigateHookInvoked } from './client/createClientInstrumentation.js';
|
|
5
6
|
//# sourceMappingURL=index.client.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.client.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.client.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
|
|
@@ -6,7 +6,9 @@ export { wrapServerAction } from './server/wrapServerAction.js';
|
|
|
6
6
|
export { wrapServerLoader } from './server/wrapServerLoader.js';
|
|
7
7
|
export { createSentryHandleError } from './server/createSentryHandleError.js';
|
|
8
8
|
export { getMetaTagTransformer } from './server/getMetaTagTransformer.js';
|
|
9
|
+
export { createSentryServerInstrumentation } from './server/createServerInstrumentation.js';
|
|
9
10
|
export { sentryReactRouter } from './vite/plugin.js';
|
|
10
11
|
export { sentryOnBuildEnd } from './vite/buildEnd/handleOnBuildEnd.js';
|
|
11
12
|
export { makeConfigInjectorPlugin } from './vite/makeConfigInjectorPlugin.js';
|
|
13
|
+
export { isInstrumentationApiUsed } from './server/serverGlobals.js';
|
|
12
14
|
//# sourceMappingURL=index.server.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.server.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.server.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;"}
|
package/build/esm/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"type":"module","version":"10.
|
|
1
|
+
{"type":"module","version":"10.38.0"}
|
|
@@ -6,10 +6,7 @@ import { captureException, flushIfServerless } from '@sentry/core';
|
|
|
6
6
|
* @returns A Sentry-instrumented handleError function
|
|
7
7
|
*/
|
|
8
8
|
function createSentryHandleError({ logErrors = false }) {
|
|
9
|
-
const handleError = async function handleError(
|
|
10
|
-
error,
|
|
11
|
-
args,
|
|
12
|
-
) {
|
|
9
|
+
const handleError = async function handleError(error, args) {
|
|
13
10
|
// React Router may abort some interrupted requests, don't report those
|
|
14
11
|
if (!args.request.signal.aborted) {
|
|
15
12
|
captureException(error, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createSentryHandleError.js","sources":["../../../src/server/createSentryHandleError.ts"],"sourcesContent":["import { captureException, flushIfServerless } from '@sentry/core';\nimport type {
|
|
1
|
+
{"version":3,"file":"createSentryHandleError.js","sources":["../../../src/server/createSentryHandleError.ts"],"sourcesContent":["import { captureException, flushIfServerless } from '@sentry/core';\nimport type { HandleErrorFunction } from 'react-router';\n\nexport type SentryHandleErrorOptions = {\n logErrors?: boolean;\n};\n\n/**\n * A complete Sentry-instrumented handleError implementation that handles error reporting\n *\n * @returns A Sentry-instrumented handleError function\n */\nexport function createSentryHandleError({ logErrors = false }: SentryHandleErrorOptions): HandleErrorFunction {\n const handleError: HandleErrorFunction = async function handleError(error, args): Promise<void> {\n // React Router may abort some interrupted requests, don't report those\n if (!args.request.signal.aborted) {\n captureException(error, {\n mechanism: {\n type: 'react-router',\n handled: false,\n },\n });\n if (logErrors) {\n // eslint-disable-next-line no-console\n console.error(error);\n }\n try {\n await flushIfServerless();\n } catch {\n // Ignore flush errors to ensure error handling completes gracefully\n }\n }\n };\n\n return handleError;\n}\n"],"names":[],"mappings":";;AAOA;AACA;AACA;AACA;AACA;AACO,SAAS,uBAAuB,CAAC,EAAE,YAAY,KAAA,EAAO,EAAiD;AAC9G,EAAE,MAAM,WAAW,GAAwB,eAAe,WAAW,CAAC,KAAK,EAAE,IAAI,EAAiB;AAClG;AACA,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE;AACtC,MAAM,gBAAgB,CAAC,KAAK,EAAE;AAC9B,QAAQ,SAAS,EAAE;AACnB,UAAU,IAAI,EAAE,cAAc;AAC9B,UAAU,OAAO,EAAE,KAAK;AACxB,SAAS;AACT,OAAO,CAAC;AACR,MAAM,IAAI,SAAS,EAAE;AACrB;AACA,QAAQ,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC;AAC5B,MAAM;AACN,MAAM,IAAI;AACV,QAAQ,MAAM,iBAAiB,EAAE;AACjC,MAAM,EAAE,MAAM;AACd;AACA,MAAM;AACN,IAAI;AACJ,EAAE,CAAC;;AAEH,EAAE,OAAO,WAAW;AACpB;;;;"}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { context } from '@opentelemetry/api';
|
|
2
|
+
import { getRPCMetadata, RPCType } from '@opentelemetry/core';
|
|
3
|
+
import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions';
|
|
4
|
+
import { debug, startSpan, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_OP, SPAN_STATUS_ERROR, getActiveSpan, getRootSpan, updateSpanName, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, flushIfServerless, getCurrentScope } from '@sentry/core';
|
|
5
|
+
import { DEBUG_BUILD } from '../common/debug-build.js';
|
|
6
|
+
import { captureInstrumentationError, getPathFromRequest, getPattern, normalizeRoutePath } from '../common/utils.js';
|
|
7
|
+
import { markInstrumentationApiUsed } from './serverGlobals.js';
|
|
8
|
+
export { isInstrumentationApiUsed } from './serverGlobals.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Options for creating Sentry server instrumentation.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Creates a Sentry server instrumentation for React Router's instrumentation API.
|
|
16
|
+
* @experimental
|
|
17
|
+
*/
|
|
18
|
+
function createSentryServerInstrumentation(
|
|
19
|
+
options = {},
|
|
20
|
+
) {
|
|
21
|
+
const { captureErrors = true } = options;
|
|
22
|
+
|
|
23
|
+
markInstrumentationApiUsed();
|
|
24
|
+
DEBUG_BUILD && debug.log('React Router server instrumentation API enabled.');
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
handler(handler) {
|
|
28
|
+
handler.instrument({
|
|
29
|
+
async request(handleRequest, info) {
|
|
30
|
+
const pathname = getPathFromRequest(info.request);
|
|
31
|
+
const activeSpan = getActiveSpan();
|
|
32
|
+
const existingRootSpan = activeSpan ? getRootSpan(activeSpan) : undefined;
|
|
33
|
+
|
|
34
|
+
if (existingRootSpan) {
|
|
35
|
+
updateSpanName(existingRootSpan, `${info.request.method} ${pathname}`);
|
|
36
|
+
existingRootSpan.setAttributes({
|
|
37
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server',
|
|
38
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react_router.instrumentation_api',
|
|
39
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const result = await handleRequest();
|
|
44
|
+
if (result.status === 'error' && result.error instanceof Error) {
|
|
45
|
+
existingRootSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
|
|
46
|
+
captureInstrumentationError(result, captureErrors, 'react_router.request_handler', {
|
|
47
|
+
'http.method': info.request.method,
|
|
48
|
+
'http.url': pathname,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
} finally {
|
|
52
|
+
await flushIfServerless();
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
await startSpan(
|
|
56
|
+
{
|
|
57
|
+
name: `${info.request.method} ${pathname}`,
|
|
58
|
+
forceTransaction: true,
|
|
59
|
+
attributes: {
|
|
60
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server',
|
|
61
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react_router.instrumentation_api',
|
|
62
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
|
|
63
|
+
'http.request.method': info.request.method,
|
|
64
|
+
'url.path': pathname,
|
|
65
|
+
'url.full': info.request.url,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
async span => {
|
|
69
|
+
try {
|
|
70
|
+
const result = await handleRequest();
|
|
71
|
+
if (result.status === 'error' && result.error instanceof Error) {
|
|
72
|
+
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
|
|
73
|
+
captureInstrumentationError(result, captureErrors, 'react_router.request_handler', {
|
|
74
|
+
'http.method': info.request.method,
|
|
75
|
+
'http.url': pathname,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
} finally {
|
|
79
|
+
await flushIfServerless();
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
route(route) {
|
|
89
|
+
route.instrument({
|
|
90
|
+
async loader(callLoader, info) {
|
|
91
|
+
const urlPath = getPathFromRequest(info.request);
|
|
92
|
+
const pattern = getPattern(info);
|
|
93
|
+
const routePattern = normalizeRoutePath(pattern) || urlPath;
|
|
94
|
+
updateRootSpanWithRoute(info.request.method, pattern, urlPath);
|
|
95
|
+
|
|
96
|
+
await startSpan(
|
|
97
|
+
{
|
|
98
|
+
name: routePattern,
|
|
99
|
+
attributes: {
|
|
100
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.loader',
|
|
101
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
async span => {
|
|
105
|
+
const result = await callLoader();
|
|
106
|
+
if (result.status === 'error' && result.error instanceof Error) {
|
|
107
|
+
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
|
|
108
|
+
captureInstrumentationError(result, captureErrors, 'react_router.loader', {
|
|
109
|
+
'http.method': info.request.method,
|
|
110
|
+
'http.url': urlPath,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
async action(callAction, info) {
|
|
118
|
+
const urlPath = getPathFromRequest(info.request);
|
|
119
|
+
const pattern = getPattern(info);
|
|
120
|
+
const routePattern = normalizeRoutePath(pattern) || urlPath;
|
|
121
|
+
updateRootSpanWithRoute(info.request.method, pattern, urlPath);
|
|
122
|
+
|
|
123
|
+
await startSpan(
|
|
124
|
+
{
|
|
125
|
+
name: routePattern,
|
|
126
|
+
attributes: {
|
|
127
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.action',
|
|
128
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
async span => {
|
|
132
|
+
const result = await callAction();
|
|
133
|
+
if (result.status === 'error' && result.error instanceof Error) {
|
|
134
|
+
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
|
|
135
|
+
captureInstrumentationError(result, captureErrors, 'react_router.action', {
|
|
136
|
+
'http.method': info.request.method,
|
|
137
|
+
'http.url': urlPath,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
);
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
async middleware(callMiddleware, info) {
|
|
145
|
+
const urlPath = getPathFromRequest(info.request);
|
|
146
|
+
const pattern = getPattern(info);
|
|
147
|
+
const routePattern = normalizeRoutePath(pattern) || urlPath;
|
|
148
|
+
|
|
149
|
+
// Update root span with parameterized route (same as loader/action)
|
|
150
|
+
updateRootSpanWithRoute(info.request.method, pattern, urlPath);
|
|
151
|
+
|
|
152
|
+
await startSpan(
|
|
153
|
+
{
|
|
154
|
+
name: routePattern,
|
|
155
|
+
attributes: {
|
|
156
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.middleware',
|
|
157
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
async span => {
|
|
161
|
+
const result = await callMiddleware();
|
|
162
|
+
if (result.status === 'error' && result.error instanceof Error) {
|
|
163
|
+
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
|
|
164
|
+
captureInstrumentationError(result, captureErrors, 'react_router.middleware', {
|
|
165
|
+
'http.method': info.request.method,
|
|
166
|
+
'http.url': urlPath,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
);
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
async lazy(callLazy) {
|
|
174
|
+
await startSpan(
|
|
175
|
+
{
|
|
176
|
+
name: 'Lazy Route Load',
|
|
177
|
+
attributes: {
|
|
178
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.lazy',
|
|
179
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
async span => {
|
|
183
|
+
const result = await callLazy();
|
|
184
|
+
if (result.status === 'error' && result.error instanceof Error) {
|
|
185
|
+
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
|
|
186
|
+
captureInstrumentationError(result, captureErrors, 'react_router.lazy', {});
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
);
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function updateRootSpanWithRoute(method, pattern, urlPath) {
|
|
197
|
+
const activeSpan = getActiveSpan();
|
|
198
|
+
if (!activeSpan) return;
|
|
199
|
+
const rootSpan = getRootSpan(activeSpan);
|
|
200
|
+
if (!rootSpan) return;
|
|
201
|
+
|
|
202
|
+
// Skip update if URL path is invalid (failed to parse)
|
|
203
|
+
if (!urlPath || urlPath === '<unknown>') {
|
|
204
|
+
DEBUG_BUILD && debug.warn('Cannot update span with invalid URL path:', urlPath);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const hasPattern = !!pattern;
|
|
209
|
+
const routeName = hasPattern ? normalizeRoutePath(pattern) || urlPath : urlPath;
|
|
210
|
+
|
|
211
|
+
const rpcMetadata = getRPCMetadata(context.active());
|
|
212
|
+
if (rpcMetadata?.type === RPCType.HTTP) {
|
|
213
|
+
rpcMetadata.route = routeName;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const transactionName = `${method} ${routeName}`;
|
|
217
|
+
updateSpanName(rootSpan, transactionName);
|
|
218
|
+
rootSpan.setAttributes({
|
|
219
|
+
[ATTR_HTTP_ROUTE]: routeName,
|
|
220
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: hasPattern ? 'route' : 'url',
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Also update the scope's transaction name so errors captured during this request
|
|
224
|
+
// have the correct transaction name (not the initial placeholder like "GET *")
|
|
225
|
+
getCurrentScope().setTransactionName(transactionName);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export { createSentryServerInstrumentation };
|
|
229
|
+
//# sourceMappingURL=createServerInstrumentation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createServerInstrumentation.js","sources":["../../../src/server/createServerInstrumentation.ts"],"sourcesContent":["import { context } from '@opentelemetry/api';\nimport { getRPCMetadata, RPCType } from '@opentelemetry/core';\nimport { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions';\nimport {\n debug,\n flushIfServerless,\n getActiveSpan,\n getCurrentScope,\n getRootSpan,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,\n SPAN_STATUS_ERROR,\n startSpan,\n updateSpanName,\n} from '@sentry/core';\nimport { DEBUG_BUILD } from '../common/debug-build';\nimport type { InstrumentableRequestHandler, InstrumentableRoute, ServerInstrumentation } from '../common/types';\nimport { captureInstrumentationError, getPathFromRequest, getPattern, normalizeRoutePath } from '../common/utils';\nimport { markInstrumentationApiUsed } from './serverGlobals';\n\n// Re-export for backward compatibility and external use\nexport { isInstrumentationApiUsed } from './serverGlobals';\n\n/**\n * Options for creating Sentry server instrumentation.\n */\nexport interface CreateSentryServerInstrumentationOptions {\n /**\n * Whether to capture errors from loaders/actions automatically.\n * @default true\n */\n captureErrors?: boolean;\n}\n\n/**\n * Creates a Sentry server instrumentation for React Router's instrumentation API.\n * @experimental\n */\nexport function createSentryServerInstrumentation(\n options: CreateSentryServerInstrumentationOptions = {},\n): ServerInstrumentation {\n const { captureErrors = true } = options;\n\n markInstrumentationApiUsed();\n DEBUG_BUILD && debug.log('React Router server instrumentation API enabled.');\n\n return {\n handler(handler: InstrumentableRequestHandler) {\n handler.instrument({\n async request(handleRequest, info) {\n const pathname = getPathFromRequest(info.request);\n const activeSpan = getActiveSpan();\n const existingRootSpan = activeSpan ? getRootSpan(activeSpan) : undefined;\n\n if (existingRootSpan) {\n updateSpanName(existingRootSpan, `${info.request.method} ${pathname}`);\n existingRootSpan.setAttributes({\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react_router.instrumentation_api',\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',\n });\n\n try {\n const result = await handleRequest();\n if (result.status === 'error' && result.error instanceof Error) {\n existingRootSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });\n captureInstrumentationError(result, captureErrors, 'react_router.request_handler', {\n 'http.method': info.request.method,\n 'http.url': pathname,\n });\n }\n } finally {\n await flushIfServerless();\n }\n } else {\n await startSpan(\n {\n name: `${info.request.method} ${pathname}`,\n forceTransaction: true,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react_router.instrumentation_api',\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',\n 'http.request.method': info.request.method,\n 'url.path': pathname,\n 'url.full': info.request.url,\n },\n },\n async span => {\n try {\n const result = await handleRequest();\n if (result.status === 'error' && result.error instanceof Error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });\n captureInstrumentationError(result, captureErrors, 'react_router.request_handler', {\n 'http.method': info.request.method,\n 'http.url': pathname,\n });\n }\n } finally {\n await flushIfServerless();\n }\n },\n );\n }\n },\n });\n },\n\n route(route: InstrumentableRoute) {\n route.instrument({\n async loader(callLoader, info) {\n const urlPath = getPathFromRequest(info.request);\n const pattern = getPattern(info);\n const routePattern = normalizeRoutePath(pattern) || urlPath;\n updateRootSpanWithRoute(info.request.method, pattern, urlPath);\n\n await startSpan(\n {\n name: routePattern,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.loader',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',\n },\n },\n async span => {\n const result = await callLoader();\n if (result.status === 'error' && result.error instanceof Error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });\n captureInstrumentationError(result, captureErrors, 'react_router.loader', {\n 'http.method': info.request.method,\n 'http.url': urlPath,\n });\n }\n },\n );\n },\n\n async action(callAction, info) {\n const urlPath = getPathFromRequest(info.request);\n const pattern = getPattern(info);\n const routePattern = normalizeRoutePath(pattern) || urlPath;\n updateRootSpanWithRoute(info.request.method, pattern, urlPath);\n\n await startSpan(\n {\n name: routePattern,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.action',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',\n },\n },\n async span => {\n const result = await callAction();\n if (result.status === 'error' && result.error instanceof Error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });\n captureInstrumentationError(result, captureErrors, 'react_router.action', {\n 'http.method': info.request.method,\n 'http.url': urlPath,\n });\n }\n },\n );\n },\n\n async middleware(callMiddleware, info) {\n const urlPath = getPathFromRequest(info.request);\n const pattern = getPattern(info);\n const routePattern = normalizeRoutePath(pattern) || urlPath;\n\n // Update root span with parameterized route (same as loader/action)\n updateRootSpanWithRoute(info.request.method, pattern, urlPath);\n\n await startSpan(\n {\n name: routePattern,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.middleware',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',\n },\n },\n async span => {\n const result = await callMiddleware();\n if (result.status === 'error' && result.error instanceof Error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });\n captureInstrumentationError(result, captureErrors, 'react_router.middleware', {\n 'http.method': info.request.method,\n 'http.url': urlPath,\n });\n }\n },\n );\n },\n\n async lazy(callLazy) {\n await startSpan(\n {\n name: 'Lazy Route Load',\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.lazy',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',\n },\n },\n async span => {\n const result = await callLazy();\n if (result.status === 'error' && result.error instanceof Error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });\n captureInstrumentationError(result, captureErrors, 'react_router.lazy', {});\n }\n },\n );\n },\n });\n },\n };\n}\n\nfunction updateRootSpanWithRoute(method: string, pattern: string | undefined, urlPath: string): void {\n const activeSpan = getActiveSpan();\n if (!activeSpan) return;\n const rootSpan = getRootSpan(activeSpan);\n if (!rootSpan) return;\n\n // Skip update if URL path is invalid (failed to parse)\n if (!urlPath || urlPath === '<unknown>') {\n DEBUG_BUILD && debug.warn('Cannot update span with invalid URL path:', urlPath);\n return;\n }\n\n const hasPattern = !!pattern;\n const routeName = hasPattern ? normalizeRoutePath(pattern) || urlPath : urlPath;\n\n const rpcMetadata = getRPCMetadata(context.active());\n if (rpcMetadata?.type === RPCType.HTTP) {\n rpcMetadata.route = routeName;\n }\n\n const transactionName = `${method} ${routeName}`;\n updateSpanName(rootSpan, transactionName);\n rootSpan.setAttributes({\n [ATTR_HTTP_ROUTE]: routeName,\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: hasPattern ? 'route' : 'url',\n });\n\n // Also update the scope's transaction name so errors captured during this request\n // have the correct transaction name (not the initial placeholder like \"GET *\")\n getCurrentScope().setTransactionName(transactionName);\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { InstrumentationBase, InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation';
|
|
2
2
|
import { SEMATTRS_HTTP_TARGET } from '@opentelemetry/semantic-conventions';
|
|
3
|
-
import { SDK_VERSION, getActiveSpan, getRootSpan,
|
|
3
|
+
import { SDK_VERSION, debug, getActiveSpan, getRootSpan, spanToJSON, updateSpanName, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, startSpan, SEMANTIC_ATTRIBUTE_SENTRY_OP } from '@sentry/core';
|
|
4
4
|
import { DEBUG_BUILD } from '../../common/debug-build.js';
|
|
5
|
+
import { isInstrumentationApiUsed } from '../serverGlobals.js';
|
|
5
6
|
import { isDataRequest, getOpName, getSpanName } from './util.js';
|
|
6
7
|
|
|
7
8
|
const supportedVersions = ['>=7.0.0'];
|
|
@@ -61,6 +62,13 @@ class ReactRouterInstrumentation extends InstrumentationBase {
|
|
|
61
62
|
return originalRequestHandler(request, initialContext);
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
// Skip OTEL instrumentation if instrumentation API is being used
|
|
66
|
+
// as it handles loader/action spans itself
|
|
67
|
+
if (isInstrumentationApiUsed()) {
|
|
68
|
+
DEBUG_BUILD && debug.log('Skipping OTEL loader/action instrumentation - using instrumentation API');
|
|
69
|
+
return originalRequestHandler(request, initialContext);
|
|
70
|
+
}
|
|
71
|
+
|
|
64
72
|
const activeSpan = getActiveSpan();
|
|
65
73
|
const rootSpan = activeSpan && getRootSpan(activeSpan);
|
|
66
74
|
|
|
@@ -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 { SEMATTRS_HTTP_TARGET } from '@opentelemetry/semantic-conventions';\nimport {\n debug,\n getActiveSpan,\n getRootSpan,\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 {\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 && debug.log('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":"
|
|
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 debug,\n getActiveSpan,\n getRootSpan,\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 { isInstrumentationApiUsed } from '../serverGlobals';\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 {\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 // Skip OTEL instrumentation if instrumentation API is being used\n // as it handles loader/action spans itself\n if (isInstrumentationApiUsed()) {\n DEBUG_BUILD && debug.log('Skipping OTEL loader/action instrumentation - using instrumentation API');\n return originalRequestHandler(request, initialContext);\n }\n\n const activeSpan = getActiveSpan();\n const rootSpan = activeSpan && getRootSpan(activeSpan);\n\n if (!rootSpan) {\n DEBUG_BUILD && debug.log('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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"}
|
|
@@ -2,6 +2,7 @@ import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions';
|
|
|
2
2
|
import { defineIntegration } from '@sentry/core';
|
|
3
3
|
import { generateInstrumentOnce, NODE_VERSION } from '@sentry/node';
|
|
4
4
|
import { ReactRouterInstrumentation } from '../instrumentation/reactRouter.js';
|
|
5
|
+
import { isInstrumentationApiUsed } from '../serverGlobals.js';
|
|
5
6
|
|
|
6
7
|
const INTEGRATION_NAME = 'ReactRouterServer';
|
|
7
8
|
|
|
@@ -23,6 +24,11 @@ const reactRouterServerIntegration = defineIntegration(() => {
|
|
|
23
24
|
return {
|
|
24
25
|
name: INTEGRATION_NAME,
|
|
25
26
|
setupOnce() {
|
|
27
|
+
// Skip OTEL patching if the instrumentation API is in use
|
|
28
|
+
if (isInstrumentationApiUsed()) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
26
32
|
if (
|
|
27
33
|
(NODE_VERSION.major === 20 && NODE_VERSION.minor < 19) || // https://nodejs.org/en/blog/release/v20.19.0
|
|
28
34
|
(NODE_VERSION.major === 22 && NODE_VERSION.minor < 12) // https://nodejs.org/en/blog/release/v22.12.0
|
|
@@ -36,13 +42,17 @@ const reactRouterServerIntegration = defineIntegration(() => {
|
|
|
36
42
|
if (
|
|
37
43
|
event.type === 'transaction' &&
|
|
38
44
|
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 *'
|
|
45
|
+
event.contexts.trace.data[ATTR_HTTP_ROUTE] === '*'
|
|
43
46
|
) {
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
const origin = event.contexts.trace.origin;
|
|
48
|
+
const isInstrumentationApiOrigin = origin?.includes('instrumentation_api');
|
|
49
|
+
|
|
50
|
+
// For instrumentation_api, always clean up bogus `*` route since we set better names
|
|
51
|
+
// For legacy, only clean up if the name has been adjusted (not METHOD *)
|
|
52
|
+
if (isInstrumentationApiOrigin || !event.transaction?.endsWith(' *')) {
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
54
|
+
delete event.contexts.trace.data[ATTR_HTTP_ROUTE];
|
|
55
|
+
}
|
|
46
56
|
}
|
|
47
57
|
|
|
48
58
|
return event;
|
|
@@ -1 +1 @@
|
|
|
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(INTEGRATION_NAME, () => {\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] === '*'
|
|
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';\nimport { isInstrumentationApiUsed } from '../serverGlobals';\n\nconst INTEGRATION_NAME = 'ReactRouterServer';\n\nconst instrumentReactRouter = generateInstrumentOnce(INTEGRATION_NAME, () => {\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 // Skip OTEL patching if the instrumentation API is in use\n if (isInstrumentationApiUsed()) {\n return;\n }\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 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 ) {\n const origin = event.contexts.trace.origin;\n const isInstrumentationApiOrigin = origin?.includes('instrumentation_api');\n\n // For instrumentation_api, always clean up bogus `*` route since we set better names\n // For legacy, only clean up if the name has been adjusted (not METHOD *)\n if (isInstrumentationApiOrigin || !event.transaction?.endsWith(' *')) {\n // eslint-disable-next-line @typescript-eslint/no-dynamic-delete\n delete event.contexts.trace.data[ATTR_HTTP_ROUTE];\n }\n }\n\n return event;\n },\n };\n});\n"],"names":[],"mappings":";;;;;;AAMA,MAAM,gBAAA,GAAmB,mBAAmB;;AAE5C,MAAM,qBAAA,GAAwB,sBAAsB,CAAC,gBAAgB,EAAE,MAAM;AAC7E,EAAE,OAAO,IAAI,0BAA0B,EAAE;AACzC,CAAC,CAAC;;AAEK,MAAM,2BAAA,GAA8B,MAAM,CAAC,MAAM;AACxD,EAAE,MAAY;AACd,IAAI,qBAAqB,EAAE;AAC3B,EAAE,CAAC;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;AACA,MAAM,IAAI,wBAAwB,EAAE,EAAE;AACtC,QAAQ;AACR,MAAM;;AAEN,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,MAAM;AACN,IAAI,CAAC;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;AACvD,QAAQ;AACR,QAAQ,MAAM,SAAS,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM;AAClD,QAAQ,MAAM,6BAA6B,MAAM,EAAE,QAAQ,CAAC,qBAAqB,CAAC;;AAElF;AACA;AACA,QAAQ,IAAI,0BAAA,IAA8B,CAAC,KAAK,CAAC,WAAW,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE;AAC9E;AACA,UAAU,OAAO,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC;AAC3D,QAAQ;AACR,MAAM;;AAEN,MAAM,OAAO,KAAK;AAClB,IAAI,CAAC;AACL,GAAG;AACH,CAAC;;;;"}
|