11/* eslint-disable max-lines */
22import { getCurrentHub , getDynamicSamplingContextFromClient , hasTracingEnabled } from '@sentry/core' ;
3- import type { Client , Scope , Span } from '@sentry/types' ;
3+ import type { HandlerDataFetch , Span } from '@sentry/types' ;
44import {
55 addInstrumentationHandler ,
66 BAGGAGE_HEADER_NAME ,
77 browserPerformanceTimeOrigin ,
88 dynamicSamplingContextToSentryBaggageHeader ,
99 generateSentryTraceHeader ,
10- isInstanceOf ,
1110 SENTRY_XHR_DATA_KEY ,
1211 stringMatchesSomePattern ,
1312} from '@sentry/utils' ;
1413
14+ import { instrumentFetchRequest } from '../common/fetch' ;
1515import { addPerformanceInstrumentationHandler } from './instrument' ;
1616
1717export const DEFAULT_TRACE_PROPAGATION_TARGETS = [ 'localhost' , / ^ \/ (? ! \/ ) / ] ;
@@ -66,26 +66,6 @@ export interface RequestInstrumentationOptions {
6666 shouldCreateSpanForRequest ?( this : void , url : string ) : boolean ;
6767}
6868
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-
8969/** Data returned from XHR request */
9070export interface XHRData {
9171 xhr ?: {
@@ -105,17 +85,6 @@ export interface XHRData {
10585 endTimestamp ?: number ;
10686}
10787
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-
11988export const defaultRequestInstrumentationOptions : RequestInstrumentationOptions = {
12089 traceFetch : true ,
12190 traceXHR : true ,
@@ -154,8 +123,8 @@ export function instrumentOutgoingRequests(_options?: Partial<RequestInstrumenta
154123 const spans : Record < string , Span > = { } ;
155124
156125 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 ) ;
159128 if ( enableHTTPTimings && createdSpan ) {
160129 addHTTPTimings ( createdSpan ) ;
161130 }
@@ -276,175 +245,6 @@ export function shouldAttachHeaders(url: string, tracePropagationTargets: (strin
276245 return stringMatchesSomePattern ( url , tracePropagationTargets || DEFAULT_TRACE_PROPAGATION_TARGETS ) ;
277246}
278247
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-
448248/**
449249 * Create and track xhr request spans
450250 *
0 commit comments