@@ -35,10 +35,7 @@ import { DEBUG_BUILD } from '../../debug-build';
35
35
import type { NodeClient } from '../../sdk/client' ;
36
36
import { INSTRUMENTATION_NAME , MAX_BODY_BYTE_LENGTH } from './constants' ;
37
37
38
- type Emit = typeof Server . prototype . emit & {
39
- __sentry_patched__ ?: boolean ;
40
- __sentryOriginalFn__ ?: typeof Server . prototype . emit ;
41
- } ;
38
+ type ServerEmit = typeof Server . prototype . emit ;
42
39
43
40
const HTTP_SERVER_INSTRUMENTED_KEY = createContextKey ( 'sentry_http_server_instrumented' ) ;
44
41
@@ -50,7 +47,8 @@ const clientToRequestSessionAggregatesMap = new Map<
50
47
// We keep track of emit functions we wrapped, to avoid double wrapping
51
48
// We do this instead of putting a non-enumerable property on the function, because
52
49
// sometimes the property seems to be migrated to forks of the emit function, which we do not want to happen
53
- const wrappedEmitFns = new WeakSet < Emit > ( ) ;
50
+ // This was the case in the nestjs-distributed-tracing E2E test
51
+ const wrappedEmitFns = new WeakSet < ServerEmit > ( ) ;
54
52
55
53
/**
56
54
* Instrument a server to capture incoming requests.
@@ -90,10 +88,8 @@ export function instrumentServer(
90
88
} ;
91
89
} ,
92
90
) : void {
93
- type Emit = typeof server . emit & { __sentryOriginalFn__ ?: typeof server . emit } ;
94
-
95
91
// eslint-disable-next-line @typescript-eslint/unbound-method
96
- const originalEmit : Emit = server . emit ;
92
+ const originalEmit : ServerEmit = server . emit ;
97
93
98
94
if ( wrappedEmitFns . has ( originalEmit ) ) {
99
95
DEBUG_BUILD &&
@@ -103,33 +99,6 @@ export function instrumentServer(
103
99
104
100
const { requestHook, responseHook, applyCustomAttributesOnSpan } = instrumentation ?? { } ;
105
101
106
- function shouldIgnoreSpansForIncomingRequest ( request : IncomingMessage ) : boolean {
107
- if ( isTracingSuppressed ( context . active ( ) ) ) {
108
- return true ;
109
- }
110
-
111
- // request.url is the only property that holds any information about the url
112
- // it only consists of the URL path and query string (if any)
113
- const urlPath = request . url ;
114
-
115
- const method = request . method ?. toUpperCase ( ) ;
116
- // We do not capture OPTIONS/HEAD requests as spans
117
- if ( method === 'OPTIONS' || method === 'HEAD' || ! urlPath ) {
118
- return true ;
119
- }
120
-
121
- // Default static asset filtering
122
- if ( ignoreStaticAssets && method === 'GET' && isStaticAssetRequest ( urlPath ) ) {
123
- return true ;
124
- }
125
-
126
- if ( ignoreSpansForIncomingRequests ?.( urlPath , request ) ) {
127
- return true ;
128
- }
129
-
130
- return false ;
131
- }
132
-
133
102
const newEmit = new Proxy ( originalEmit , {
134
103
apply ( target , thisArg , args : [ event : string , ...args : unknown [ ] ] ) {
135
104
// Only traces request events
@@ -138,7 +107,7 @@ export function instrumentServer(
138
107
}
139
108
140
109
// Make sure we do not double execute our wrapper code, for edge cases...
141
- // Without this check, if we double-wrap emit, for whatever reason, you'd get to http.server spans (one the children of the other)
110
+ // Without this check, if we double-wrap emit, for whatever reason, you'd get two http.server spans (one the children of the other)
142
111
if ( context . active ( ) . getValue ( HTTP_SERVER_INSTRUMENTED_KEY ) ) {
143
112
return target . apply ( thisArg , args ) ;
144
113
}
@@ -193,7 +162,14 @@ export function instrumentServer(
193
162
194
163
return context . with ( ctx , ( ) => {
195
164
// if opting out of span creation, we can end here
196
- if ( ! spans || shouldIgnoreSpansForIncomingRequest ( request ) || ! client ) {
165
+ if (
166
+ ! spans ||
167
+ ! client ||
168
+ shouldIgnoreSpansForIncomingRequest ( request , {
169
+ ignoreStaticAssets,
170
+ ignoreSpansForIncomingRequests,
171
+ } )
172
+ ) {
197
173
DEBUG_BUILD && debug . log ( INSTRUMENTATION_NAME , 'Skipping span creation for incoming request' ) ;
198
174
return target . apply ( thisArg , args ) ;
199
175
}
@@ -249,7 +225,8 @@ export function instrumentServer(
249
225
context . bind ( context . active ( ) , request ) ;
250
226
context . bind ( context . active ( ) , response ) ;
251
227
252
- // After 'error', no further events other than 'close' should be emitted.
228
+ // Ensure we only end the span once
229
+ // E.g. error can be emitted before close is emitted
253
230
let isEnded = false ;
254
231
function endSpan ( status : SpanStatus ) : void {
255
232
if ( isEnded ) {
@@ -264,10 +241,9 @@ export function instrumentServer(
264
241
span . end ( ) ;
265
242
266
243
// Update the transaction name if the route has changed
267
- if ( newAttributes [ 'http.route' ] ) {
268
- getIsolationScope ( ) . setTransactionName (
269
- `${ request . method ?. toUpperCase ( ) || 'GET' } ${ newAttributes [ 'http.route' ] } ` ,
270
- ) ;
244
+ const route = newAttributes [ 'http.route' ] ;
245
+ if ( route ) {
246
+ getIsolationScope ( ) . setTransactionName ( `${ request . method ?. toUpperCase ( ) || 'GET' } ${ route } ` ) ;
271
247
}
272
248
}
273
249
@@ -577,3 +553,39 @@ export function isStaticAssetRequest(urlPath: string): boolean {
577
553
578
554
return false ;
579
555
}
556
+
557
+ function shouldIgnoreSpansForIncomingRequest (
558
+ request : IncomingMessage ,
559
+ {
560
+ ignoreStaticAssets,
561
+ ignoreSpansForIncomingRequests,
562
+ } : {
563
+ ignoreStaticAssets ?: boolean ;
564
+ ignoreSpansForIncomingRequests ?: ( urlPath : string , request : IncomingMessage ) => boolean ;
565
+ } ,
566
+ ) : boolean {
567
+ if ( isTracingSuppressed ( context . active ( ) ) ) {
568
+ return true ;
569
+ }
570
+
571
+ // request.url is the only property that holds any information about the url
572
+ // it only consists of the URL path and query string (if any)
573
+ const urlPath = request . url ;
574
+
575
+ const method = request . method ?. toUpperCase ( ) ;
576
+ // We do not capture OPTIONS/HEAD requests as spans
577
+ if ( method === 'OPTIONS' || method === 'HEAD' || ! urlPath ) {
578
+ return true ;
579
+ }
580
+
581
+ // Default static asset filtering
582
+ if ( ignoreStaticAssets && method === 'GET' && isStaticAssetRequest ( urlPath ) ) {
583
+ return true ;
584
+ }
585
+
586
+ if ( ignoreSpansForIncomingRequests ?.( urlPath , request ) ) {
587
+ return true ;
588
+ }
589
+
590
+ return false ;
591
+ }
0 commit comments