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' ;
10
5
import 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' ;
30
7
31
8
interface NodeFetchOptions {
32
9
/**
@@ -46,106 +23,38 @@ const _nativeNodeFetchIntegration = ((options: NodeFetchOptions = {}) => {
46
23
const _breadcrumbs = typeof options . breadcrumbs === 'undefined' ? true : options . breadcrumbs ;
47
24
const _ignoreOutgoingRequests = options . ignoreOutgoingRequests ;
48
25
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 => {
73
32
const url = getAbsoluteUrl ( request . origin , request . path ) ;
74
- const tracingDisabled = ! hasTracingEnabled ( ) ;
75
33
const shouldIgnore = _ignoreOutgoingRequests && url && _ignoreOutgoingRequests ( url ) ;
76
34
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 ;
115
36
} ,
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
+ }
118
46
} ,
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
- }
135
47
} ) ;
48
+
49
+ addOpenTelemetryInstrumentation ( instrumentation ) ;
136
50
} ,
137
51
} ;
138
52
} ) satisfies IntegrationFn ;
139
53
140
54
export const nativeNodeFetchIntegration = defineIntegration ( _nativeNodeFetchIntegration ) ;
141
55
142
- /** Update the span with data we need. */
143
- function _updateSpan ( span : Span ) : void {
144
- addOriginToSpan ( span , 'auto.http.otel.node_fetch' ) ;
145
- }
146
-
147
56
/** Add a breadcrumb for outgoing requests. */
148
- function _addRequestBreadcrumb ( request : FetchRequest , response : FetchResponse ) : void {
57
+ function addRequestBreadcrumb ( request : UndiciRequest , response : UndiciResponse ) : void {
149
58
const data = getBreadcrumbData ( request ) ;
150
59
151
60
addBreadcrumb (
@@ -165,7 +74,7 @@ function _addRequestBreadcrumb(request: FetchRequest, response: FetchResponse):
165
74
) ;
166
75
}
167
76
168
- function getBreadcrumbData ( request : FetchRequest ) : Partial < SanitizedRequestData > {
77
+ function getBreadcrumbData ( request : UndiciRequest ) : Partial < SanitizedRequestData > {
169
78
try {
170
79
const url = new URL ( request . path , request . origin ) ;
171
80
const parsedUrl = parseUrl ( url . toString ( ) ) ;
0 commit comments