UNPKG

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
/** * 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. */ 'use strict'; 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 };