1
1
/* eslint-disable max-lines */
2
2
import { getCurrentHub , getDynamicSamplingContextFromClient , hasTracingEnabled } from '@sentry/core' ;
3
- import type { Client , Scope , Span } from '@sentry/types' ;
3
+ import type { HandlerDataFetch , Span } from '@sentry/types' ;
4
4
import {
5
5
addInstrumentationHandler ,
6
6
BAGGAGE_HEADER_NAME ,
7
7
browserPerformanceTimeOrigin ,
8
8
dynamicSamplingContextToSentryBaggageHeader ,
9
9
generateSentryTraceHeader ,
10
- isInstanceOf ,
11
10
SENTRY_XHR_DATA_KEY ,
12
11
stringMatchesSomePattern ,
13
12
} from '@sentry/utils' ;
14
13
14
+ import { instrumentFetchRequest } from '../common/fetch' ;
15
15
import { addPerformanceInstrumentationHandler } from './instrument' ;
16
16
17
17
export const DEFAULT_TRACE_PROPAGATION_TARGETS = [ 'localhost' , / ^ \/ (? ! \/ ) / ] ;
@@ -66,26 +66,6 @@ export interface RequestInstrumentationOptions {
66
66
shouldCreateSpanForRequest ?( this : void , url : string ) : boolean ;
67
67
}
68
68
69
- /** Data returned from fetch callback */
70
- export interface FetchData {
71
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
72
- args : any [ ] ; // the arguments passed to the fetch call itself
73
- fetchData ?: {
74
- method : string ;
75
- url : string ;
76
- // span_id
77
- __span ?: string ;
78
- } ;
79
-
80
- // TODO Should this be unknown instead? If we vendor types, make it a Response
81
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
82
- response ?: any ;
83
- error ?: unknown ;
84
-
85
- startTimestamp : number ;
86
- endTimestamp ?: number ;
87
- }
88
-
89
69
/** Data returned from XHR request */
90
70
export interface XHRData {
91
71
xhr ?: {
@@ -105,17 +85,6 @@ export interface XHRData {
105
85
endTimestamp ?: number ;
106
86
}
107
87
108
- type PolymorphicRequestHeaders =
109
- | Record < string , string | undefined >
110
- | Array < [ string , string ] >
111
- // the below is not preicsely the Header type used in Request, but it'll pass duck-typing
112
- | {
113
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
114
- [ key : string ] : any ;
115
- append : ( key : string , value : string ) => void ;
116
- get : ( key : string ) => string | null | undefined ;
117
- } ;
118
-
119
88
export const defaultRequestInstrumentationOptions : RequestInstrumentationOptions = {
120
89
traceFetch : true ,
121
90
traceXHR : true ,
@@ -154,8 +123,8 @@ export function instrumentOutgoingRequests(_options?: Partial<RequestInstrumenta
154
123
const spans : Record < string , Span > = { } ;
155
124
156
125
if ( traceFetch ) {
157
- addInstrumentationHandler ( 'fetch' , ( handlerData : FetchData ) => {
158
- const createdSpan = fetchCallback ( handlerData , shouldCreateSpan , shouldAttachHeadersWithTargets , spans ) ;
126
+ addInstrumentationHandler ( 'fetch' , ( handlerData : HandlerDataFetch ) => {
127
+ const createdSpan = instrumentFetchRequest ( handlerData , shouldCreateSpan , shouldAttachHeadersWithTargets , spans ) ;
159
128
if ( enableHTTPTimings && createdSpan ) {
160
129
addHTTPTimings ( createdSpan ) ;
161
130
}
@@ -276,175 +245,6 @@ export function shouldAttachHeaders(url: string, tracePropagationTargets: (strin
276
245
return stringMatchesSomePattern ( url , tracePropagationTargets || DEFAULT_TRACE_PROPAGATION_TARGETS ) ;
277
246
}
278
247
279
- /**
280
- * Create and track fetch request spans
281
- *
282
- * @returns Span if a span was created, otherwise void.
283
- */
284
- export function fetchCallback (
285
- handlerData : FetchData ,
286
- shouldCreateSpan : ( url : string ) => boolean ,
287
- shouldAttachHeaders : ( url : string ) => boolean ,
288
- spans : Record < string , Span > ,
289
- ) : Span | undefined {
290
- if ( ! hasTracingEnabled ( ) || ! handlerData . fetchData ) {
291
- return undefined ;
292
- }
293
-
294
- const shouldCreateSpanResult = shouldCreateSpan ( handlerData . fetchData . url ) ;
295
-
296
- if ( handlerData . endTimestamp && shouldCreateSpanResult ) {
297
- const spanId = handlerData . fetchData . __span ;
298
- if ( ! spanId ) return ;
299
-
300
- const span = spans [ spanId ] ;
301
- if ( span ) {
302
- if ( handlerData . response ) {
303
- // TODO (kmclb) remove this once types PR goes through
304
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
305
- span . setHttpStatus ( handlerData . response . status ) ;
306
-
307
- const contentLength : string =
308
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
309
- handlerData . response && handlerData . response . headers && handlerData . response . headers . get ( 'content-length' ) ;
310
-
311
- const contentLengthNum = parseInt ( contentLength ) ;
312
- if ( contentLengthNum > 0 ) {
313
- span . setData ( 'http.response_content_length' , contentLengthNum ) ;
314
- }
315
- } else if ( handlerData . error ) {
316
- span . setStatus ( 'internal_error' ) ;
317
- }
318
- span . finish ( ) ;
319
-
320
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
321
- delete spans [ spanId ] ;
322
- }
323
- return undefined ;
324
- }
325
-
326
- const hub = getCurrentHub ( ) ;
327
- const scope = hub . getScope ( ) ;
328
- const client = hub . getClient ( ) ;
329
- const parentSpan = scope . getSpan ( ) ;
330
-
331
- const { method, url } = handlerData . fetchData ;
332
-
333
- const span =
334
- shouldCreateSpanResult && parentSpan
335
- ? parentSpan . startChild ( {
336
- data : {
337
- url,
338
- type : 'fetch' ,
339
- 'http.method' : method ,
340
- } ,
341
- description : `${ method } ${ url } ` ,
342
- op : 'http.client' ,
343
- origin : 'auto.http.browser' ,
344
- } )
345
- : undefined ;
346
-
347
- if ( span ) {
348
- handlerData . fetchData . __span = span . spanId ;
349
- spans [ span . spanId ] = span ;
350
- }
351
-
352
- if ( shouldAttachHeaders ( handlerData . fetchData . url ) && client ) {
353
- const request : string | Request = handlerData . args [ 0 ] ;
354
-
355
- // In case the user hasn't set the second argument of a fetch call we default it to `{}`.
356
- handlerData . args [ 1 ] = handlerData . args [ 1 ] || { } ;
357
-
358
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
359
- const options : { [ key : string ] : any } = handlerData . args [ 1 ] ;
360
-
361
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
362
- options . headers = addTracingHeadersToFetchRequest ( request , client , scope , options , span ) ;
363
- }
364
-
365
- return span ;
366
- }
367
-
368
- /**
369
- * Adds sentry-trace and baggage headers to the various forms of fetch headers
370
- */
371
- export function addTracingHeadersToFetchRequest (
372
- request : string | unknown , // unknown is actually type Request but we can't export DOM types from this package,
373
- client : Client ,
374
- scope : Scope ,
375
- options : {
376
- headers ?:
377
- | {
378
- [ key : string ] : string [ ] | string | undefined ;
379
- }
380
- | PolymorphicRequestHeaders ;
381
- } ,
382
- requestSpan ?: Span ,
383
- ) : PolymorphicRequestHeaders | undefined {
384
- const span = requestSpan || scope . getSpan ( ) ;
385
-
386
- const transaction = span && span . transaction ;
387
-
388
- const { traceId, sampled, dsc } = scope . getPropagationContext ( ) ;
389
-
390
- const sentryTraceHeader = span ? span . toTraceparent ( ) : generateSentryTraceHeader ( traceId , undefined , sampled ) ;
391
- const dynamicSamplingContext = transaction
392
- ? transaction . getDynamicSamplingContext ( )
393
- : dsc
394
- ? dsc
395
- : getDynamicSamplingContextFromClient ( traceId , client , scope ) ;
396
-
397
- const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader ( dynamicSamplingContext ) ;
398
-
399
- const headers =
400
- typeof Request !== 'undefined' && isInstanceOf ( request , Request ) ? ( request as Request ) . headers : options . headers ;
401
-
402
- if ( ! headers ) {
403
- return { 'sentry-trace' : sentryTraceHeader , baggage : sentryBaggageHeader } ;
404
- } else if ( typeof Headers !== 'undefined' && isInstanceOf ( headers , Headers ) ) {
405
- const newHeaders = new Headers ( headers as Headers ) ;
406
-
407
- newHeaders . append ( 'sentry-trace' , sentryTraceHeader ) ;
408
-
409
- if ( sentryBaggageHeader ) {
410
- // If the same header is appended multiple times the browser will merge the values into a single request header.
411
- // Its therefore safe to simply push a "baggage" entry, even though there might already be another baggage header.
412
- newHeaders . append ( BAGGAGE_HEADER_NAME , sentryBaggageHeader ) ;
413
- }
414
-
415
- return newHeaders as PolymorphicRequestHeaders ;
416
- } else if ( Array . isArray ( headers ) ) {
417
- const newHeaders = [ ...headers , [ 'sentry-trace' , sentryTraceHeader ] ] ;
418
-
419
- if ( sentryBaggageHeader ) {
420
- // If there are multiple entries with the same key, the browser will merge the values into a single request header.
421
- // Its therefore safe to simply push a "baggage" entry, even though there might already be another baggage header.
422
- newHeaders . push ( [ BAGGAGE_HEADER_NAME , sentryBaggageHeader ] ) ;
423
- }
424
-
425
- return newHeaders as PolymorphicRequestHeaders ;
426
- } else {
427
- const existingBaggageHeader = 'baggage' in headers ? headers . baggage : undefined ;
428
- const newBaggageHeaders : string [ ] = [ ] ;
429
-
430
- if ( Array . isArray ( existingBaggageHeader ) ) {
431
- newBaggageHeaders . push ( ...existingBaggageHeader ) ;
432
- } else if ( existingBaggageHeader ) {
433
- newBaggageHeaders . push ( existingBaggageHeader ) ;
434
- }
435
-
436
- if ( sentryBaggageHeader ) {
437
- newBaggageHeaders . push ( sentryBaggageHeader ) ;
438
- }
439
-
440
- return {
441
- ...( headers as Exclude < typeof headers , Headers > ) ,
442
- 'sentry-trace' : sentryTraceHeader ,
443
- baggage : newBaggageHeaders . length > 0 ? newBaggageHeaders . join ( ',' ) : undefined ,
444
- } ;
445
- }
446
- }
447
-
448
248
/**
449
249
* Create and track xhr request spans
450
250
*
0 commit comments