Skip to content

Commit db46eb4

Browse files
committed
WIP improve things...
1 parent fdc96b4 commit db46eb4

File tree

4 files changed

+51
-62
lines changed

4 files changed

+51
-62
lines changed

packages/node/src/integrations/http/index.ts

Lines changed: 47 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import type { ClientRequest, IncomingMessage, RequestOptions, ServerResponse } from 'node:http';
22
import { diag } from '@opentelemetry/api';
3-
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
4-
import { addOpenTelemetryInstrumentation } from '@sentry/opentelemetry';
3+
import { HttpInstrumentation, HttpInstrumentationConfig } from '@opentelemetry/instrumentation-http';
54

65
import { defineIntegration } from '@sentry/core';
76
import { getClient } from '@sentry/opentelemetry';
87
import type { IntegrationFn, Span } from '@sentry/types';
98

9+
import { generateInstrumentOnce } from '../../otel/instrument';
1010
import type { NodeClient } from '../../sdk/client';
1111
import type { HTTPModuleRequestIncomingMessage } from '../../transports/http-module';
1212
import { addOriginToSpan } from '../../utils/addOriginToSpan';
@@ -55,6 +55,12 @@ interface HttpOptions {
5555
*/
5656
ignoreIncomingRequests?: (urlPath: string, request: IncomingMessage) => boolean;
5757

58+
/**
59+
* If true, do not generate spans for incoming requests at all.
60+
* This is used by Remix to avoid generating spans for incoming requests, as it generates its own spans.
61+
*/
62+
disableIncomingRequestSpans?: boolean;
63+
5864
/**
5965
* Additional instrumentation options that are passed to the underlying HttpInstrumentation.
6066
*/
@@ -73,35 +79,53 @@ interface HttpOptions {
7379
*/
7480
_experimentalConfig?: ConstructorParameters<typeof HttpInstrumentation>[0];
7581
};
76-
77-
/** Allows to pass a custom version of HttpInstrumentation. We use this for Next.js. */
78-
_instrumentation?: typeof HttpInstrumentation;
7982
}
8083

81-
let _httpOptions: HttpOptions = {};
82-
let _sentryHttpInstrumentation: SentryHttpInstrumentation | undefined;
83-
let _httpInstrumentation: HttpInstrumentation | undefined;
84+
const instrumentSentryHttp = generateInstrumentOnce<{ breadcrumbs?: boolean }>(
85+
`${INTEGRATION_NAME}.sentry`,
86+
options => {
87+
return new SentryHttpInstrumentation({ breadcrumbs: options?.breadcrumbs });
88+
},
89+
);
90+
91+
const instrumentOtelHttp = generateInstrumentOnce<HttpInstrumentationConfig>(`${INTEGRATION_NAME}.otel`, config => {
92+
const instrumentation = new HttpInstrumentation(config);
93+
94+
// We want to update the logger namespace so we can better identify what is happening here
95+
try {
96+
instrumentation['_diag'] = diag.createComponentLogger({
97+
namespace: INSTRUMENTATION_NAME,
98+
});
99+
// @ts-expect-error We are writing a read-only property here...
100+
instrumentation.instrumentationName = INSTRUMENTATION_NAME;
101+
} catch {
102+
// ignore errors here...
103+
}
104+
105+
return instrumentation;
106+
});
84107

