1
- import type { ClientRequest , IncomingMessage , ServerResponse } from 'node:http' ;
1
+ import type { ClientRequest , ServerResponse } from 'node:http' ;
2
2
import type { Span } from '@opentelemetry/api' ;
3
- import { SpanKind } from '@opentelemetry/api' ;
4
3
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http' ;
5
4
import { addOpenTelemetryInstrumentation } from '@sentry/opentelemetry' ;
6
5
@@ -13,10 +12,10 @@ import {
13
12
isSentryRequestUrl ,
14
13
setCapturedScopesOnSpan ,
15
14
} from '@sentry/core' ;
16
- import { getClient , getRequestSpanData , getSpanKind } from '@sentry/opentelemetry' ;
17
- import type { IntegrationFn } from '@sentry/types' ;
15
+ import { getClient } from '@sentry/opentelemetry' ;
16
+ import type { IntegrationFn , SanitizedRequestData } from '@sentry/types' ;
18
17
19
- import { stripUrlQueryAndFragment } from '@sentry/utils' ;
18
+ import { getSanitizedUrlString , parseUrl , stripUrlQueryAndFragment } from '@sentry/utils' ;
20
19
import type { NodeClient } from '../sdk/client' ;
21
20
import { setIsolationScope } from '../sdk/scope' ;
22
21
import type { HTTPModuleRequestIncomingMessage } from '../transports/http-module' ;
@@ -127,18 +126,23 @@ const _httpIntegration = ((options: HttpOptions = {}) => {
127
126
128
127
isolationScope . setTransactionName ( bestEffortTransactionName ) ;
129
128
} ,
130
- responseHook : ( span , res ) => {
131
- if ( _breadcrumbs ) {
132
- _addRequestBreadcrumb ( span , res ) ;
133
- }
134
-
129
+ responseHook : ( ) => {
135
130
const client = getClient < NodeClient > ( ) ;
136
131
if ( client && client . getOptions ( ) . autoSessionTracking ) {
137
132
setImmediate ( ( ) => {
138
133
client [ '_captureRequestSession' ] ( ) ;
139
134
} ) ;
140
135
}
141
136
} ,
137
+ applyCustomAttributesOnSpan : (
138
+ _span : Span ,
139
+ request : ClientRequest | HTTPModuleRequestIncomingMessage ,
140
+ response : HTTPModuleRequestIncomingMessage | ServerResponse ,
141
+ ) => {
142
+ if ( _breadcrumbs ) {
143
+ _addRequestBreadcrumb ( request , response ) ;
144
+ }
145
+ } ,
142
146
} ) ,
143
147
) ;
144
148
} ,
@@ -152,12 +156,16 @@ const _httpIntegration = ((options: HttpOptions = {}) => {
152
156
export const httpIntegration = defineIntegration ( _httpIntegration ) ;
153
157
154
158
/** Add a breadcrumb for outgoing requests. */
155
- function _addRequestBreadcrumb ( span : Span , response : HTTPModuleRequestIncomingMessage | ServerResponse ) : void {
156
- if ( getSpanKind ( span ) !== SpanKind . CLIENT ) {
159
+ function _addRequestBreadcrumb (
160
+ request : ClientRequest | HTTPModuleRequestIncomingMessage ,
161
+ response : HTTPModuleRequestIncomingMessage | ServerResponse ,
162
+ ) : void {
163
+ // Only generate breadcrumbs for outgoing requests
164
+ if ( ! _isClientRequest ( request ) ) {
157
165
return ;
158
166
}
159
167
160
- const data = getRequestSpanData ( span ) ;
168
+ const data = getBreadcrumbData ( request ) ;
161
169
addBreadcrumb (
162
170
{
163
171
category : 'http' ,
@@ -169,19 +177,42 @@ function _addRequestBreadcrumb(span: Span, response: HTTPModuleRequestIncomingMe
169
177
} ,
170
178
{
171
179
event : 'response' ,
172
- // TODO FN: Do we need access to `request` here?
173
- // If we do, we'll have to use the `applyCustomAttributesOnSpan` hook instead,
174
- // but this has worse context semantics than request/responseHook.
180
+ request,
175
181
response,
176
182
} ,
177
183
) ;
178
184
}
179
185
186
+ function getBreadcrumbData ( request : ClientRequest ) : Partial < SanitizedRequestData > {
187
+ try {
188
+ // `request.host` does not contain the port, but the host header does
189
+ const host = request . getHeader ( 'host' ) || request . host ;
190
+ const url = new URL ( request . path , `${ request . protocol } //${ host } ` ) ;
191
+ const parsedUrl = parseUrl ( url . toString ( ) ) ;
192
+
193
+ const data : Partial < SanitizedRequestData > = {
194
+ url : getSanitizedUrlString ( parsedUrl ) ,
195
+ 'http.method' : request . method || 'GET' ,
196
+ } ;
197
+
198
+ if ( parsedUrl . search ) {
199
+ data [ 'http.query' ] = parsedUrl . search ;
200
+ }
201
+ if ( parsedUrl . hash ) {
202
+ data [ 'http.fragment' ] = parsedUrl . hash ;
203
+ }
204
+
205
+ return data ;
206
+ } catch {
207
+ return { } ;
208
+ }
209
+ }
210
+
180
211
/**
181
212
* Determines if @param req is a ClientRequest, meaning the request was created within the express app
182
213
* and it's an outgoing request.
183
214
* Checking for properties instead of using `instanceOf` to avoid importing the request classes.
184
215
*/
185
- function _isClientRequest ( req : ClientRequest | IncomingMessage ) : req is ClientRequest {
216
+ function _isClientRequest ( req : ClientRequest | HTTPModuleRequestIncomingMessage ) : req is ClientRequest {
186
217
return 'outputData' in req && 'outputSize' in req && ! ( 'client' in req ) && ! ( 'statusCode' in req ) ;
187
218
}
0 commit comments