Skip to content

Commit 512e319

Browse files
committed
ref(node): Avoid double wrapping http module for vercel-edge
1 parent 72ddee3 commit 512e319

File tree

4 files changed

+64
-187
lines changed

4 files changed

+64
-187
lines changed

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

+62
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable max-lines */
12
import { subscribe } from 'node:diagnostics_channel';
23
import type * as http from 'node:http';
34
import type { EventEmitter } from 'node:stream';
@@ -9,6 +10,7 @@ import type { AggregationCounts, Client, SanitizedRequestData, Scope } from '@se
910
import {
1011
addBreadcrumb,
1112
addNonEnumerableProperty,
13+
flush,
1214
generateSpanId,
1315
getBreadcrumbLogLevelFromHttpStatusCode,
1416
getClient,
@@ -19,6 +21,7 @@ import {
1921
logger,
2022
parseUrl,
2123
stripUrlQueryAndFragment,
24+
vercelWaitUntil,
2225
withIsolationScope,
2326
} from '@sentry/core';
2427
import { DEBUG_BUILD } from '../../debug-build';
@@ -114,6 +117,14 @@ export class SentryHttpInstrumentation extends InstrumentationBase<SentryHttpIns
114117
this._onOutgoingRequestFinish(request, response);
115118
});
116119

120+
// On vercel, ensure that we flush events before the lambda freezes
121+
if (process.env.VERCEL) {
122+
subscribe('http.server.response.created', data => {
123+
const response = (data as { response: http.ServerResponse }).response;
124+
patchResponseToFlushOnServerlessPlatforms(response);
125+
});
126+
}
127+
117128
return [];
118129
}
119130

@@ -458,3 +469,54 @@ const clientToRequestSessionAggregatesMap = new Map<
458469
Client,
459470
{ [timestampRoundedToSeconds: string]: { exited: number; crashed: number; errored: number } }
460471
>();
472+
473+
function patchResponseToFlushOnServerlessPlatforms(res: http.OutgoingMessage): void {
474+
// Freely extend this function with other platforms if necessary
475+
if (process.env.VERCEL) {
476+
// In some cases res.end does not seem to be defined leading to errors if passed to Proxy
477+
// https://github.com/getsentry/sentry-javascript/issues/15759
478+
if (typeof res.end === 'function') {
479+
let markOnEndDone = (): void => undefined;
480+
const onEndDonePromise = new Promise<void>(res => {
481+
markOnEndDone = res;
482+
});
483+
484+
res.on('close', () => {
485+
markOnEndDone();
486+
});
487+
488+
// eslint-disable-next-line @typescript-eslint/unbound-method
489+
res.end = new Proxy(res.end, {
490+
apply(target, thisArg, argArray) {
491+
vercelWaitUntil(
492+
new Promise<void>(finishWaitUntil => {
493+
// Define a timeout that unblocks the lambda just to be safe so we're not indefinitely keeping it alive, exploding server bills
494+
const timeout = setTimeout(() => {
495+
finishWaitUntil();
496+
}, 2000);
497+
498+
onEndDonePromise
499+
.then(() => {
500+
DEBUG_BUILD && logger.log('Flushing events before Vercel Lambda freeze');
501+
return flush(2000);
502+
})
503+
.then(
504+
() => {
505+
clearTimeout(timeout);
506+
finishWaitUntil();
507+
},
508+
e => {
509+
clearTimeout(timeout);
510+
DEBUG_BUILD && logger.log('Error while flushing events for Vercel:\n', e);
511+
finishWaitUntil();
512+
},
513+
);
514+
}),
515+
);
516+
517+
return target.apply(thisArg, argArray);
518+
},
519+
});
520+
}
521+
}
522+
}

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

-130
This file was deleted.

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

+2-18
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { addOriginToSpan } from '../../utils/addOriginToSpan';
1212
import { getRequestUrl } from '../../utils/getRequestUrl';
1313
import type { SentryHttpInstrumentationOptions } from './SentryHttpInstrumentation';
1414
import { SentryHttpInstrumentation } from './SentryHttpInstrumentation';
15-
import { SentryHttpInstrumentationBeforeOtel } from './SentryHttpInstrumentationBeforeOtel';
1615

1716
const INTEGRATION_NAME = 'Http';
1817

@@ -108,10 +107,6 @@ interface HttpOptions {
108107
};
109108
}
110109

111-
const instrumentSentryHttpBeforeOtel = generateInstrumentOnce(`${INTEGRATION_NAME}.sentry-before-otel`, () => {
112-
return new SentryHttpInstrumentationBeforeOtel();
113-
});
114-
115110
const instrumentSentryHttp = generateInstrumentOnce<SentryHttpInstrumentationOptions>(
116111
`${INTEGRATION_NAME}.sentry`,
117112
options => {
@@ -151,18 +146,6 @@ export const httpIntegration = defineIntegration((options: HttpOptions = {}) =>
151146
return {
152147
name: INTEGRATION_NAME,
153148
setupOnce() {
154-
// Below, we instrument the Node.js HTTP API three times. 2 times Sentry-specific, 1 time OTEL specific.
155-
// Due to timing reasons, we sometimes need to apply Sentry instrumentation _before_ we apply the OTEL
156-
// instrumentation (e.g. to flush on serverless platforms), and sometimes we need to apply Sentry instrumentation
157-
// _after_ we apply OTEL instrumentation (e.g. for isolation scope handling and breadcrumbs).
158-
159-
// This is Sentry-specific instrumentation that is applied _before_ any OTEL instrumentation.
160-
if (process.env.VERCEL) {
161-
// Currently this instrumentation only does something when deployed on Vercel, so to save some overhead, we short circuit adding it here only for Vercel.
162-
// If it's functionality is extended in the future, feel free to remove the if statement and this comment.
163-
instrumentSentryHttpBeforeOtel();
164-
}
165-
166149
const instrumentSpans = _shouldInstrumentSpans(options, getClient<NodeClient>()?.getOptions());
167150

168151
// This is the "regular" OTEL instrumentation that emits spans
@@ -171,7 +154,8 @@ export const httpIntegration = defineIntegration((options: HttpOptions = {}) =>
171154
instrumentOtelHttp(instrumentationConfig);
172155
}
173156

174-
// This is Sentry-specific instrumentation that is applied _after_ any OTEL instrumentation.
157+
// This is Sentry-specific instrumentation
158+
// It uses diagnostics channel, so it does not rely on import-in-the-middle or similar shenanigans
175159
instrumentSentryHttp({
176160
...options,
177161
// If spans are not instrumented, it means the HttpInstrumentation has not been added

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

-39
This file was deleted.

0 commit comments

Comments
 (0)