UNPKG

ca-apm-probe

Version:

CA APM Node.js Agent monitors real-time health and performance of Node.js applications

272 lines (218 loc) 9.51 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. */ // probe for mongodb driver 2.1.* 'use strict'; var agent = require('../../../agent'); var proxy = require('../../../proxy'); var util = require('util'); var instrUtil = require("../../../utils/instrument-util"); var traceUtil = require("../../../utils/trace-util"); var config = require('../../../configdata').getConfigData(); var logger = require("../../../logger.js"); var DEFAULT_DB = 'default_db'; var DEFAULT_COLLECTION_NAME = 'unknown_collection'; var DEFAULT_ERROR_CLASS_NAME = 'MongoDBError'; var EVENT_CLASS_NAME = 'mongodbCursor'; var debug = logger.isDebug(); var reportRawQuery = (config.mongodb.reportRawQuery == undefined) ? false : config.mongodb.reportRawQuery; var targetModule = new Object; module.exports = function (mongodb) { targetModule.mongodb = mongodb; targetModule.methodMap = getMethodsWithProbes(); var methodMap = targetModule.methodMap; // probe for cursor operations which supports Promise and have common tracing requirements ['toArray', 'next'].forEach(function (op) { var probes = getCursorProbes(op); proxy.around(mongodb.Cursor.prototype, op, probes[0], probes[1]); }); function getCursorProbes(operation) { var probes = []; probes[0] = function caMongodbCursorBeforeHook(cursor, args, storage) { // callback index is supposed to be static - 0, don't pay cost of finding it at run time var callbackIndex = 0; var ctx = storage.get('ctx'); if (shouldSkipInstrumentation(operation)) { return; } var eventNameFormatted = EVENT_CLASS_NAME + '.' + operation; var eventArgs = {}; updateEventArgs(eventArgs, cursor); if (debug) { logDiagnosticInfo(eventNameFormatted + ' start', ctx); } // send start trace event ctx = agent.asynchEventStart(ctx, eventNameFormatted, eventArgs); storage.set('ctx', ctx); if (isCallbackAFunction(args, callbackIndex)) { ctx.callbackIndex = callbackIndex; instrumentCallback(args, callbackIndex, storage, cursor, ctx, eventNameFormatted); } else { ctx.callbackIndex = -1; } }; probes[1] = function caMongodbCursorAfterHook(cursor, args, promise, storage) { registerPromiseHandlers(storage, cursor, promise, operation); }; return probes; } proxy.before(mongodb.Cursor.prototype, 'forEach', function caMongodbBeforeHook(cursor, args, storage) { var operation = 'forEach'; var callbackIndex = 1; var ctx = storage.get('ctx'); if (!isCallbackAFunction(args, callbackIndex) || shouldSkipInstrumentation(operation)) { return; } var eventNameFormatted = EVENT_CLASS_NAME + '.' + operation; var eventArgs = {}; updateEventArgs(eventArgs, cursor); if (debug) { logDiagnosticInfo(eventNameFormatted + ' start', ctx); } // send start trace event ctx = agent.asynchEventStart(ctx, eventNameFormatted, eventArgs); storage.set('ctx', ctx); //ctx.mongodbOpReentry = operation; instrumentCallback(args, callbackIndex, storage, cursor, ctx, eventNameFormatted); }); function shouldSkipInstrumentation(funcName) { return (methodMap[0] === "skip_instrument"); } function shouldSkipTracing(callbackIndex, operation, ctx) { // if callaback in not available or this operation is not selected, or // already tracing another mongodb operation in call stack (reentry==true), skip tracing return (callbackIndex == -1 || shouldSkipInstrumentation(operation) || (ctx && ctx.mongodbOpReentry)); } function isCallbackAFunction(args, callbackIndex) { return (args[callbackIndex] && typeof(args[callbackIndex]) === 'function'); } function registerPromiseHandlers(storage, cursor, promise, operation) { var ctx = storage.get('ctx'); if (ctx) { if (ctx.callbackIndex == -1 && !shouldSkipInstrumentation(operation)) { var eventNameFormatted = EVENT_CLASS_NAME + '.' + operation; promise.then(getPromiseSuccessHandler(storage, ctx, cursor, eventNameFormatted), getPromiseRejectionHandler(storage, ctx, cursor, eventNameFormatted)); } } }; } function getPromiseSuccessHandler(storage, ctx, cursor, eventNameFormatted) { return function caMongodbPromiseSuccessHandler(val) { if (debug) { logDiagnosticInfo(eventNameFormatted + ' done - promise resolved successfully', ctx); } if (ctx) { var eventArgs = {}; if (cursor) { if (cursor.server && cursor.server.s.serverDetails) { eventArgs.dbServer = cursor.server.s.serverDetails.name; } } ctx = agent.asynchEventDone(ctx, eventNameFormatted, eventArgs, null); storage.set('ctx', ctx); agent.asynchEventFinish(ctx); } }; } function getPromiseRejectionHandler(storage, ctx, cursor, eventNameFormatted) { return function caMongodbPromiseRejectionHandler(val) { if (debug) { logDiagnosticInfo(eventNameFormatted + ' done - promise rejected', ctx); } if (ctx) { var eventArgs = {}; var errorObject = traceUtil.getFormattedErrorObject(val) || traceUtil.createErrorObject(DEFAULT_ERROR_CLASS_NAME, val); if (cursor) { if (cursor.server && cursor.server.s.serverDetails) { eventArgs.dbServer = cursor.server.s.serverDetails.name; } } ctx = agent.asynchEventDone(ctx, eventNameFormatted, eventArgs, errorObject); storage.set('ctx', ctx); agent.asynchEventFinish(ctx); } }; } function instrumentCallback(args, callbackIndex, storage, cursor, ctx, eventNameFormatted) { proxy.callback(args, callbackIndex, function (obj, args, storage) { if (debug) { logDiagnosticInfo(eventNameFormatted + ' done - callback start ', ctx); } if (ctx) { var eventArgs = {}; var errorObject = traceUtil.checkCallbackArgsForError(args, DEFAULT_ERROR_CLASS_NAME); if (cursor) { if (cursor.server && cursor.server.s.serverDetails) { eventArgs.dbServer = cursor.server.s.serverDetails.name; } // FUTURE WORK: confirm cursor id is useful //if (cursor.cursorState) { // eventArgs.cursorId = cursor.cursorState.cursorId; //} } ctx = agent.asynchEventDone(ctx, eventNameFormatted, eventArgs, errorObject); storage.set('ctx', ctx); } }, function (obj, args) { if (debug) { logDiagnosticInfo(eventNameFormatted + ' finish - callback finish', ctx); } if (ctx) { agent.asynchEventFinish(ctx); } }); } function updateEventArgs(eventArgs, cursor) { var ns = cursor.s.ns; if (ns) { var info = ns.split('.'); if (info) { eventArgs.dbName = info[0] || DEFAULT_DB; eventArgs.collectionName = info[1] || DEFAULT_COLLECTION_NAME; } } if (reportRawQuery) { var cmd = cursor.s.cmd; if (cmd) { eventArgs.query = traceUtil.formatEventArgValueObject(cmd.query); } } } function logDiagnosticInfo(eventName, ctx) { if (ctx != null) { logger.debug('event name: %s , current context: [%d %d %d]', eventName, ctx.txid, ctx.lane, ctx.evtid); } else { logger.debug('event name: %s - no context', eventName); logger.debug((new Error('No context')).stack); } } function getMethodsWithProbes() { if (!targetModule.methodMap) { var mt = new Object; mt[0] = 'mongodb#all'; targetModule.methodMap = mt; } return targetModule.methodMap; } function instrument(methodMap) { targetModule.methodMap = methodMap; } module.exports.getMethodsWithProbes = getMethodsWithProbes; module.exports.instrument = instrument.bind(module);