1
- import type { Hub } from '@sentry/core' ;
2
- import type { EventProcessor , Integration } from '@sentry/types' ;
1
+ import { getCurrentHub , getDynamicSamplingContextFromClient } from '@sentry/core' ;
2
+ import type { EventProcessor , Integration , Span } from '@sentry/types' ;
3
3
import {
4
4
dynamicRequire ,
5
5
dynamicSamplingContextToSentryBaggageHeader ,
6
+ generateSentryTraceHeader ,
6
7
getSanitizedUrlString ,
7
8
parseUrl ,
8
9
stringMatchesSomePattern ,
@@ -12,7 +13,13 @@ import { LRUMap } from 'lru_map';
12
13
import type { NodeClient } from '../../client' ;
13
14
import { NODE_VERSION } from '../../nodeVersion' ;
14
15
import { isSentryRequest } from '../utils/http' ;
15
- import type { DiagnosticsChannel , RequestCreateMessage , RequestEndMessage , RequestErrorMessage } from './types' ;
16
+ import type {
17
+ DiagnosticsChannel ,
18
+ RequestCreateMessage ,
19
+ RequestEndMessage ,
20
+ RequestErrorMessage ,
21
+ RequestWithSentry ,
22
+ } from './types' ;
16
23
17
24
export enum ChannelName {
18
25
// https://github.com/nodejs/undici/blob/e6fc80f809d1217814c044f52ed40ef13f21e43c/docs/api/DiagnosticsChannel.md#undicirequestcreate
@@ -81,7 +88,7 @@ export class Undici implements Integration {
81
88
/**
82
89
* @inheritDoc
83
90
*/
84
- public setupOnce ( _addGlobalEventProcessor : ( callback : EventProcessor ) => void , getCurrentHub : ( ) => Hub ) : void {
91
+ public setupOnce ( _addGlobalEventProcessor : ( callback : EventProcessor ) => void ) : void {
85
92
// Requires Node 16+ to use the diagnostics_channel API.
86
93
if ( NODE_VERSION . major && NODE_VERSION . major < 16 ) {
87
94
return ;
@@ -99,169 +106,205 @@ export class Undici implements Integration {
99
106
return ;
100
107
}
101
108
102
- const shouldCreateSpan = ( url : string ) : boolean => {
103
- if ( this . _options . shouldCreateSpanForRequest === undefined ) {
109
+ // https://github.com/nodejs/undici/blob/e6fc80f809d1217814c044f52ed40ef13f21e43c/docs/api/DiagnosticsChannel.md
110
+ ds . subscribe ( ChannelName . RequestCreate , this . _onRequestCreate ) ;
111
+ ds . subscribe ( ChannelName . RequestEnd , this . _onRequestEnd ) ;
112
+ ds . subscribe ( ChannelName . RequestError , this . _onRequestError ) ;
113
+ }
114
+
115
+ /** Helper that wraps shouldCreateSpanForRequest option */
116
+ private _shouldCreateSpan ( url : string ) : boolean {
117
+ if ( this . _options . shouldCreateSpanForRequest === undefined ) {
118
+ return true ;
119
+ }
120
+
121
+ const cachedDecision = this . _createSpanUrlMap . get ( url ) ;
122
+ if ( cachedDecision !== undefined ) {
123
+ return cachedDecision ;
124
+ }
125
+
126
+ const decision = this . _options . shouldCreateSpanForRequest ( url ) ;
127
+ this . _createSpanUrlMap . set ( url , decision ) ;
128
+ return decision ;
129
+ }
130
+
131
+ private _onRequestCreate = ( message : unknown ) : void => {
132
+ const hub = getCurrentHub ( ) ;
133
+ if ( ! hub . getIntegration ( Undici ) ) {
134
+ return ;
135
+ }
136
+
137
+ const { request } = message as RequestCreateMessage ;
138
+
139
+ const stringUrl = request . origin ? request . origin . toString ( ) + request . path : request . path ;
140
+
141
+ if ( isSentryRequest ( stringUrl ) || request . __sentry_span__ !== undefined ) {
142
+ return ;
143
+ }
144
+
145
+ const client = hub . getClient < NodeClient > ( ) ;
146
+ if ( ! client ) {
147
+ return ;
148
+ }
149
+
150
+ const clientOptions = client . getOptions ( ) ;
151
+ const scope = hub . getScope ( ) ;
152
+
153
+ const parentSpan = scope . getSpan ( ) ;
154
+
155
+ const span = this . _shouldCreateSpan ( stringUrl ) ? createRequestSpan ( parentSpan , request , stringUrl ) : undefined ;
156
+ if ( span ) {
157
+ request . __sentry_span__ = span ;
158
+ }
159
+
160
+ const shouldAttachTraceData = ( url : string ) : boolean => {
161
+ if ( clientOptions . tracePropagationTargets === undefined ) {
104
162
return true ;
105
163
}
106
164
107
- const cachedDecision = this . _createSpanUrlMap . get ( url ) ;
165
+ const cachedDecision = this . _headersUrlMap . get ( url ) ;
108
166
if ( cachedDecision !== undefined ) {
109
167
return cachedDecision ;
110
168
}
111
169
112
- const decision = this . _options . shouldCreateSpanForRequest ( url ) ;
113
- this . _createSpanUrlMap . set ( url , decision ) ;
170
+ const decision = stringMatchesSomePattern ( url , clientOptions . tracePropagationTargets ) ;
171
+ this . _headersUrlMap . set ( url , decision ) ;
114
172
return decision ;
115
173
} ;
116
174
117
- // https://github.com/nodejs/undici/blob/e6fc80f809d1217814c044f52ed40ef13f21e43c/docs/api/DiagnosticsChannel.md
118
- ds . subscribe ( ChannelName . RequestCreate , message => {
119
- const hub = getCurrentHub ( ) ;
120
- if ( ! hub . getIntegration ( Undici ) ) {
121
- return ;
175
+ if ( shouldAttachTraceData ( stringUrl ) ) {
176
+ if ( span ) {
177
+ const dynamicSamplingContext = span ?. transaction ?. getDynamicSamplingContext ( ) ;
178
+ const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader ( dynamicSamplingContext ) ;
179
+
180
+ setHeadersOnRequest ( request , span . toTraceparent ( ) , sentryBaggageHeader ) ;
181
+ } else {
182
+ const { traceId, sampled, dsc } = scope . getPropagationContext ( ) ;
183
+ const sentryTrace = generateSentryTraceHeader ( traceId , undefined , sampled ) ;
184
+ const dynamicSamplingContext = dsc || getDynamicSamplingContextFromClient ( traceId , client , scope ) ;
185
+ const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader ( dynamicSamplingContext ) ;
186
+ setHeadersOnRequest ( request , sentryTrace , sentryBaggageHeader ) ;
122
187
}
188
+ }
189
+ } ;
123
190
124
- const { request } = message as RequestCreateMessage ;
191
+ private _onRequestEnd = ( message : unknown ) : void => {
192
+ const hub = getCurrentHub ( ) ;
193
+ if ( ! hub . getIntegration ( Undici ) ) {
194
+ return ;
195
+ }
125
196
126
- const stringUrl = request . origin ? request . origin . toString ( ) + request . path : request . path ;
127
- const url = parseUrl ( stringUrl ) ;
197
+ const { request, response } = message as RequestEndMessage ;
128
198
129
- if ( isSentryRequest ( stringUrl ) || request . __sentry__ !== undefined ) {
130
- return ;
131
- }
199
+ const stringUrl = request . origin ? request . origin . toString ( ) + request . path : request . path ;
132
200
133
- const client = hub . getClient < NodeClient > ( ) ;
134
- const scope = hub . getScope ( ) ;
135
-
136
- const activeSpan = scope . getSpan ( ) ;
137
-
138
- if ( activeSpan && client ) {
139
- const clientOptions = client . getOptions ( ) ;
140
-
141
- if ( shouldCreateSpan ( stringUrl ) ) {
142
- const method = request . method || 'GET' ;
143
- const data : Record < string , unknown > = {
144
- 'http.method' : method ,
145
- } ;
146
- if ( url . search ) {
147
- data [ 'http.query' ] = url . search ;
148
- }
149
- if ( url . hash ) {
150
- data [ 'http.fragment' ] = url . hash ;
151
- }
152
- const span = activeSpan . startChild ( {
153
- op : 'http.client' ,
154
- description : `${ method } ${ getSanitizedUrlString ( url ) } ` ,
155
- data,
156
- } ) ;
157
- request . __sentry__ = span ;
158
-
159
- const shouldAttachTraceData = ( url : string ) : boolean => {
160
- if ( clientOptions . tracePropagationTargets === undefined ) {
161
- return true ;
162
- }
163
-
164
- const cachedDecision = this . _headersUrlMap . get ( url ) ;
165
- if ( cachedDecision !== undefined ) {
166
- return cachedDecision ;
167
- }
168
-
169
- const decision = stringMatchesSomePattern ( url , clientOptions . tracePropagationTargets ) ;
170
- this . _headersUrlMap . set ( url , decision ) ;
171
- return decision ;
172
- } ;
173
-
174
- if ( shouldAttachTraceData ( stringUrl ) ) {
175
- request . addHeader ( 'sentry-trace' , span . toTraceparent ( ) ) ;
176
- if ( span . transaction ) {
177
- const dynamicSamplingContext = span . transaction . getDynamicSamplingContext ( ) ;
178
- const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader ( dynamicSamplingContext ) ;
179
- if ( sentryBaggageHeader ) {
180
- request . addHeader ( 'baggage' , sentryBaggageHeader ) ;
181
- }
182
- }
183
- }
184
- }
185
- }
186
- } ) ;
201
+ if ( isSentryRequest ( stringUrl ) ) {
202
+ return ;
203
+ }
187
204
188
- ds . subscribe ( ChannelName . RequestEnd , message => {
189
- const hub = getCurrentHub ( ) ;
190
- if ( ! hub . getIntegration ( Undici ) ) {
191
- return ;
192
- }
205
+ const span = request . __sentry_span__ ;
206
+ if ( span ) {
207
+ span . setHttpStatus ( response . statusCode ) ;
208
+ span . finish ( ) ;
209
+ }
193
210
194
- const { request, response } = message as RequestEndMessage ;
211
+ if ( this . _options . breadcrumbs ) {
212
+ hub . addBreadcrumb (
213
+ {
214
+ category : 'http' ,
215
+ data : {
216
+ method : request . method ,
217
+ status_code : response . statusCode ,
218
+ url : stringUrl ,
219
+ } ,
220
+ type : 'http' ,
221
+ } ,
222
+ {
223
+ event : 'response' ,
224
+ request,
225
+ response,
226
+ } ,
227
+ ) ;
228
+ }
229
+ } ;
195
230
196
- const stringUrl = request . origin ? request . origin . toString ( ) + request . path : request . path ;
231
+ private _onRequestError = ( message : unknown ) : void => {
232
+ const hub = getCurrentHub ( ) ;
233
+ if ( ! hub . getIntegration ( Undici ) ) {
234
+ return ;
235
+ }
197
236
198
- if ( isSentryRequest ( stringUrl ) ) {
199
- return ;
200
- }
237
+ const { request } = message as RequestErrorMessage ;
201
238
202
- const span = request . __sentry__ ;
203
- if ( span ) {
204
- span . setHttpStatus ( response . statusCode ) ;
205
- span . finish ( ) ;
206
- }
239
+ const stringUrl = request . origin ? request . origin . toString ( ) + request . path : request . path ;
207
240
208
- if ( this . _options . breadcrumbs ) {
209
- hub . addBreadcrumb (
210
- {
211
- category : 'http' ,
212
- data : {
213
- method : request . method ,
214
- status_code : response . statusCode ,
215
- url : stringUrl ,
216
- } ,
217
- type : 'http' ,
218
- } ,
219
- {
220
- event : 'response' ,
221
- request,
222
- response,
223
- } ,
224
- ) ;
225
- }
226
- } ) ;
241
+ if ( isSentryRequest ( stringUrl ) ) {
242
+ return ;
243
+ }
227
244
228
- ds . subscribe ( ChannelName . RequestError , message => {
229
- const hub = getCurrentHub ( ) ;
230
- if ( ! hub . getIntegration ( Undici ) ) {
231
- return ;
232
- }
245
+ const span = request . __sentry_span__ ;
246
+ if ( span ) {
247
+ span . setStatus ( 'internal_error' ) ;
248
+ span . finish ( ) ;
249
+ }
233
250
234
- const { request } = message as RequestErrorMessage ;
251
+ if ( this . _options . breadcrumbs ) {
252
+ hub . addBreadcrumb (
253
+ {
254
+ category : 'http' ,
255
+ data : {
256
+ method : request . method ,
257
+ url : stringUrl ,
258
+ } ,
259
+ level : 'error' ,
260
+ type : 'http' ,
261
+ } ,
262
+ {
263
+ event : 'error' ,
264
+ request,
265
+ } ,
266
+ ) ;
267
+ }
268
+ } ;
269
+ }
235
270
236
- const stringUrl = request . origin ? request . origin . toString ( ) + request . path : request . path ;
271
+ function setHeadersOnRequest (
272
+ request : RequestWithSentry ,
273
+ sentryTrace : string ,
274
+ sentryBaggageHeader : string | undefined ,
275
+ ) : void {
276
+ if ( request . __sentry_has_headers__ ) {
277
+ return ;
278
+ }
237
279
238
- if ( isSentryRequest ( stringUrl ) ) {
239
- return ;
240
- }
280
+ request . addHeader ( 'sentry-trace' , sentryTrace ) ;
281
+ if ( sentryBaggageHeader ) {
282
+ request . addHeader ( 'baggage' , sentryBaggageHeader ) ;
283
+ }
241
284
242
- const span = request . __sentry__ ;
243
- if ( span ) {
244
- span . setStatus ( 'internal_error' ) ;
245
- span . finish ( ) ;
246
- }
285
+ request . __sentry_has_headers__ = true ;
286
+ }
247
287
248
- if ( this . _options . breadcrumbs ) {
249
- hub . addBreadcrumb (
250
- {
251
- category : 'http' ,
252
- data : {
253
- method : request . method ,
254
- url : stringUrl ,
255
- } ,
256
- level : 'error' ,
257
- type : 'http' ,
258
- } ,
259
- {
260
- event : 'error' ,
261
- request,
262
- } ,
263
- ) ;
264
- }
265
- } ) ;
288
+ function createRequestSpan (
289
+ activeSpan : Span | undefined ,
290
+ request : RequestWithSentry ,
291
+ stringUrl : string ,
292
+ ) : Span | undefined {
293
+ const url = parseUrl ( stringUrl ) ;
294
+
295
+ const method = request . method || 'GET' ;
296
+ const data : Record < string , unknown > = {
297
+ 'http.method' : method ,
298
+ } ;
299
+ if ( url . search ) {
300
+ data [ 'http.query' ] = url . search ;
301
+ }
302
+ if ( url . hash ) {
303
+ data [ 'http.fragment' ] = url . hash ;
266
304
}
305
+ return activeSpan ?. startChild ( {
306
+ op : 'http.client' ,
307
+ description : `${ method } ${ getSanitizedUrlString ( url ) } ` ,
308
+ data,
309
+ } ) ;
267
310
}
0 commit comments