|
1 | | -import { |
2 | | - captureException, |
3 | | - getActiveSpan, |
4 | | - getCurrentScope, |
5 | | - getRootSpan, |
6 | | - handleCallbackErrors, |
7 | | - SEMANTIC_ATTRIBUTE_SENTRY_OP, |
8 | | - SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, |
9 | | - SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, |
10 | | - setCapturedScopesOnSpan, |
11 | | - startSpan, |
12 | | - winterCGRequestToRequestData, |
13 | | - withIsolationScope, |
14 | | -} from '@sentry/core'; |
15 | | -import { addHeadersAsAttributes } from '../common/utils/addHeadersAsAttributes'; |
16 | | -import { flushSafelyWithTimeout, waitUntil } from '../common/utils/responseEnd'; |
| 1 | +import { captureException, getIsolationScope, winterCGRequestToRequestData } from '@sentry/core'; |
| 2 | +import { flushSafelyWithTimeout } from '../common/utils/responseEnd'; |
17 | 3 | import type { EdgeRouteHandler } from './types'; |
18 | 4 |
|
19 | 5 | /** |
20 | | - * Wraps a Next.js edge route handler with Sentry error and performance instrumentation. |
| 6 | + * Wraps a Next.js edge route handler with Sentry error monitoring. |
21 | 7 | */ |
22 | 8 | export function wrapApiHandlerWithSentry<H extends EdgeRouteHandler>( |
23 | 9 | handler: H, |
24 | 10 | parameterizedRoute: string, |
25 | 11 | ): (...params: Parameters<H>) => Promise<ReturnType<H>> { |
26 | 12 | return new Proxy(handler, { |
27 | 13 | apply: async (wrappingTarget, thisArg, args: Parameters<H>) => { |
28 | | - // TODO: We still should add central isolation scope creation for when our build-time instrumentation does not work anymore with turbopack. |
29 | | - |
30 | | - return withIsolationScope(isolationScope => { |
| 14 | + try { |
31 | 15 | const req: unknown = args[0]; |
32 | | - const currentScope = getCurrentScope(); |
33 | 16 |
|
34 | | - let headerAttributes: Record<string, string> = {}; |
| 17 | + // Set transaction name on isolation scope to ensure parameterized routes are used |
| 18 | + // The HTTP server integration sets it on isolation scope, so we need to match that |
| 19 | + const isolationScope = getIsolationScope(); |
35 | 20 |
|
36 | 21 | if (req instanceof Request) { |
| 22 | + const method = req.method || 'GET'; |
| 23 | + isolationScope.setTransactionName(`${method} ${parameterizedRoute}`); |
| 24 | + // Set SDK processing metadata |
37 | 25 | isolationScope.setSDKProcessingMetadata({ |
38 | 26 | normalizedRequest: winterCGRequestToRequestData(req), |
39 | 27 | }); |
40 | | - currentScope.setTransactionName(`${req.method} ${parameterizedRoute}`); |
41 | | - headerAttributes = addHeadersAsAttributes(req.headers); |
42 | 28 | } else { |
43 | | - currentScope.setTransactionName(`handler (${parameterizedRoute})`); |
| 29 | + isolationScope.setTransactionName(`handler (${parameterizedRoute})`); |
44 | 30 | } |
45 | 31 |
|
46 | | - let spanName: string; |
47 | | - let op: string | undefined = 'http.server'; |
| 32 | + return await wrappingTarget.apply(thisArg, args); |
| 33 | + } catch (error) { |
| 34 | + captureException(error, { |
| 35 | + mechanism: { |
| 36 | + type: 'auto.function.nextjs.wrap_api_handler', |
| 37 | + handled: false, |
| 38 | + }, |
| 39 | + }); |
48 | 40 |
|
49 | | - // If there is an active span, it likely means that the automatic Next.js OTEL instrumentation worked and we can |
50 | | - // rely on that for parameterization. |
51 | | - const activeSpan = getActiveSpan(); |
52 | | - if (activeSpan) { |
53 | | - spanName = `handler (${parameterizedRoute})`; |
54 | | - op = undefined; |
| 41 | + // we need to await the flush here to ensure that the error is captured |
| 42 | + // as the runtime freezes as soon as the error is thrown below |
| 43 | + await flushSafelyWithTimeout(); |
55 | 44 |
|
56 | | - const rootSpan = getRootSpan(activeSpan); |
57 | | - if (rootSpan) { |
58 | | - rootSpan.updateName( |
59 | | - req instanceof Request ? `${req.method} ${parameterizedRoute}` : `handler ${parameterizedRoute}`, |
60 | | - ); |
61 | | - rootSpan.setAttributes({ |
62 | | - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server', |
63 | | - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', |
64 | | - ...headerAttributes, |
65 | | - }); |
66 | | - setCapturedScopesOnSpan(rootSpan, currentScope, isolationScope); |
67 | | - } |
68 | | - } else if (req instanceof Request) { |
69 | | - spanName = `${req.method} ${parameterizedRoute}`; |
70 | | - } else { |
71 | | - spanName = `handler ${parameterizedRoute}`; |
72 | | - } |
73 | | - |
74 | | - return startSpan( |
75 | | - { |
76 | | - name: spanName, |
77 | | - op: op, |
78 | | - attributes: { |
79 | | - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', |
80 | | - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs.wrap_api_handler', |
81 | | - ...headerAttributes, |
82 | | - }, |
83 | | - }, |
84 | | - () => { |
85 | | - return handleCallbackErrors( |
86 | | - () => wrappingTarget.apply(thisArg, args), |
87 | | - error => { |
88 | | - captureException(error, { |
89 | | - mechanism: { |
90 | | - type: 'auto.function.nextjs.wrap_api_handler', |
91 | | - handled: false, |
92 | | - }, |
93 | | - }); |
94 | | - }, |
95 | | - () => { |
96 | | - waitUntil(flushSafelyWithTimeout()); |
97 | | - }, |
98 | | - ); |
99 | | - }, |
100 | | - ); |
101 | | - }); |
| 45 | + // We rethrow here so that nextjs can do with the error whatever it would normally do. |
| 46 | + throw error; |
| 47 | + } |
102 | 48 | }, |
103 | 49 | }); |
104 | 50 | } |
0 commit comments