85108
/**
86109
* Instrument the HTTP module.
87110
* This can only be instrumented once! If this called again later, we just update the options.
88111
*/
89112
export const instrumentHttp = Object.assign(
90-
function (): void {
113+
function (options: HttpOptions = {}) {
91114
// This is the "regular" OTEL instrumentation that emits spans
92-
if (_httpOptions.spans !== false && !_httpInstrumentation) {
93-
const _InstrumentationClass = _httpOptions._instrumentation || HttpInstrumentation;
115+
if (options.spans !== false) {
116+
const instrumentationConfig = {
117+
...options.instrumentation?._experimentalConfig,
118+
119+
disableIncomingRequestInstrumentation: options.disableIncomingRequestSpans,
94120

95-
_httpInstrumentation = new _InstrumentationClass({
96-
..._httpOptions.instrumentation?._experimentalConfig,
97121
ignoreOutgoingRequestHook: request => {
98122
const url = getRequestUrl(request);
99123

100124
if (!url) {
101125
return false;
102126
}
103127

104-
const _ignoreOutgoingRequests = _httpOptions.ignoreOutgoingRequests;
128+
const _ignoreOutgoingRequests = options.ignoreOutgoingRequests;
105129
if (_ignoreOutgoingRequests && _ignoreOutgoingRequests(url, request)) {
106130
return true;
107131
}
@@ -120,7 +144,7 @@ export const instrumentHttp = Object.assign(
120144
return true;
121145
}
122146

123-
const _ignoreIncomingRequests = _httpOptions.ignoreIncomingRequests;
147+
const _ignoreIncomingRequests = options.ignoreIncomingRequests;
124148
if (urlPath && _ignoreIncomingRequests && _ignoreIncomingRequests(urlPath, request)) {
125149
return true;
126150
}
@@ -136,7 +160,7 @@ export const instrumentHttp = Object.assign(
136160
span.setAttribute('sentry.http.prefetch', true);
137161
}
138162

139-
_httpOptions.instrumentation?.requestHook?.(span, req);
163+
options.instrumentation?.requestHook?.(span, req);
140164
},
141165
responseHook: (span, res) => {
142166
const client = getClient<NodeClient>();
@@ -146,42 +170,21 @@ export const instrumentHttp = Object.assign(
146170
});
147171
}
148172

149-
_httpOptions.instrumentation?.responseHook?.(span, res);
173+
options.instrumentation?.responseHook?.(span, res);
150174
},
151175
applyCustomAttributesOnSpan: (
152176
span: Span,
153177
request: ClientRequest | HTTPModuleRequestIncomingMessage,
154178
response: HTTPModuleRequestIncomingMessage | ServerResponse,
155179
) => {
156-
_httpOptions.instrumentation?.applyCustomAttributesOnSpan?.(span, request, response);
180+
options.instrumentation?.applyCustomAttributesOnSpan?.(span, request, response);
157181
},
158-
});
159-
160-
// We want to update the logger namespace so we can better identify what is happening here
161-
try {
162-
_httpInstrumentation['_diag'] = diag.createComponentLogger({
163-
namespace: INSTRUMENTATION_NAME,
164-
});
165-
// @ts-expect-error We are writing a read-only property here...
166-
_httpInstrumentation.instrumentationName = INSTRUMENTATION_NAME;
167-
} catch {
168-
// ignore errors here...
169-
}
170-
171-
addOpenTelemetryInstrumentation(_httpInstrumentation);
172-
} else if (_httpOptions.spans === false && _httpInstrumentation) {
173-
_httpInstrumentation.disable();
174-
}
182+
} satisfies HttpInstrumentationConfig;
175183

176-
// This is our custom instrumentation that is responsible for request isolation etc.
177-
// We have to add it after the OTEL instrumentation to ensure that we wrap the already wrapped http module
178-
// Otherwise, the isolation scope does not encompass the OTEL spans
179-
if (!_sentryHttpInstrumentation) {
180-
_sentryHttpInstrumentation = new SentryHttpInstrumentation({ breadcrumbs: _httpOptions.breadcrumbs });
181-
addOpenTelemetryInstrumentation(_sentryHttpInstrumentation);
182-
} else {
183-
_sentryHttpInstrumentation.setConfig({ breadcrumbs: _httpOptions.breadcrumbs });
184+
instrumentOtelHttp(instrumentationConfig);
184185
}
186+
187+
instrumentSentryHttp(options);
185188
},
186189
{
187190
id: INTEGRATION_NAME,
@@ -192,8 +195,7 @@ const _httpIntegration = ((options: HttpOptions = {}) => {
192195
return {
193196
name: INTEGRATION_NAME,
194197
setupOnce() {
195-
_httpOptions = options;
196-
instrumentHttp();
198+
instrumentHttp(options);
197199
},
198200
};
199201
}) satisfies IntegrationFn;

packages/node/src/integrations/tracing/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Integration } from '@sentry/types';
2+
import { instrumentHttp } from '../http';
23

34
import { amqplibIntegration, instrumentAmqplib } from './amqplib';
45
import { connectIntegration, instrumentConnect } from './connect';
@@ -53,6 +54,7 @@ export function getAutoPerformanceIntegrations(): Integration[] {
5354
// eslint-disable-next-line @typescript-eslint/no-explicit-any
5455
export function getOpenTelemetryInstrumentationToPreload(): (((options?: any) => void) & { id: string })[] {
5556
return [
57+
instrumentHttp,
5658
instrumentExpress,
5759
instrumentConnect,
5860
instrumentFastify,

packages/remix/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@
5252
"access": "public"
5353
},
5454
"dependencies": {
55-
"@opentelemetry/instrumentation-http": "0.53.0",
5655
"@remix-run/router": "1.x",
5756
"@sentry/cli": "^2.35.0",
5857
"@sentry/core": "8.31.0",
Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,6 @@
1-
// This integration is ported from the Next.JS SDK.
2-
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
31
import { httpIntegration as originalHttpIntegration } from '@sentry/node';
42
import type { IntegrationFn } from '@sentry/types';
53

6-
class RemixHttpIntegration extends HttpInstrumentation {
7-
// Instead of the default behavior, we just don't do any wrapping for incoming requests
8-
protected _getPatchIncomingRequestFunction(_component: 'http' | 'https') {
9-
return (
10-
original: (event: string, ...args: unknown[]) => boolean,
11-
): ((this: unknown, event: string, ...args: unknown[]) => boolean) => {
12-
return function incomingRequest(this: unknown, event: string, ...args: unknown[]): boolean {
13-
return original.apply(this, [event, ...args]);
14-
};
15-
};
16-
}
17-
}
18-
194
type HttpOptions = Parameters<typeof originalHttpIntegration>[0];
205

216
/**
@@ -25,6 +10,7 @@ type HttpOptions = Parameters<typeof originalHttpIntegration>[0];
2510
export const httpIntegration = ((options: HttpOptions = {}) => {
2611
return originalHttpIntegration({
2712
...options,
28-
_instrumentation: RemixHttpIntegration,
13+
// We disable incoming request spans here, because otherwise we'd end up with duplicate spans.
14+
disableIncomingRequestSpans: true,
2915
});
3016
}) satisfies IntegrationFn;

0 commit comments

Comments
 (0)