UNPKG

apminsight

Version:

monitor nodejs applications

412 lines (393 loc) 13.7 kB
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 };