1
- import { captureException , flush , getCurrentHub , startTransaction } from '@sentry/node' ;
1
+ import { captureException , getCurrentHub , startTransaction } from '@sentry/node' ;
2
2
import { extractTraceparentData , hasTracingEnabled } from '@sentry/tracing' ;
3
3
import {
4
4
addExceptionMechanism ,
@@ -11,14 +11,8 @@ import {
11
11
import * as domain from 'domain' ;
12
12
13
13
import { formatAsCode , nextLogger } from '../../utils/nextLogger' ;
14
- import type {
15
- AugmentedNextApiRequest ,
16
- AugmentedNextApiResponse ,
17
- NextApiHandler ,
18
- ResponseEndMethod ,
19
- WrappedNextApiHandler ,
20
- WrappedResponseEndMethod ,
21
- } from './types' ;
14
+ import type { AugmentedNextApiRequest , AugmentedNextApiResponse , NextApiHandler , WrappedNextApiHandler } from './types' ;
15
+ import { autoEndTransactionOnResponseEnd , finishTransaction , flushQueue } from './utils/responseEnd' ;
22
16
23
17
/**
24
18
* Wrap the given API route handler for tracing and error capturing. Thin wrapper around `withSentry`, which only
@@ -72,11 +66,6 @@ export function withSentry(origHandler: NextApiHandler, parameterizedRoute?: str
72
66
}
73
67
req . __withSentry_applied__ = true ;
74
68
75
- // first order of business: monkeypatch `res.end()` so that it will wait for us to send events to sentry before it
76
- // fires (if we don't do this, the lambda will close too early and events will be either delayed or lost)
77
- // eslint-disable-next-line @typescript-eslint/unbound-method
78
- res . end = wrapEndMethod ( res . end ) ;
79
-
80
69
// use a domain in order to prevent scope bleed between requests
81
70
const local = domain . create ( ) ;
82
71
local . add ( req ) ;
@@ -137,9 +126,7 @@ export function withSentry(origHandler: NextApiHandler, parameterizedRoute?: str
137
126
) ;
138
127
currentScope . setSpan ( transaction ) ;
139
128
140
- // save a link to the transaction on the response, so that even if there's an error (landing us outside of
141
- // the domain), we can still finish it (albeit possibly missing some scope data)
142
- res . __sentryTransaction = transaction ;
129
+ autoEndTransactionOnResponseEnd ( transaction , res ) ;
143
130
}
144
131
}
145
132
@@ -189,7 +176,8 @@ export function withSentry(origHandler: NextApiHandler, parameterizedRoute?: str
189
176
// out. (Apps which are deployed on Vercel run their API routes in lambdas, and those lambdas will shut down the
190
177
// moment they detect an error, so it's important to get this done before rethrowing the error. Apps not
191
178
// deployed serverlessly will run into this cleanup function again in `res.end(), but it'll just no-op.)
192
- await finishSentryProcessing ( res ) ;
179
+ await finishTransaction ( res ) ;
180
+ await flushQueue ( ) ;
193
181
194
182
// We rethrow here so that nextjs can do with the error whatever it would normally do. (Sometimes "whatever it
195
183
// would normally do" is to allow the error to bubble up to the global handlers - another reason we need to mark
@@ -203,57 +191,3 @@ export function withSentry(origHandler: NextApiHandler, parameterizedRoute?: str
203
191
return boundHandler ( ) ;
204
192
} ;
205
193
}
206
-
207
- /**
208
- * Wrap `res.end()` so that it closes the transaction and flushes events before letting the request finish.
209
- *
210
- * Note: This wraps a sync method with an async method. While in general that's not a great idea in terms of keeping
211
- * things in the right order, in this case it's safe, because the native `.end()` actually *is* async, and its run
212
- * actually *is* awaited, just manually so (which reflects the fact that the core of the request/response code in Node
213
- * by far predates the introduction of `async`/`await`). When `.end()` is done, it emits the `prefinish` event, and
214
- * only once that fires does request processing continue. See
215
- * https://github.com/nodejs/node/commit/7c9b607048f13741173d397795bac37707405ba7.
216
- *
217
- * @param origEnd The original `res.end()` method
218
- * @returns The wrapped version
219
- */
220
- function wrapEndMethod ( origEnd : ResponseEndMethod ) : WrappedResponseEndMethod {
221
- return async function newEnd ( this : AugmentedNextApiResponse , ...args : unknown [ ] ) {
222
- await finishSentryProcessing ( this ) ;
223
-
224
- return origEnd . call ( this , ...args ) ;
225
- } ;
226
- }
227
-
228
- /**
229
- * Close the open transaction (if any) and flush events to Sentry.
230
- *
231
- * @param res The outgoing response for this request, on which the transaction is stored
232
- */
233
- async function finishSentryProcessing ( res : AugmentedNextApiResponse ) : Promise < void > {
234
- const { __sentryTransaction : transaction } = res ;
235
-
236
- if ( transaction ) {
237
- transaction . setHttpStatus ( res . statusCode ) ;
238
-
239
- // Push `transaction.finish` to the next event loop so open spans have a better chance of finishing before the
240
- // transaction closes, and make sure to wait until that's done before flushing events
241
- const transactionFinished : Promise < void > = new Promise ( resolve => {
242
- setImmediate ( ( ) => {
243
- transaction . finish ( ) ;
244
- resolve ( ) ;
245
- } ) ;
246
- } ) ;
247
- await transactionFinished ;
248
- }
249
-
250
- // Flush the event queue to ensure that events get sent to Sentry before the response is finished and the lambda
251
- // ends. If there was an error, rethrow it so that the normal exception-handling mechanisms can apply.
252
- try {
253
- __DEBUG_BUILD__ && logger . log ( 'Flushing events...' ) ;
254
- await flush ( 2000 ) ;
255
- __DEBUG_BUILD__ && logger . log ( 'Done flushing events' ) ;
256
- } catch ( e ) {
257
- __DEBUG_BUILD__ && logger . log ( 'Error while flushing events:\n' , e ) ;
258
- }
259
- }
0 commit comments