@@ -23,6 +23,7 @@ import {
23
23
import type { Client , HandlerDataXhr , SentryWrappedXMLHttpRequest , Span } from '@sentry/types' ;
24
24
import {
25
25
BAGGAGE_HEADER_NAME ,
26
+ addFetchEndInstrumentationHandler ,
26
27
addFetchInstrumentationHandler ,
27
28
browserPerformanceTimeOrigin ,
28
29
dynamicSamplingContextToSentryBaggageHeader ,
@@ -93,14 +94,17 @@ export interface RequestInstrumentationOptions {
93
94
shouldCreateSpanForRequest ?( this : void , url : string ) : boolean ;
94
95
}
95
96
97
+ const responseToSpanId = new WeakMap < object , string > ( ) ;
98
+ const spanIdToEndTimestamp = new Map < string , number > ( ) ;
99
+
96
100
export const defaultRequestInstrumentationOptions : RequestInstrumentationOptions = {
97
101
traceFetch : true ,
98
102
traceXHR : true ,
99
103
enableHTTPTimings : true ,
100
104
} ;
101
105
102
106
/** Registers span creators for xhr and fetch requests */
103
- export function instrumentOutgoingRequests ( _options ?: Partial < RequestInstrumentationOptions > ) : void {
107
+ export function instrumentOutgoingRequests ( client : Client , _options ?: Partial < RequestInstrumentationOptions > ) : void {
104
108
const { traceFetch, traceXHR, shouldCreateSpanForRequest, enableHTTPTimings, tracePropagationTargets } = {
105
109
traceFetch : defaultRequestInstrumentationOptions . traceFetch ,
106
110
traceXHR : defaultRequestInstrumentationOptions . traceXHR ,
@@ -115,8 +119,39 @@ export function instrumentOutgoingRequests(_options?: Partial<RequestInstrumenta
115
119
const spans : Record < string , Span > = { } ;
116
120
117
121
if ( traceFetch ) {
122
+ // Keeping track of http requests, whose body payloads resolved later than the intial resolved request
123
+ // e.g. streaming using server sent events (SSE)
124
+ client . addEventProcessor ( event => {
125
+ if ( event . type === 'transaction' && event . spans ) {
126
+ event . spans . forEach ( span => {
127
+ if ( span . op === 'http.client' ) {
128
+ const updatedTimestamp = spanIdToEndTimestamp . get ( span . span_id ) ;
129
+ if ( updatedTimestamp ) {
130
+ span . timestamp = updatedTimestamp / 1000 ;
131
+ spanIdToEndTimestamp . delete ( span . span_id ) ;
132
+ }
133
+ }
134
+ } ) ;
135
+ }
136
+ return event ;
137
+ } ) ;
138
+
139
+ addFetchEndInstrumentationHandler ( handlerData => {
140
+ if ( handlerData . response ) {
141
+ const span = responseToSpanId . get ( handlerData . response ) ;
142
+ if ( span && handlerData . endTimestamp ) {
143
+ spanIdToEndTimestamp . set ( span , handlerData . endTimestamp ) ;
144
+ }
145
+ }
146
+ } ) ;
147
+
118
148
addFetchInstrumentationHandler ( handlerData => {
119
149
const createdSpan = instrumentFetchRequest ( handlerData , shouldCreateSpan , shouldAttachHeadersWithTargets , spans ) ;
150
+
151
+ if ( handlerData . response && handlerData . fetchData . __span ) {
152
+ responseToSpanId . set ( handlerData . response , handlerData . fetchData . __span ) ;
153
+ }
154
+
120
155
// We cannot use `window.location` in the generic fetch instrumentation,
121
156
// but we need it for reliable `server.address` attribute.
122
157
// so we extend this in here
0 commit comments