11import { subscribe } from 'node:diagnostics_channel' ;
22import type { RequestOptions } from 'node:http' ;
3- import type { Integration , IntegrationFn } from '@sentry/core' ;
3+ import type { HttpClientRequest , HttpIncomingMessage , Integration , IntegrationFn } from '@sentry/core' ;
44import {
5+ addOutgoingRequestBreadcrumb ,
56 continueTrace ,
67 debug ,
78 generateSpanId ,
89 getCurrentScope ,
910 getHttpClientSubscriptions ,
1011 getIsolationScope ,
1112 HTTP_ON_CLIENT_REQUEST ,
12- HTTP_ON_CLIENT_REQUEST_FALLBACK ,
1313 httpRequestToRequestData ,
1414 stripUrlQueryAndFragment ,
1515 withIsolationScope ,
1616} from '@sentry/core' ;
1717import type { ClientRequest , IncomingMessage , Server } from 'node:http' ;
1818import { DEBUG_BUILD } from '../../debug-build' ;
1919import { patchRequestToCaptureBody } from '../../utils/captureRequestBody' ;
20- import { getRequestOptions } from '../../utils/outgoingHttpRequest' ;
20+ import { getClientRequestUrl , getRequestOptions } from '../../utils/outgoingHttpRequest' ;
2121import type { LightNodeClient } from '../client' ;
2222import { errorMonitor } from 'node:events' ;
2323import { NODE_VERSION } from '../../nodeVersion' ;
@@ -106,10 +106,6 @@ const _httpIntegration = ((options: HttpIntegrationOptions = {}) => {
106106
107107 const { ignoreOutgoingRequests } = _options ;
108108
109- const channel = FULLY_SUPPORTS_HTTP_DIAGNOSTICS_CHANNEL
110- ? HTTP_ON_CLIENT_REQUEST
111- : HTTP_ON_CLIENT_REQUEST_FALLBACK ;
112-
113109 const { [ HTTP_ON_CLIENT_REQUEST ] : onHttpClientRequestCreated } = getHttpClientSubscriptions ( {
114110 breadcrumbs : _options . breadcrumbs ,
115111 propagateTrace : _options . tracePropagation ,
@@ -122,13 +118,52 @@ const _httpIntegration = ((options: HttpIntegrationOptions = {}) => {
122118 } ) ;
123119
124120 subscribe ( 'http.server.request.start' , onHttpServerRequestStart ) ;
125- // Subscribe on the request creation in node versions that support it,
126- // or to the request start (ie, .end() being called) on older versions.
127- subscribe ( channel , onHttpClientRequestCreated ) ;
121+
122+ // Subscribe on the request creation in node versions that support it
123+ subscribe ( HTTP_ON_CLIENT_REQUEST , onHttpClientRequestCreated ) ;
124+
125+ // fall back to just doing breadcrumbs on the request.end() channel
126+ // if we do not have earlier access to the request object at creation
127+ // time. The http.client.request.error channel is only available on
128+ // the same node versions as client.request.created, so no help.
129+ if ( _options . breadcrumbs && ! FULLY_SUPPORTS_HTTP_DIAGNOSTICS_CHANNEL ) {
130+ subscribe ( 'http.client.response.finish' , ( data : unknown ) => {
131+ const { request, response } = data as {
132+ request : HttpClientRequest ;
133+ response : HttpIncomingMessage ;
134+ } ;
135+ onOutgoingResponseFinish ( request , response , _options ) ;
136+ } ) ;
137+ }
128138 } ,
129139 } ;
130140} ) satisfies IntegrationFn ;
131141
142+ function onOutgoingResponseFinish (
143+ request : HttpClientRequest ,
144+ response : HttpIncomingMessage | undefined ,
145+ options : {
146+ breadcrumbs : boolean ;
147+ ignoreOutgoingRequests ?: ( url : string , request : RequestOptions ) => boolean ;
148+ } ,
149+ ) : void {
150+ if ( ! options . breadcrumbs ) {
151+ return ;
152+ }
153+ // Check if tracing is suppressed (e.g. for Sentry's own transport requests)
154+ if ( getCurrentScope ( ) . getScopeData ( ) . sdkProcessingMetadata . __SENTRY_SUPPRESS_TRACING__ ) {
155+ return ;
156+ }
157+ const { ignoreOutgoingRequests } = options ;
158+ if ( ignoreOutgoingRequests ) {
159+ const url = getClientRequestUrl ( request as ClientRequest ) ;
160+ if ( ignoreOutgoingRequests ( url , getRequestOptions ( request as ClientRequest ) ) ) {
161+ return ;
162+ }
163+ }
164+ addOutgoingRequestBreadcrumb ( request , response ) ;
165+ }
166+
132167/**
133168 * This integration handles incoming and outgoing HTTP requests in light mode (without OpenTelemetry).
134169 *
0 commit comments