1- import type { Span } from '@opentelemetry/api' ;
2- import { trace } from '@opentelemetry/api' ;
3- import { context , propagation } from '@opentelemetry/api' ;
4- import { addBreadcrumb , defineIntegration , getCurrentScope , hasTracingEnabled } from '@sentry/core' ;
5- import {
6- addOpenTelemetryInstrumentation ,
7- generateSpanContextForPropagationContext ,
8- getPropagationContextFromSpan ,
9- } from '@sentry/opentelemetry' ;
1+ import type { UndiciRequest , UndiciResponse } from '@opentelemetry/instrumentation-undici' ;
2+ import { UndiciInstrumentation } from '@opentelemetry/instrumentation-undici' ;
3+ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN , addBreadcrumb , defineIntegration } from '@sentry/core' ;
4+ import { addOpenTelemetryInstrumentation } from '@sentry/opentelemetry' ;
105import type { IntegrationFn , SanitizedRequestData } from '@sentry/types' ;
11- import { getSanitizedUrlString , logger , parseUrl } from '@sentry/utils' ;
12- import { DEBUG_BUILD } from '../debug-build' ;
13- import { NODE_MAJOR } from '../nodeVersion' ;
14-
15- import type { FetchInstrumentation } from 'opentelemetry-instrumentation-fetch-node' ;
16-
17- import { addOriginToSpan } from '../utils/addOriginToSpan' ;
18-
19- interface FetchRequest {
20- method : string ;
21- origin : string ;
22- path : string ;
23- headers : string | string [ ] ;
24- }
25-
26- interface FetchResponse {
27- headers : Buffer [ ] ;
28- statusCode : number ;
29- }
6+ import { getSanitizedUrlString , parseUrl } from '@sentry/utils' ;
307
318interface NodeFetchOptions {
329 /**
@@ -46,106 +23,38 @@ const _nativeNodeFetchIntegration = ((options: NodeFetchOptions = {}) => {
4623 const _breadcrumbs = typeof options . breadcrumbs === 'undefined' ? true : options . breadcrumbs ;
4724 const _ignoreOutgoingRequests = options . ignoreOutgoingRequests ;
4825
49- async function getInstrumentation ( ) : Promise < FetchInstrumentation | void > {
50- // Only add NodeFetch if Node >= 18, as previous versions do not support it
51- if ( NODE_MAJOR < 18 ) {
52- DEBUG_BUILD && logger . log ( 'NodeFetch is not supported on Node < 18, skipping instrumentation...' ) ;
53- return ;
54- }
55-
56- try {
57- const pkg = await import ( 'opentelemetry-instrumentation-fetch-node' ) ;
58- const { FetchInstrumentation } = pkg ;
59-
60- class SentryNodeFetchInstrumentation extends FetchInstrumentation {
61- // We extend this method so we have access to request _and_ response for the breadcrumb
62- public onHeaders ( { request, response } : { request : FetchRequest ; response : FetchResponse } ) : void {
63- if ( _breadcrumbs ) {
64- _addRequestBreadcrumb ( request , response ) ;
65- }
66-
67- return super . onHeaders ( { request, response } ) ;
68- }
69- }
70-
71- return new SentryNodeFetchInstrumentation ( {
72- ignoreRequestHook : ( request : FetchRequest ) => {
26+ return {
27+ name : 'NodeFetch' ,
28+ setupOnce ( ) {
29+ const instrumentation = new UndiciInstrumentation ( {
30+ requireParentforSpans : false ,
31+ ignoreRequestHook : request => {
7332 const url = getAbsoluteUrl ( request . origin , request . path ) ;
74- const tracingDisabled = ! hasTracingEnabled ( ) ;
7533 const shouldIgnore = _ignoreOutgoingRequests && url && _ignoreOutgoingRequests ( url ) ;
7634
77- if ( shouldIgnore ) {
78- return true ;
79- }
80-
81- // If tracing is disabled, we still want to propagate traces
82- // So we do that manually here, matching what the instrumentation does otherwise
83- if ( tracingDisabled ) {
84- const ctx = context . active ( ) ;
85- const addedHeaders : Record < string , string > = { } ;
86-
87- // We generate a virtual span context from the active one,
88- // Where we attach the URL to the trace state, so the propagator can pick it up
89- const activeSpan = trace . getSpan ( ctx ) ;
90- const propagationContext = activeSpan
91- ? getPropagationContextFromSpan ( activeSpan )
92- : getCurrentScope ( ) . getPropagationContext ( ) ;
93-
94- const spanContext = generateSpanContextForPropagationContext ( propagationContext ) ;
95- // We know that in practice we'll _always_ haven a traceState here
96- spanContext . traceState = spanContext . traceState ?. set ( 'sentry.url' , url ) ;
97- const ctxWithUrlTraceState = trace . setSpanContext ( ctx , spanContext ) ;
98-
99- propagation . inject ( ctxWithUrlTraceState , addedHeaders ) ;
100-
101- const requestHeaders = request . headers ;
102- if ( Array . isArray ( requestHeaders ) ) {
103- Object . entries ( addedHeaders ) . forEach ( headers => requestHeaders . push ( ...headers ) ) ;
104- } else {
105- request . headers += Object . entries ( addedHeaders )
106- . map ( ( [ k , v ] ) => `${ k } : ${ v } \r\n` )
107- . join ( '' ) ;
108- }
109-
110- // Prevent starting a span for this request
111- return true ;
112- }
113-
114- return false ;
35+ return ! ! shouldIgnore ;
11536 } ,
116- onRequest : ( { span } : { span : Span } ) => {
117- _updateSpan ( span ) ;
37+ startSpanHook : ( ) => {
38+ return {
39+ [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.http.otel.node_fetch' ,
40+ } ;
41+ } ,
42+ responseHook : ( _ , { request, response } ) => {
43+ if ( _breadcrumbs ) {
44+ addRequestBreadcrumb ( request , response ) ;
45+ }
11846 } ,
119- // eslint-disable-next-line @typescript-eslint/no-explicit-any
120- } as any ) ;
121- } catch ( error ) {
122- // Could not load instrumentation
123- DEBUG_BUILD && logger . log ( 'Error while loading NodeFetch instrumentation: \n' , error ) ;
124- }
125- }
126-
127- return {
128- name : 'NodeFetch' ,
129- setupOnce ( ) {
130- // eslint-disable-next-line @typescript-eslint/no-floating-promises
131- getInstrumentation ( ) . then ( instrumentation => {
132- if ( instrumentation ) {
133- addOpenTelemetryInstrumentation ( instrumentation ) ;
134- }
13547 } ) ;
48+
49+ addOpenTelemetryInstrumentation ( instrumentation ) ;
13650 } ,
13751 } ;
13852} ) satisfies IntegrationFn ;
13953
14054export const nativeNodeFetchIntegration = defineIntegration ( _nativeNodeFetchIntegration ) ;
14155
142- /** Update the span with data we need. */
143- function _updateSpan ( span : Span ) : void {
144- addOriginToSpan ( span , 'auto.http.otel.node_fetch' ) ;
145- }
146-
14756/** Add a breadcrumb for outgoing requests. */
148- function _addRequestBreadcrumb ( request : FetchRequest , response : FetchResponse ) : void {
57+ function addRequestBreadcrumb ( request : UndiciRequest , response : UndiciResponse ) : void {
14958 const data = getBreadcrumbData ( request ) ;
15059
15160 addBreadcrumb (
@@ -165,7 +74,7 @@ function _addRequestBreadcrumb(request: FetchRequest, response: FetchResponse):
16574 ) ;
16675}
16776
168- function getBreadcrumbData ( request : FetchRequest ) : Partial < SanitizedRequestData > {
77+ function getBreadcrumbData ( request : UndiciRequest ) : Partial < SanitizedRequestData > {
16978 try {
17079 const url = new URL ( request . path , request . origin ) ;
17180 const parsedUrl = parseUrl ( url . toString ( ) ) ;
0 commit comments