@sentry/node
Version:
Sentry Node SDK using OpenTelemetry for performance instrumentation
131 lines (111 loc) • 4.51 kB
JavaScript
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
const core = require('@opentelemetry/core');
const instrumentation = require('@opentelemetry/instrumentation');
const core$1 = require('@sentry/core');
const debugBuild = require('../../debug-build.js');
const utils = require('./utils.js');
// The reason this "before OTEL" integration even exists is due to timing reasons. We need to be able to register a
// `res.on('close')` handler **after** OTEL registers its own handler (which it uses to end spans), so that we can do
// something (ie. flush) after OTEL has ended a span for a request. If you think about it like an onion:
//
// (Sentry after OTEL instrumentation
// (OTEL instrumentation
// (Sentry before OTEL instrumentation
// (orig HTTP request handler))))
//
// registering an instrumentation before OTEL allows us to do this for incoming requests.
/**
* A Sentry specific http instrumentation that is applied before the otel instrumentation.
*/
class SentryHttpInstrumentationBeforeOtel extends instrumentation.InstrumentationBase {
constructor() {
super('@sentry/instrumentation-http-before-otel', core.VERSION, {});
}
// eslint-disable-next-line jsdoc/require-jsdoc
init() {
return [this._getHttpsInstrumentation(), this._getHttpInstrumentation()];
}
/** Get the instrumentation for the http module. */
_getHttpInstrumentation() {
return new instrumentation.InstrumentationNodeModuleDefinition('http', ['*'], (moduleExports) => {
// Patch incoming requests
utils.stealthWrap(moduleExports.Server.prototype, 'emit', this._getPatchIncomingRequestFunction());
return moduleExports;
});
}
/** Get the instrumentation for the https module. */
_getHttpsInstrumentation() {
return new instrumentation.InstrumentationNodeModuleDefinition('https', ['*'], (moduleExports) => {
// Patch incoming requests
utils.stealthWrap(moduleExports.Server.prototype, 'emit', this._getPatchIncomingRequestFunction());
return moduleExports;
});
}
/**
* Patch the incoming request function for request isolation.
*/
_getPatchIncomingRequestFunction()
{
return (
original,
) => {
return function incomingRequest( ...args) {
// Only traces request events
if (args[0] !== 'request') {
return original.apply(this, args);
}
const response = args[1] ;
patchResponseToFlushOnServerlessPlatforms(response);
return original.apply(this, args);
};
};
}
}
function patchResponseToFlushOnServerlessPlatforms(res) {
// Freely extend this function with other platforms if necessary
if (process.env.VERCEL) {
// In some cases res.end does not seem to be defined leading to errors if passed to Proxy
// https://github.com/getsentry/sentry-javascript/issues/15759
if (typeof res.end === 'function') {
let markOnEndDone = () => undefined;
const onEndDonePromise = new Promise(res => {
markOnEndDone = res;
});
res.on('close', () => {
markOnEndDone();
});
// eslint-disable-next-line @typescript-eslint/unbound-method
res.end = new Proxy(res.end, {
apply(target, thisArg, argArray) {
core$1.vercelWaitUntil(
new Promise(finishWaitUntil => {
// Define a timeout that unblocks the lambda just to be safe so we're not indefinitely keeping it alive, exploding server bills
const timeout = setTimeout(() => {
finishWaitUntil();
}, 2000);
onEndDonePromise
.then(() => {
debugBuild.DEBUG_BUILD && core$1.logger.log('Flushing events before Vercel Lambda freeze');
return core$1.flush(2000);
})
.then(
() => {
clearTimeout(timeout);
finishWaitUntil();
},
e => {
clearTimeout(timeout);
debugBuild.DEBUG_BUILD && core$1.logger.log('Error while flushing events for Vercel:\n', e);
finishWaitUntil();
},
);
}),
);
return target.apply(thisArg, argArray);
},
});
}
}
}
exports.SentryHttpInstrumentationBeforeOtel = SentryHttpInstrumentationBeforeOtel;
//# sourceMappingURL=SentryHttpInstrumentationBeforeOtel.js.map