1- import {
2- captureException ,
3- continueTrace ,
4- debug ,
5- getActiveSpan ,
6- httpRequestToRequestData ,
7- isString ,
8- isURLObjectRelative ,
9- objectify ,
10- parseStringToURLObject ,
11- SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ,
12- SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ,
13- setHttpStatus ,
14- startSpanManual ,
15- withIsolationScope ,
16- } from '@sentry/core' ;
1+ import { captureException , debug , objectify } from '@sentry/core' ;
172import type { NextApiRequest } from 'next' ;
183import type { AugmentedNextApiResponse , NextApiHandler } from '../types' ;
19- import { flushSafelyWithTimeout , waitUntil } from '../utils/responseEnd' ;
20- import { dropNextjsRootContext , escapeNextjsTracing } from '../utils/tracingUtils' ;
21- import { HTTP_ROUTE , SENTRY_KIND , URL_FULL , URL_PATH } from '@sentry/conventions/attributes' ;
4+ import { flushSafelyWithTimeout } from '../utils/responseEnd' ;
225
236export type AugmentedNextApiRequest = NextApiRequest & {
247 __withSentry_applied__ ?: boolean ;
@@ -34,15 +17,13 @@ export type AugmentedNextApiRequest = NextApiRequest & {
3417 */
3518export function wrapApiHandlerWithSentry ( apiHandler : NextApiHandler , parameterizedRoute : string ) : NextApiHandler {
3619 return new Proxy ( apiHandler , {
37- apply : (
20+ apply : async (
3821 wrappingTarget ,
3922 thisArg ,
4023 args : [ AugmentedNextApiRequest | undefined , AugmentedNextApiResponse | undefined ] ,
4124 ) => {
42- dropNextjsRootContext ( ) ;
43- return escapeNextjsTracing ( ( ) => {
25+ try {
4426 const [ req , res ] = args ;
45-
4627 if ( ! req ) {
4728 debug . log (
4829 `Wrapped API handler on route "${ parameterizedRoute } " was not passed a request object. Will not instrument.` ,
@@ -59,93 +40,37 @@ export function wrapApiHandlerWithSentry(apiHandler: NextApiHandler, parameteriz
5940 if ( req . __withSentry_applied__ ) {
6041 return wrappingTarget . apply ( thisArg , args ) ;
6142 }
43+
6244 req . __withSentry_applied__ = true ;
6345
64- return withIsolationScope ( isolationScope => {
65- // Normally, there is an active span here (from Next.js OTEL) and we just use that as parent
66- // Else, we manually continueTrace from the incoming headers
67- const continueTraceIfNoActiveSpan = getActiveSpan ( )
68- ? < T > ( _opts : unknown , callback : ( ) => T ) => callback ( )
69- : continueTrace ;
46+ return await wrappingTarget . apply ( thisArg , args ) ;
47+ } catch ( e ) {
48+ // In case we have a primitive, wrap it in the equivalent wrapper class (string -> String, etc.) so that we can
49+ // store a seen flag on it. (Because of the one-way-on-Vercel-one-way-off-of-Vercel approach we've been forced
50+ // to take, it can happen that the same thrown object gets caught in two different ways, and flagging it is a
51+ // way to prevent it from actually being reported twice.)
52+ const objectifiedErr = objectify ( e ) ;
7053
71- return continueTraceIfNoActiveSpan (
72- {
73- sentryTrace :
74- req . headers && isString ( req . headers [ 'sentry-trace' ] ) ? req . headers [ 'sentry-trace' ] : undefined ,
75- baggage : req . headers ?. baggage ,
54+ captureException ( objectifiedErr , {
55+ mechanism : {
56+ type : 'auto.http.nextjs.api_handler' ,
57+ handled : false ,
58+ data : {
59+ wrapped_handler : wrappingTarget . name ,
60+ function : 'withSentry' ,
7661 } ,
77- ( ) => {
78- const reqMethod = `${ ( req . method || 'GET' ) . toUpperCase ( ) } ` ;
79- const normalizedRequest = httpRequestToRequestData ( req ) ;
80-
81- isolationScope . setSDKProcessingMetadata ( { normalizedRequest } ) ;
82- isolationScope . setTransactionName ( `${ reqMethod } ${ parameterizedRoute } ` ) ;
83-
84- const requestUrl = normalizedRequest . url || req . url ;
85- const urlObject = requestUrl ? parseStringToURLObject ( requestUrl ) : undefined ;
86-
87- return startSpanManual (
88- {
89- name : `${ reqMethod } ${ parameterizedRoute } ` ,
90- op : 'http.server' ,
91- forceTransaction : true ,
92- attributes : {
93- [ SENTRY_KIND ] : 'server' ,
94- [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] : 'route' ,
95- [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.http.nextjs' ,
96- [ URL_FULL ] : urlObject && ! isURLObjectRelative ( urlObject ) ? urlObject . href : undefined ,
97- [ URL_PATH ] : urlObject ?. pathname ,
98- [ HTTP_ROUTE ] : parameterizedRoute ,
99- } ,
100- } ,
101- async span => {
102- // eslint-disable-next-line @typescript-eslint/unbound-method
103- res . end = new Proxy ( res . end , {
104- apply ( target , thisArg , argArray ) {
105- setHttpStatus ( span , res . statusCode ) ;
106- span . end ( ) ;
107- waitUntil ( flushSafelyWithTimeout ( ) ) ;
108- return target . apply ( thisArg , argArray ) ;
109- } ,
110- } ) ;
111- try {
112- return await wrappingTarget . apply ( thisArg , args ) ;
113- } catch ( e ) {
114- // In case we have a primitive, wrap it in the equivalent wrapper class (string -> String, etc.) so that we can
115- // store a seen flag on it. (Because of the one-way-on-Vercel-one-way-off-of-Vercel approach we've been forced
116- // to take, it can happen that the same thrown object gets caught in two different ways, and flagging it is a
117- // way to prevent it from actually being reported twice.)
118- const objectifiedErr = objectify ( e ) ;
119-
120- captureException ( objectifiedErr , {
121- mechanism : {
122- type : 'auto.http.nextjs.api_handler' ,
123- handled : false ,
124- data : {
125- wrapped_handler : wrappingTarget . name ,
126- function : 'withSentry' ,
127- } ,
128- } ,
129- } ) ;
130-
131- setHttpStatus ( span , 500 ) ;
132- span . end ( ) ;
62+ } ,
63+ } ) ;
13364
134- // we need to await the flush here to ensure that the error is captured
135- // as the runtime freezes as soon as the error is thrown below
136- await flushSafelyWithTimeout ( ) ;
65+ // we need to await the flush here to ensure that the error is captured
66+ // as the runtime freezes as soon as the error is thrown below
67+ await flushSafelyWithTimeout ( ) ;
13768
138- // We rethrow here so that nextjs can do with the error whatever it would normally do. (Sometimes "whatever it
139- // would normally do" is to allow the error to bubble up to the global handlers - another reason we need to mark
140- // the error as already having been captured.)
141- throw objectifiedErr ;
142- }
143- } ,
144- ) ;
145- } ,
146- ) ;
147- } ) ;
148- } ) ;
69+ // We rethrow here so that nextjs can do with the error whatever it would normally do. (Sometimes "whatever it
70+ // would normally do" is to allow the error to bubble up to the global handlers - another reason we need to mark
71+ // the error as already having been captured.)
72+ throw objectifiedErr ;
73+ }
14974 } ,
15075 } ) ;
15176}
0 commit comments