1
1
/* eslint-disable max-lines */
2
2
import {
3
+ SEMANTIC_ATTRIBUTE_SENTRY_OP ,
3
4
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ,
4
5
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ,
6
+ continueTrace ,
5
7
getActiveSpan ,
6
8
getActiveTransaction ,
7
9
getClient ,
8
- getCurrentScope ,
9
10
getDynamicSamplingContextFromSpan ,
11
+ getRootSpan ,
12
+ handleCallbackErrors ,
10
13
hasTracingEnabled ,
11
14
setHttpStatus ,
12
15
spanToJSON ,
13
16
spanToTraceHeader ,
17
+ startSpan ,
14
18
withIsolationScope ,
15
19
} from '@sentry/core' ;
16
- import { captureException , getCurrentHub } from '@sentry/node-experimental' ;
17
- import type { Hub , Transaction , TransactionSource , WrappedFunction } from '@sentry/types' ;
20
+ import { captureException } from '@sentry/node-experimental' ;
21
+ import type { Span , TransactionSource , WrappedFunction } from '@sentry/types' ;
18
22
import {
19
23
addExceptionMechanism ,
20
24
dynamicSamplingContextToSentryBaggageHeader ,
@@ -24,7 +28,6 @@ import {
24
28
loadModule ,
25
29
logger ,
26
30
objectify ,
27
- tracingContextFromHeaders ,
28
31
} from '@sentry/utils' ;
29
32
30
33
import { DEBUG_BUILD } from './debug-build' ;
@@ -186,46 +189,50 @@ function makeWrappedDocumentRequestFunction(remixVersion?: number) {
186
189
context : EntryContext ,
187
190
loadContext ?: Record < string , unknown > ,
188
191
) : Promise < Response > {
189
- let res : Response ;
190
- // eslint-disable-next-line deprecation/deprecation
191
- const activeTransaction = getActiveTransaction ( ) ;
192
+ const activeSpan = getActiveSpan ( ) ;
193
+ const rootSpan = activeSpan && getRootSpan ( activeSpan ) ;
192
194
193
- try {
194
- // eslint-disable-next-line deprecation/deprecation
195
- const span = activeTransaction ?. startChild ( {
196
- op : 'function.remix.document_request' ,
197
- origin : 'auto.function.remix' ,
198
- name : spanToJSON ( activeTransaction ) . description ,
195
+ const name = rootSpan ? spanToJSON ( rootSpan ) . description : undefined ;
196
+
197
+ return startSpan (
198
+ {
199
+ // If we don't have a root span, `onlyIfParent` will lead to the span not being created anyhow
200
+ // So we don't need to care too much about the fallback name, it's just for typing purposes....
201
+ name : name || '<unknown>' ,
202
+ onlyIfParent : true ,
199
203
attributes : {
200
204
method : request . method ,
201
205
url : request . url ,
206
+ [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.function.remix' ,
207
+ [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'function.remix.document_request' ,
202
208
} ,
203
- } ) ;
204
-
205
- res = await origDocumentRequestFunction . call (
206
- this ,
207
- request ,
208
- responseStatusCode ,
209
- responseHeaders ,
210
- context ,
211
- loadContext ,
212
- ) ;
213
-
214
- span ?. end ( ) ;
215
- } catch ( err ) {
216
- const isRemixV1 = ! FUTURE_FLAGS ?. v2_errorBoundary && remixVersion !== 2 ;
217
-
218
- // This exists to capture the server-side rendering errors on Remix v1
219
- // On Remix v2, we capture SSR errors at `handleError`
220
- // We also skip primitives here, as we can't dedupe them, and also we don't expect any primitive SSR errors.
221
- if ( isRemixV1 && ! isPrimitive ( err ) ) {
222
- await captureRemixServerException ( err , 'documentRequest' , request ) ;
223
- }
224
-
225
- throw err ;
226
- }
227
-
228
- return res ;
209
+ } ,
210
+ ( ) => {
211
+ return handleCallbackErrors (
212
+ ( ) => {
213
+ return origDocumentRequestFunction . call (
214
+ this ,
215
+ request ,
216
+ responseStatusCode ,
217
+ responseHeaders ,
218
+ context ,
219
+ loadContext ,
220
+ ) ;
221
+ } ,
222
+ err => {
223
+ const isRemixV1 = ! FUTURE_FLAGS ?. v2_errorBoundary && remixVersion !== 2 ;
224
+
225
+ // This exists to capture the server-side rendering errors on Remix v1
226
+ // On Remix v2, we capture SSR errors at `handleError`
227
+ // We also skip primitives here, as we can't dedupe them, and also we don't expect any primitive SSR errors.
228
+ if ( isRemixV1 && ! isPrimitive ( err ) ) {
229
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
230
+ captureRemixServerException ( err , 'documentRequest' , request ) ;
231
+ }
232
+ } ,
233
+ ) ;
234
+ } ,
235
+ ) ;
229
236
} ;
230
237
} ;
231
238
}
@@ -242,47 +249,32 @@ function makeWrappedDataFunction(
242
249
return origFn . call ( this , args ) ;
243
250
}
244
251
245
- let res : Response | AppData ;
246
- // eslint-disable-next-line deprecation/deprecation
247
- const activeTransaction = getActiveTransaction ( ) ;
248
- const currentScope = getCurrentScope ( ) ;
249
-
250
- try {
251
- // eslint-disable-next-line deprecation/deprecation
252
- const span = activeTransaction ?. startChild ( {
252
+ return startSpan (
253
+ {
253
254
op : `function.remix.${ name } ` ,
254
255
origin : 'auto.ui.remix' ,
255
256
name : id ,
256
257
attributes : {
257
258
name,
258
259
} ,
259
- } ) ;
260
-
261
- if ( span ) {
262
- // Assign data function to hub to be able to see `db` transactions (if any) as children.
263
- // eslint-disable-next-line deprecation/deprecation
264
- currentScope . setSpan ( span ) ;
265
- }
266
-
267
- res = await origFn . call ( this , args ) ;
268
-
269
- // eslint-disable-next-line deprecation/deprecation
270
- currentScope . setSpan ( activeTransaction ) ;
271
- span ?. end ( ) ;
272
- } catch ( err ) {
273
- const isRemixV2 = FUTURE_FLAGS ?. v2_errorBoundary || remixVersion === 2 ;
274
-
275
- // On Remix v2, we capture all unexpected errors (except the `Route Error Response`s / Thrown Responses) in `handleError` function.
276
- // This is both for consistency and also avoid duplicates such as primitives like `string` or `number` being captured twice.
277
- // Remix v1 does not have a `handleError` function, so we capture all errors here.
278
- if ( isRemixV2 ? isResponse ( err ) : true ) {
279
- await captureRemixServerException ( err , name , args . request ) ;
280
- }
281
-
282
- throw err ;
283
- }
284
-
285
- return res ;
260
+ } ,
261
+ ( ) => {
262
+ return handleCallbackErrors (
263
+ ( ) => origFn . call ( this , args ) ,
264
+ err => {
265
+ const isRemixV2 = FUTURE_FLAGS ?. v2_errorBoundary || remixVersion === 2 ;
266
+
267
+ // On Remix v2, we capture all unexpected errors (except the `Route Error Response`s / Thrown Responses) in `handleError` function.
268
+ // This is both for consistency and also avoid duplicates such as primitives like `string` or `number` being captured twice.
269
+ // Remix v1 does not have a `handleError` function, so we capture all errors here.
270
+ if ( isRemixV2 ? isResponse ( err ) : true ) {
271
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
272
+ captureRemixServerException ( err , name , args . request ) ;
273
+ }
274
+ } ,
275
+ ) ;
276
+ } ,
277
+ ) ;
286
278
} ;
287
279
}
288
280
@@ -382,47 +374,44 @@ export function createRoutes(manifest: ServerRouteManifest, parentId?: string):
382
374
}
383
375
384
376
/**
385
- * Starts a new transaction for the given request to be used by different `RequestHandler` wrappers.
377
+ * Starts a new active span for the given request to be used by different `RequestHandler` wrappers.
386
378
*/
387
- export function startRequestHandlerTransaction (
388
- hub : Hub ,
389
- name : string ,
390
- source : TransactionSource ,
391
- request : {
392
- headers : {
393
- 'sentry-trace' : string ;
394
- baggage : string ;
395
- } ;
379
+ export function startRequestHandlerSpan < T > (
380
+ {
381
+ name,
382
+ source,
383
+ sentryTrace,
384
+ baggage,
385
+ method,
386
+ } : {
387
+ name : string ;
388
+ source : TransactionSource ;
389
+ sentryTrace : string ;
390
+ baggage : string ;
396
391
method : string ;
397
392
} ,
398
- ) : Transaction {
399
- // eslint-disable-next-line deprecation/deprecation
400
- const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders (
401
- request . headers [ 'sentry-trace' ] ,
402
- request . headers . baggage ,
403
- ) ;
404
- // eslint-disable-next-line deprecation/deprecation
405
- hub . getScope ( ) . setPropagationContext ( propagationContext ) ;
406
-
407
- // TODO: Refactor this to `startSpan()`
408
- // eslint-disable-next-line deprecation/deprecation
409
- const transaction = hub . startTransaction ( {
410
- name,
411
- op : 'http.server' ,
412
- attributes : {
413
- [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.http.remix' ,
414
- [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] : source ,
415
- method : request . method ,
393
+ callback : ( span : Span ) => T ,
394
+ ) : T {
395
+ return continueTrace (
396
+ {
397
+ sentryTrace,
398
+ baggage,
416
399
} ,
417
- ...traceparentData ,
418
- metadata : {
419
- dynamicSamplingContext : traceparentData && ! dynamicSamplingContext ? { } : dynamicSamplingContext ,
400
+ ( ) => {
401
+ return startSpan (
402
+ {
403
+ name,
404
+ attributes : {
405
+ [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.http.remix' ,
406
+ [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] : source ,
407
+ [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'http.server' ,
408
+ method,
409
+ } ,
410
+ } ,
411
+ callback ,
412
+ ) ;
420
413
} ,
421
- } ) ;
422
-
423
- // eslint-disable-next-line deprecation/deprecation
424
- hub . getScope ( ) . setSpan ( transaction ) ;
425
- return transaction ;
414
+ ) ;
426
415
}
427
416
428
417
/**
@@ -445,8 +434,6 @@ function wrapRequestHandler(origRequestHandler: RequestHandler, build: ServerBui
445
434
}
446
435
447
436
return withIsolationScope ( async isolationScope => {
448
- // eslint-disable-next-line deprecation/deprecation
449
- const hub = getCurrentHub ( ) ;
450
437
const options = getClient ( ) ?. getOptions ( ) ;
451
438
452
439
let normalizedRequest : Record < string , unknown > = request ;
@@ -473,23 +460,24 @@ function wrapRequestHandler(origRequestHandler: RequestHandler, build: ServerBui
473
460
return origRequestHandler . call ( this , request , loadContext ) ;
474
461
}
475
462
476
- const transaction = startRequestHandlerTransaction ( hub , name , source , {
477
- headers : {
478
- 'sentry-trace' : request . headers . get ( 'sentry-trace' ) || '' ,
463
+ return startRequestHandlerSpan (
464
+ {
465
+ name,
466
+ source,
467
+ sentryTrace : request . headers . get ( 'sentry-trace' ) || '' ,
479
468
baggage : request . headers . get ( 'baggage' ) || '' ,
469
+ method : request . method ,
480
470
} ,
481
- method : request . method ,
482
- } ) ;
483
-
484
- const res = ( await origRequestHandler . call ( this , request , loadContext ) ) as Response ;
471
+ async span => {
472
+ const res = ( await origRequestHandler . call ( this , request , loadContext ) ) as Response ;
485
473
486
- if ( isResponse ( res ) ) {
487
- setHttpStatus ( transaction , res . status ) ;
488
- }
489
-
490
- transaction . end ( ) ;
474
+ if ( isResponse ( res ) ) {
475
+ setHttpStatus ( span , res . status ) ;
476
+ }
491
477
492
- return res ;
478
+ return res ;
479
+ } ,
480
+ ) ;
493
481
} ) ;
494
482
} ;
495
483
}
0 commit comments