@instana/core
Version:
Core library for Instana's Node.js packages
101 lines (86 loc) • 3.46 kB
JavaScript
/*
* (c) Copyright IBM Corp. 2021
* (c) Copyright Instana Inc. and contributors 2020
*/
;
const shimmer = require('../../shimmer');
const hook = require('../../../util/hook');
const cls = require('../../cls');
/** @type {import('@instana/core/src/core').GenericLogger} */
let logger;
/**
* @param {import('@instana/core/src/config').InstanaConfig} config
*/
exports.init = function (config) {
logger = config.logger;
hook.onModuleLoad('superagent', exports.instrument);
};
// This instruments the Request object exported by superagent. The superagent library uses Node.js' http/https/http2
// modules under the hood and thus would not need custom instrumentation to capture HTTP exit spans. But: It's Request
// object is a custom thenable, and async_hooks currently does not propagate the async context for those (only for
// actual promise instances).
//
// Thus, when used with async/await – that is, using a pattern like
// const response = superagent.get('http://example.com');
// we will have lost the async context in Request#then.
//
// To work around that issue, we attach the async context that is active when _creating_ the request and restore it in
// Request#then.
/**
* @param {import('superagent')} superagent
*/
exports.instrument = function instrument(superagent) {
// @ts-ignore
const OriginalRequest = superagent.Request;
if (!OriginalRequest || typeof OriginalRequest !== 'function') {
logger.debug('Failed to instrument superagent. The provided object has no function named "Request".');
return;
}
if (OriginalRequest.__in) {
logger.debug('Attempted duplicated instrumentation of superagent. The provided object is already instrumented.');
return;
}
// Instrument the superagent.Request constructor function to attach the async context to the request object.
// @ts-ignore
superagent.Request = function InstrumentedRequest() {
const request = new (Function.prototype.bind.apply(
OriginalRequest,
[null].concat(Array.prototype.slice.call(arguments))
))();
request.prototype = OriginalRequest.prototype;
// attach async context to request object so it can be retrieved when request then is called.
request.__inctx = cls.getAsyncContext();
return request;
};
// @ts-ignore
superagent.Request.__in = true;
// @ts-ignore
superagent.Request.prototype = OriginalRequest.prototype;
// @ts-ignore
shimmer.wrap(superagent.Request.prototype, 'then', instrumentThen);
};
/**
* @param {*} originalThen
*/
function instrumentThen(originalThen) {
return function instrumentedThen() {
if (!this.__inctx) {
return originalThen.apply(this, arguments);
} else {
const originalThis = this;
const originalArgs = new Array(arguments.length);
for (let i = 0; i < arguments.length; i++) {
originalArgs[i] = arguments[i];
}
return cls.runInAsyncContext(this.__inctx, () => originalThen.apply(originalThis, originalArgs));
}
};
}
exports.activate = function () {
// This instrumentation does not record spans on its own, it just helps propagating the async context. Thus the
// instrumentation is always on and cannot be activted or deactivated.
};
exports.deactivate = function () {
// This instrumentation does not record spans on its own, it just helps propagating the async context. Thus the
// instrumentation is always on and cannot be activted or deactivated.
};