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' ;
33import {
44 dynamicRequire ,
55 dynamicSamplingContextToSentryBaggageHeader ,
6+ generateSentryTraceHeader ,
67 getSanitizedUrlString ,
78 parseUrl ,
89 stringMatchesSomePattern ,
@@ -12,7 +13,13 @@ import { LRUMap } from 'lru_map';
1213import type { NodeClient } from '../../client' ;
1314import { NODE_VERSION } from '../../nodeVersion' ;
1415import { 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' ;
1623
1724export enum ChannelName {
1825 // https://github.com/nodejs/undici/blob/e6fc80f809d1217814c044f52ed40ef13f21e43c/docs/api/DiagnosticsChannel.md#undicirequestcreate
@@ -81,7 +88,7 @@ export class Undici implements Integration {
8188 /**
8289 * @inheritDoc
8390 */
84- public setupOnce ( _addGlobalEventProcessor : ( callback : EventProcessor ) => void , getCurrentHub : ( ) => Hub ) : void {
91+ public setupOnce ( _addGlobalEventProcessor : ( callback : EventProcessor ) => void ) : void {
8592 // Requires Node 16+ to use the diagnostics_channel API.
8693 if ( NODE_VERSION . major && NODE_VERSION . major < 16 ) {
8794 return ;
@@ -99,169 +106,205 @@ export class Undici implements Integration {
99106 return ;
100107 }
101108
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 ) {
104162 return true ;
105163 }
106164
107- const cachedDecision = this . _createSpanUrlMap . get ( url ) ;
165+ const cachedDecision = this . _headersUrlMap . get ( url ) ;
108166 if ( cachedDecision !== undefined ) {
109167 return cachedDecision ;
110168 }
111169
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 ) ;
114172 return decision ;
115173 } ;
116174
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 ) ;
122187 }
188+ }
189+ } ;
123190
124- const { request } = message as RequestCreateMessage ;
191+ private _onRequestEnd = ( message : unknown ) : void => {
192+ const hub = getCurrentHub ( ) ;
193+ if ( ! hub . getIntegration ( Undici ) ) {
194+ return ;
195+ }
125196
126- const stringUrl = request . origin ? request . origin . toString ( ) + request . path : request . path ;
127- const url = parseUrl ( stringUrl ) ;
197+ const { request, response } = message as RequestEndMessage ;
128198
129- if ( isSentryRequest ( stringUrl ) || request . __sentry__ !== undefined ) {
130- return ;
131- }
199+ const stringUrl = request . origin ? request . origin . toString ( ) + request . path : request . path ;
132200
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+ }
187204
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+ }
193210
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+ } ;
195230
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+ }
197236
198- if ( isSentryRequest ( stringUrl ) ) {
199- return ;
200- }
237+ const { request } = message as RequestErrorMessage ;
201238
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 ;
207240
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+ }
227244
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+ }
233250
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+ }
235270
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+ }
237279
238- if ( isSentryRequest ( stringUrl ) ) {
239- return ;
240- }
280+ request . addHeader ( 'sentry-trace' , sentryTrace ) ;
281+ if ( sentryBaggageHeader ) {
282+ request . addHeader ( 'baggage' , sentryBaggageHeader ) ;
283+ }
241284
242- const span = request . __sentry__ ;
243- if ( span ) {
244- span . setStatus ( 'internal_error' ) ;
245- span . finish ( ) ;
246- }
285+ request . __sentry_has_headers__ = true ;
286+ }
247287
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 ;
266304 }
305+ return activeSpan ?. startChild ( {
306+ op : 'http.client' ,
307+ description : `${ method } ${ getSanitizedUrlString ( url ) } ` ,
308+ data,
309+ } ) ;
267310}
0 commit comments