1
- /* eslint-disable max-lines */
2
1
import type { ChannelListener } from 'node:diagnostics_channel' ;
3
2
import { subscribe } from 'node:diagnostics_channel' ;
4
3
import type { EventEmitter } from 'node:events' ;
5
4
import type { IncomingMessage , RequestOptions , Server , ServerResponse } from 'node:http' ;
6
5
import type { Socket } from 'node:net' ;
7
6
import { context , createContextKey , propagation } from '@opentelemetry/api' ;
8
- import type { AggregationCounts , Client , Integration , IntegrationFn , RequestEventData , Scope } from '@sentry/core' ;
7
+ import type { AggregationCounts , Client , Integration , IntegrationFn , Scope } from '@sentry/core' ;
9
8
import {
9
+ addNonEnumerableProperty ,
10
10
debug ,
11
11
generateSpanId ,
12
12
getClient ,
@@ -22,17 +22,14 @@ import { MAX_BODY_BYTE_LENGTH } from './constants';
22
22
23
23
type ServerEmit = typeof Server . prototype . emit ;
24
24
25
+ type StartSpanCallback = ( next : ( ) => boolean ) => boolean ;
26
+ type RequestWithOptionalStartSpanCallback = IncomingMessage & {
27
+ _startSpanCallback ?: StartSpanCallback ;
28
+ } ;
29
+
25
30
const HTTP_SERVER_INSTRUMENTED_KEY = createContextKey ( 'sentry_http_server_instrumented' ) ;
26
31
const INTEGRATION_NAME = 'Http.Server' ;
27
32
28
- interface ServerCallbackOptions {
29
- request : IncomingMessage ;
30
- response : ServerResponse ;
31
- normalizedRequest : RequestEventData ;
32
- }
33
-
34
- type ServerCallback = ( fn : ( ) => boolean , options : ServerCallbackOptions ) => boolean ;
35
-
36
33
const clientToRequestSessionAggregatesMap = new Map <
37
34
Client ,
38
35
{ [ timestampRoundedToSeconds : string ] : { exited : number ; crashed : number ; errored : number } }
@@ -86,6 +83,14 @@ export interface HttpServerIntegrationOptions {
86
83
maxRequestBodySize ?: 'none' | 'small' | 'medium' | 'always' ;
87
84
}
88
85
86
+ /**
87
+ * Add a callback to the request object that will be called when the request is started.
88
+ * The callback will receive the next function to continue processing the request.
89
+ */
90
+ export function addStartSpanCallback ( request : RequestWithOptionalStartSpanCallback , callback : StartSpanCallback ) : void {
91
+ addNonEnumerableProperty ( request , '_startSpanCallback' , callback ) ;
92
+ }
93
+
89
94
const _httpServerIntegration = ( ( options : HttpServerIntegrationOptions = { } ) => {
90
95
const _options = {
91
96
sessions : options . sessions ?? true ,
@@ -94,22 +99,17 @@ const _httpServerIntegration = ((options: HttpServerIntegrationOptions = {}) =>
94
99
ignoreRequestBody : options . ignoreRequestBody ,
95
100
} ;
96
101
97
- const serverCallbacks : ServerCallback [ ] = [ ] ;
98
-
99
102
return {
100
103
name : INTEGRATION_NAME ,
101
104
setupOnce ( ) {
102
105
const onHttpServerRequestStart = ( ( _data : unknown ) => {
103
106
const data = _data as { server : Server } ;
104
107
105
- instrumentServer ( data . server , _options , serverCallbacks ) ;
108
+ instrumentServer ( data . server , _options ) ;
106
109
} ) satisfies ChannelListener ;
107
110
108
111
subscribe ( 'http.server.request.start' , onHttpServerRequestStart ) ;
109
112
} ,
110
- addServerCallback ( callback : ServerCallback ) {
111
- serverCallbacks . push ( callback ) ;
112
- } ,
113
113
afterAllSetup ( client ) {
114
114
if ( DEBUG_BUILD && client . getIntegrationByName ( 'Http' ) ) {
115
115
debug . warn (
@@ -129,7 +129,6 @@ export const httpServerIntegration = _httpServerIntegration as (
129
129
) => Integration & {
130
130
name : 'HttpServer' ;
131
131
setupOnce : ( ) => void ;
132
- addServerCallback : ( callback : ServerCallback ) => void ;
133
132
} ;
134
133
135
134
/**
@@ -149,7 +148,6 @@ function instrumentServer(
149
148
sessions : boolean ;
150
149
sessionFlushingDelayMS : number ;
151
150
} ,
152
- serverCallbacks : ServerCallback [ ] ,
153
151
) : void {
154
152
// eslint-disable-next-line @typescript-eslint/unbound-method
155
153
const originalEmit : ServerEmit = server . emit ;
@@ -221,15 +219,14 @@ function instrumentServer(
221
219
. setValue ( HTTP_SERVER_INSTRUMENTED_KEY , true ) ;
222
220
223
221
return context . with ( ctx , ( ) => {
224
- if ( serverCallbacks ?. length ) {
225
- return wrapInCallbacks (
226
- ( ) => target . apply ( thisArg , args ) ,
227
- { request, response, normalizedRequest } ,
228
- serverCallbacks . slice ( ) ,
229
- ) ;
230
- } else {
231
- return target . apply ( thisArg , args ) ;
222
+ // This is used (optionally) by the httpServerSpansIntegration to attach _startSpanCallback to the request object
223
+ client . emit ( 'httpServerRequest' , request , response , normalizedRequest ) ;
224
+
225
+ const callback = ( request as RequestWithOptionalStartSpanCallback ) . _startSpanCallback ;
226
+ if ( callback ) {
227
+ return callback ( ( ) => target . apply ( thisArg , args ) ) ;
232
228
}
229
+ return target . apply ( thisArg , args ) ;
233
230
} ) ;
234
231
} ) ;
235
232
} ,
@@ -432,18 +429,3 @@ function patchRequestToCaptureBody(
432
429
}
433
430
}
434
431
}
435
-
436
- // Wrap a fn in one or multiple callbacks
437
- function wrapInCallbacks (
438
- fn : ( ) => boolean ,
439
- options : ServerCallbackOptions ,
440
- callbacks : ( ( fn : ( ) => boolean , options : ServerCallbackOptions ) => boolean ) [ ] ,
441
- ) : boolean {
442
- const callback = callbacks . shift ( ) ;
443
-
444
- if ( ! callback ) {
445
- return fn ( ) ;
446
- }
447
-
448
- return callback ( ( ) => wrapInCallbacks ( fn , options , callbacks ) , options ) ;
449
- }
0 commit comments