apminsight
Version:
monitor nodejs applications
412 lines (393 loc) • 13.7 kB
JavaScript
var utils = require("./../util/utils");
var logger = require("./../util/logger");
var constants = require("./../constants");
var metricstore = require("./../metrics/metricstore");
function checkAndWrapFunction(
actualModule,
functionInfo,
moduleName,
functionName
) {
if (!actualModule) {
return;
}
if (Array.isArray(functionName)) {
functionName.forEach(function (eachFunctionName) {
checkAndWrapFunction(
actualModule,
functionInfo,
moduleName,
eachFunctionName
);
});
return;
}
var qualifiedFuncName = functionName.split(".");
var original = actualModule[qualifiedFuncName[0]];
if (utils.isEmpty(original)) {
logger.info(functionName + " is undefined in module::" + moduleName);
return;
}
for (var index = 1; index < qualifiedFuncName.length; index++) {
original = original[qualifiedFuncName[index]];
if (utils.isEmpty(original)) {
logger.info(
functionName + " is undefined in module::" + moduleName
);
return;
}
}
if (!utils.isFunction(original)) {
logger.info(
functionName + " is not a function in module::" + moduleName
);
return;
}
logger.info("Instrumenting function ::" + functionName);
wrapAndOverride(
actualModule,
original,
functionInfo,
moduleName,
functionName
);
}
function wrapAndOverride(
actualModule,
originalFunc,
functionInfo,
moduleName,
functionName
) {
var wrappedFunc = wrap(
originalFunc,
functionInfo,
moduleName,
functionName
);
if (functionInfo.override) {
functionInfo.override(actualModule, wrappedFunc);
return;
}
var qualifiedName = functionName.split(".");
if (!qualifiedName) {
return;
}
if (qualifiedName.length === 1) {
actualModule[functionName] = wrappedFunc;
} else if (qualifiedName.length === 2) {
actualModule[qualifiedName[0]][qualifiedName[1]] = wrappedFunc;
} else if (qualifiedName.length === 3) {
actualModule[qualifiedName[0]][qualifiedName[1]][qualifiedName[2]] =
wrappedFunc;
}
}
function wrap(original, functionInfo, moduleName, functionName) {
if (functionInfo.wrapper) {
return functionInfo.wrapper(
original,
functionInfo,
moduleName,
functionName
);
} else {
return wrapCaller(original, functionInfo, moduleName, functionName);
}
}
function wrapCaller(actualCaller, functionInfo, moduleName, functionName) {
return function () {
var curTxn = apmInsightAgentInstance.getCurTxn();
var parentTracker = apmInsightAgentInstance.getCurTracker();
if (!curTxn || curTxn.isCompleted()) {
return actualCaller.apply(this, arguments);
}
if (functionInfo.sync) {
var trackerName =
moduleName + "." + (functionInfo.trackerName || functionName);
var trackerInfo = {
trackerName: trackerName,
component: functionInfo.component,
sync: true
};
var syncOpnInfo = {
txn: curTxn,
syncParentTracker: parentTracker,
trackerInfo: trackerInfo
};
return invokeSyncOpn(actualCaller, this, arguments, syncOpnInfo);
}
var asynOpnInfo = {
moduleName: moduleName,
functionInfo: functionInfo,
functionName: functionName,
curTxn: curTxn,
parentTracker: parentTracker
};
return invokeAsyncOpn(actualCaller, this, arguments, asynOpnInfo);
};
}
function invokeAsyncOpn(actualCaller, obj, args, asynOpnInfo) {
var result;
var trackerName;
if (asynOpnInfo.functionInfo.completeTrackerName) {
trackerName = asynOpnInfo.functionInfo.completeTrackerName;
} else {
trackerName =
asynOpnInfo.moduleName +
"." +
(asynOpnInfo.functionInfo.trackerName || asynOpnInfo.functionName);
}
var callBackIndex = getCallBackIndex(
args,
asynOpnInfo.functionInfo.callBackIndex
);
var trackerInfo = {
trackerName: trackerName,
component: asynOpnInfo.functionInfo.component,
parent: asynOpnInfo.parentTracker
};
if (asynOpnInfo.functionInfo.trackerType == constants.dbTracker) {
trackerInfo.isDbTracker = true;
} else if (asynOpnInfo.functionInfo.trackerType == constants.esTracker) {
trackerInfo.isEsTracker = true;
}
var track = apmInsightAgentInstance.createTracker(trackerInfo);
try {
if (callBackIndex >= 0 && callBackIndex < args.length) {
args[callBackIndex] = wrapCallBack(
args[callBackIndex],
track,
asynOpnInfo.curTxn
);
result = actualCaller.apply(obj, args);
} else {
result = actualCaller.apply(obj, args);
result = checkAndWrapPromise(result, asynOpnInfo.curTxn, track);
}
} catch (e) {
apmInsightAgentInstance.handleErrorTracker(track, e);
logger.error(
"Error occured in sync opn : " +
trackerInfo.trackerName +
" :: " +
e
);
throw e;
} finally {
apmInsightAgentInstance.setCurTracker(asynOpnInfo.parentTracker);
}
if (track && asynOpnInfo.functionInfo.extractInfo) {
asynOpnInfo.functionInfo.extractInfo(
obj,
args,
result,
track,
asynOpnInfo
);
}
return result;
}
function checkAndWrapPromise(promise, txn, tracker) {
if (!tracker || !promise || !utils.isFunction(promise.then)) {
return promise;
}
var actualThen = promise.then;
promise.then = function promiseWrapper() {
var wrappedArgs = Array.prototype.map.call(
arguments,
function (handler) {
if (utils.isFunction(handler)) {
return wrapPromiseHandler(handler, txn, tracker);
}
return handler;
}
);
return actualThen.apply(this, wrappedArgs);
};
return promise;
}
function wrapPromiseHandler(actualHandler, txn, tracker) {
return function () {
var activeTxn = apmInsightAgentInstance.getCurTxn();
var activeTracker = apmInsightAgentInstance.getCurTracker();
txn = activeTxn ? activeTxn : txn;
if (!txn || txn.isCompleted()) {
return actualHandler.apply(this, arguments);
}
checkAndMarkError(tracker, txn, arguments);
try {
apmInsightAgentInstance.setCurContext(txn, tracker);
return actualHandler.apply(this, arguments);
} catch (err) {
logger.error("Error occured on wrapPromiseHandler.");
throw err;
} finally {
apmInsightAgentInstance.setCurContext(activeTxn, activeTracker);
}
};
}
function wrapCallBack(actualCallBack, curTracker, txn) {
return function () {
return checkAndInvokeCallback(
this,
arguments,
actualCallBack,
txn,
curTracker
);
};
}
function checkAndInvokeCallback(obj, args, actualCallBack, txn, curTracker) {
var info = {};
try {
if (args && args[0]) {
if (args[0].statusCode) {
info.statusCode = args[0].statusCode;
if (
utils.isPositiveNumber(args[0].statusCode) &&
args[0].statusCode >= 400 &&
txn
) {
curTracker.setError(new Error(args[0].statusCode), txn);
}
} else if (args[0] instanceof Error) {
var curTrackerParent = curTracker._parent;
curTrackerParent.setError(args[0], txn);
var txnMetric = metricstore.getWebTxnMetric();
if (txnMetric[txn.getUrl()] && curTrackerParent &&curTrackerParent._error) {
var matchedTxn = txnMetric[txn.getUrl()];
matchedTxn.checkAndAddError(
curTrackerParent._error.getType()
);
if (!utils.isEmpty(curTrackerParent.getComponent())) {
if (
curTrackerParent.getComponent() &&
curTrackerParent.getInfo().host &&
curTrackerParent.getInfo().port
) {
var compName =
curTrackerParent.getComponent() +
curTrackerParent.getInfo().host +
curTrackerParent.getInfo().port;
if (matchedTxn.getExtCompInfo()[compName]) {
matchedTxn.getExtCompInfo()[compName]
._errorCount++;
if (
matchedTxn.getExtCompInfo()[compName]
._count > 0
) {
matchedTxn.getExtCompInfo()[compName]
._count--;
}
}
}
}
txnMetric[txn.getUrl()] = matchedTxn;
}
}
if (args[0].method) {
info.method = args[0].method;
}
curTracker.updateInfo(info);
}
} catch (err) {
logger.error(
"Error while checking the argument of call back for error info :: " +
err
);
}
if (!txn || txn.isCompleted()) {
return actualCallBack.apply(obj, args);
}
var CallbackName = utils.isEmpty(actualCallBack.name)
? "Anonymous"
: actualCallBack.name;
var trackerInfo = { trackerName: "Callback:" + CallbackName, sync: true };
trackerInfo.component = curTracker.component
? curTracker.component
: "NODEJS-CORE";
var syncOpnInfo = {
txn: txn,
asyncParentTracker: curTracker,
trackerInfo: trackerInfo
};
invokeSyncOpn(actualCallBack, obj, args, syncOpnInfo);
}
function invokeSyncOpn(handler, obj, args, syncOpnInfo) {
try {
checkAndMarkError(
syncOpnInfo.asyncParentTracker,
syncOpnInfo.txn,
args
);
var prevTxn = apmInsightAgentInstance.getCurTxn();
var prevTracker = apmInsightAgentInstance.getCurTracker();
var parentTracker = syncOpnInfo.syncParentTracker || syncOpnInfo.asyncParentTracker || prevTracker;
apmInsightAgentInstance.setCurContext(syncOpnInfo.txn, parentTracker);
syncOpnInfo.trackerInfo.parent = parentTracker;
var cbtracker = apmInsightAgentInstance.createTracker(
syncOpnInfo.trackerInfo
);
var result = handler.apply(obj, args);
apmInsightAgentInstance.endTracker(cbtracker);
return result;
} catch (e) {
apmInsightAgentInstance.handleErrorTracker(cbtracker, e);
let trackerName = "unknown tracker";
if (syncOpnInfo && syncOpnInfo.trackerInfo && syncOpnInfo.trackerInfo.trackerName) {
trackerName = syncOpnInfo.trackerInfo.trackerName;
}
logger.error(
"Error occured in sync opn : " +
trackerName +
" :: " +
e
);
throw e;
} finally {
apmInsightAgentInstance.setCurContext(prevTxn, prevTracker);
}
}
/* eslint-disable no-unused-vars */
function checkAndMarkError(curTracker, txn, callBackArgs) {
if (curTracker) {
//Commenting this because while instrumenting Next.js framework, we found that it internally uses fs.mkdir
//and some other similar functions of fs module which creates a lot of exceptions like EEXIST, ENOENT etc.
//Its callback args [0] contains err object if any, but it is not handled or thrown by Next.js.
//So we neednot make the tracker as error.
// if (
// callBackArgs &&
// callBackArgs[0] &&
// callBackArgs[0] instanceof Error
// ) {
// curTracker.setError(callBackArgs[0], txn);
// }
apmInsightAgentInstance.endTracker(curTracker, txn);
}
}
/* eslint-enable no-unused-vars */
function getCallBackIndex(originalArguments, callbackInfo) {
if (!originalArguments || originalArguments.length <= 0) {
return -1;
}
if (callbackInfo === undefined) {
if (utils.isFunction(originalArguments[originalArguments.length - 1])) {
return originalArguments.length - 1;
}
} else if (
originalArguments.length > callbackInfo &&
utils.isFunction(originalArguments[callbackInfo])
) {
return callbackInfo;
}
return -1;
}
module.exports = {
checkAndWrapFunction: checkAndWrapFunction,
wrapCaller: wrapCaller,
wrapCallBack: wrapCallBack,
invokeAsyncOpn: invokeAsyncOpn,
invokeSyncOpn: invokeSyncOpn,
getCallBackIndex: getCallBackIndex,
checkAndWrapPromise: checkAndWrapPromise
};