ca-apm-probe
Version:
CA APM Node.js Agent monitors real-time health and performance of Node.js applications
277 lines (226 loc) • 11.1 kB
JavaScript
/**
* Copyright (c) 2016 CA. All rights reserved.
*
* This software and all information contained therein is confidential and proprietary and
* shall not be duplicated, used, disclosed or disseminated in any way except as authorized
* by the applicable license agreement, without the express written permission of CA. All
* authorized reproductions must be marked with this language.
*
* EXCEPT AS SET FORTH IN THE APPLICABLE LICENSE AGREEMENT, TO THE EXTENT
* PERMITTED BY APPLICABLE LAW, CA PROVIDES THIS SOFTWARE WITHOUT WARRANTY
* OF ANY KIND, INCLUDING WITHOUT LIMITATION, ANY IMPLIED WARRANTIES OF
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL CA BE
* LIABLE TO THE END USER OR ANY THIRD PARTY FOR ANY LOSS OR DAMAGE, DIRECT OR
* INDIRECT, FROM THE USE OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, LOST
* PROFITS, BUSINESS INTERRUPTION, GOODWILL, OR LOST DATA, EVEN IF CA IS
* EXPRESSLY ADVISED OF SUCH LOSS OR DAMAGE.
*/
;
var agent = require('../agent');
var proxy = require('../proxy');
var logger = require("../logger.js");
var traceUtil = require("./trace-util");
var util = require('util');
var DEFAULT_EVENT_CLASS_NAME = 'Object';
/**
* Find index of callback function in arguments array.
* @method
* @param {array} args array of arguments
* @returns {number}
*/
var findCallbackIndex = function (args) {
var callbackIndex = args.length - 1;
while (callbackIndex >= 0 && typeof(args[callbackIndex]) !== 'function') {
callbackIndex -= 1;
}
return callbackIndex;
}
var findClassName = function (object) {
return object.__ca_apm_probe_mod_id__ || (object.constructor.name && object.constructor.name.toLowerCase()) || DEFAULT_EVENT_CLASS_NAME;
}
/**
* Instrument callback based asynchronous non-blocking functions
* @method
* @param {object} object Object selected for instrumentation
* @param {array} fnNames Names of functions to instrument
* @param {string} eventClassName Class name used for creating trace events
* @param {function} startEventParamsCallback Callback function to fetch parameters for start trace event.
* Arguments passed to this callback are:
* -obj object being traced
* -args arguments passed to function under execution
* @param finishEventParamsCallback Callback function to fetch parameters for finish trace event
* Arguments passed to this callback are:
* -obj callback object being traced
* -args arguments passed to callback. Usually non null args[0] indicates error, args[1] refers to returned value
*/
var instrumentAsync = function (object, fnNames, eventClassName, startEventParamsCallback, finishEventParamsCallback) {
var eventClassName = eventClassName || findClassName(object);
var fnNames = fnNames || [];
if (!(fnNames instanceof Array)) {
fnNames = [fnNames];
}
fnNames.forEach(function (fnName) {
var eventNameFormatted = util.format('%s.%s', eventClassName, fnName);
proxy.before(object, fnName, function (obj, args, storage) {
var callbackIndex = findCallbackIndex(args);
// if callback in not available, skip tracing
if (callbackIndex == -1) {
return;
}
var ctx = storage.get('ctx');
var eventArgs = startEventParamsCallback ? startEventParamsCallback(obj, args) : {};
if (logger.isDebug()) {
logger.debug('%s - execution started', eventNameFormatted);
if (!ctx) {
logger.debug('%s - no context', eventNameFormatted);
}
}
//on start of execution, send 'start-trace' event
ctx = agent.asynchEventStart(ctx, eventNameFormatted, eventArgs);
storage.set('ctx', ctx);
proxy.callback(args, -1, function (obj, args, storage) {
if (ctx != null) {
if (logger.isDebug()) {
logger.debug('%s - execution finished', eventNameFormatted);
}
var eventArgs = finishEventParamsCallback ? finishEventParamsCallback(obj, args) : {};
var errorObject = traceUtil.checkCallbackArgsForError(args);
//on callback invocation, send 'end-trace' event
ctx = agent.asynchEventDone(ctx, eventNameFormatted, eventArgs, errorObject);
storage.set('ctx', ctx);
}
}, function (obj, args) {
agent.asynchEventFinish(ctx);
});
});
});
};
/**
* Instrument promise based asynchronous non-blocking functions
* @method
* @param {object} object Object selected for instrumentation
* @param {array} fnNames Names of functions to instrument
* @param {string} eventClassName Class name used for creating trace events
* @param {function} startEventParamsCallback Callback function to fetch parameters for start trace event.
* Arguments passed to this callback are:
* -obj object being traced
* -args arguments passed to function under execution
* @param finishEventParamsCallback Callback function to fetch parameters for finish trace event
* Arguments passed to this callback are:
* -val result of async operation when promise is successfully resolved
*/
var instrumentAsyncPromise = function (object, fnNames, eventClassName, startEventParamsCallback, finishEventParamsCallback) {
var eventClassName = eventClassName || findClassName(object);
var fnNames = fnNames || [];
if (!(fnNames instanceof Array)) {
fnNames = [fnNames];
}
fnNames.forEach(function (fnName) {
var eventNameFormatted = util.format('%s.%s', eventClassName, fnName);
proxy.around(object, fnName, function (obj, args, storage) {
var ctx = storage.get('ctx');
var eventArgs = startEventParamsCallback ? startEventParamsCallback(obj, args) : {};
if (logger.isDebug()) {
logger.debug('%s - execution started', eventNameFormatted);
if (!ctx) {
logger.debug('%s - no context', eventNameFormatted);
}
}
//on start of execution, send 'start-trace' event
ctx = agent.asynchEventStart(ctx, eventNameFormatted, eventArgs);
storage.set('ctx', ctx);
},
function (obj, args, rval, storage) {
var promise = rval;
var ctx = storage.get('ctx');
if (ctx) {
// register promise handlers
promise.then(getPromiseSuccessHandler(storage, ctx, eventNameFormatted, finishEventParamsCallback),
getPromiseRejectionHandler(storage, ctx, eventNameFormatted, finishEventParamsCallback));
}
});
});
};
function getPromiseSuccessHandler(storage, ctx, eventNameFormatted, finishEventParamsCallback) {
return function caMongodbPromiseSuccessHandler(value) {
if (logger.isDebug()) {
logger.debug('%s - execution finished - promise resolved successfully', eventNameFormatted);
}
if (ctx) {
var eventArgs = finishEventParamsCallback ? finishEventParamsCallback(value) : {};
ctx = agent.asynchEventDone(ctx, eventNameFormatted, eventArgs);
storage.set('ctx', ctx);
agent.asynchEventFinish(ctx);
}
};
}
function getPromiseRejectionHandler(storage, ctx, eventNameFormatted, finishEventParamsCallback) {
return function caMongodbPromiseRejectionHandler(value) {
if (logger.isDebug()) {
logger.debug('%s - execution finished - promise rejected', eventNameFormatted);
}
if (ctx) {
var eventArgs = {};
var errorObject = traceUtil.getFormattedErrorObject(value);
ctx = agent.asynchEventDone(ctx, eventNameFormatted, eventArgs, errorObject);
storage.set('ctx', ctx);
agent.asynchEventFinish(ctx);
}
};
}
/**
* Instrument synchronous blocking functions
* @method
* @param {object} object selected for instrumentation
* @param {array} fnNames Names of functions to instrument
* @param {string} eventClassName Class name used for creating trace events
* @param {function} startEventParamsCallback Callback function to fetch parameters for start trace event.
* Arguments passed to this callback are:
* -obj object being traced
* -args arguments passed to function under execution
* @param finishEventParamsCallback Callback function to fetch parameters for finish trace event
* Arguments passed to this callback are:
* -obj callback object being traced
* -args arguments passed to function under execution
* -rval value returned by function
*/
var instrumentSync = function (object, fnNames, eventClassName, startEventParamsCallback, finishEventParamsCallback) {
var eventClassName = eventClassName || findClassName(object);
var fnNames = fnNames || [];
if (!(fnNames instanceof Array)) {
fnNames = [fnNames];
}
fnNames.forEach(function (fnName) {
var eventNameFormatted = util.format('%s.%s', eventClassName, fnName);
proxy.around(object, fnName, function (obj, args, storage) {
var ctx = storage.get('ctx');
var eventArgs = startEventParamsCallback ? startEventParamsCallback(obj, args) : {};
if (logger.isDebug()) {
logger.debug('%s - execution started', eventNameFormatted);
if (!ctx) {
logger.debug('%s - no context', eventNameFormatted);
}
}
//on start of execution, send 'start-trace' event
ctx = agent.asynchEventStart(ctx, eventNameFormatted, eventArgs);
storage.set('ctx', ctx);
},
function (obj, args, rval, storage) {
var ctx = storage.get('ctx');
if (ctx != null) {
if (logger.isDebug()) {
logger.debug('%s - execution finished', eventNameFormatted);
}
var eventArgs = finishEventParamsCallback ? finishEventParamsCallback(obj, args, rval) : {};
ctx = agent.asynchEventDone(ctx, eventNameFormatted, eventArgs);
agent.asynchEventFinish(ctx);
}
});
});
};
module.exports = {
findCallbackIndex: findCallbackIndex,
instrumentAsync: instrumentAsync,
instrumentAsyncPromise: instrumentAsyncPromise,
instrumentSync: instrumentSync
};