UNPKG

apminsight

Version:

monitor nodejs applications

662 lines (638 loc) 20.6 kB
var constants = require("./../constants"); var utils = require("./../util/utils"); var apmwrapper = require("./wrapper"); var http2Info = require("./http2"); var url = require("url"); var logger = require("./../util/logger"); var componentName = "NODEJS-CORE"; var MODULES_INFO = { events: [ { functionName: "prototype.addListener", wrapper: registerEvent, override: function (actualModule, wrappedFunc) { actualModule.prototype.addListener = wrappedFunc; actualModule.prototype.on = wrappedFunc; }, component: componentName } ], http: [ { functionName: "createServer", wrapper: createServerWrap, component: componentName }, { functionName: "Server", wrapper: createServerWrap, component: componentName }, { functionName: "request", component: "HTTP", wrapper: handleWebRequest, extractInfo: getUrl }, { functionName: "get", component: "HTTP", wrapper: handleWebRequest, extractInfo: getUrl } ], https: [ { functionName: "createServer", wrapper: createServerWrap, component: componentName }, { functionName: "Server", wrapper: createServerWrap, component: componentName }, { functionName: "request", component: "HTTP", wrapper: handleWebRequest, extractInfo: getUrl }, { functionName: "get", component: "HTTP", wrapper: handleWebRequest, extractInfo: getUrl } ], http2: [ { functionName: "createSecureServer", wrapper: http2ServerWrap, component: componentName }, { functionName: [ "Http2ServerRequest.prototype.setTimeout", "Http2ServerResponse.prototype.end", "Http2ServerResponse.prototype.setTimeout", "Http2ServerResponse.prototype.createPushResponse" ], component: componentName } ], net: [ { functionName: [ "Server.prototype.getConnections", "Server.prototype.close", "Socket.prototype.setTimeout", "Socket.prototype.write", "Socket.prototype.connect", "createConnection" ], component: componentName }, { functionName: "Server.prototype.listen", wrapper: handleListeningPort, component: componentName } ], fs: [ { functionName: [ "access", "ftruncate", "mkdir", "mkdtemp", "readdir", "readFile", "readlink", "realpath", "truncate", "appendFile", "symlink", "copyFile", "open", "writeFile", "chmod", "fchmod", "lchmod", "link", "rename", "chown", "fchown", "lchown", "futimes", "utimes", "close", "exists", "fdatasync", "fstat", "fsync", "lstat", "stat", "rmdir", "unlink", "read", "write", "realpath.native" ], component: componentName } ], dns: [ { functionName: [ "lookupService", "lookup", "resolve", "resolve4", "resolve6", "resolveCname", "resolveMx", "resolveNaptr", "resolveNs", "resolvePtr", "resolveSoa", "resolveSrv", "resolveTxt", "resolveAny", "reverse" ], component: componentName } ], crypto: [ { functionName: [ "pbkdf2", "randomBytes", "randomFill", "generateKeyPair", "scrypt" ], component: componentName } ], domain: [ { functionName: [ "Domain.prototype.bind", "Domain.prototype.intercept" ], component: componentName } ], zlib: [ { functionName: [ "Deflate.prototype.close", "Inflate.prototype.close", "Gzip.prototype.close", "Gunzip.prototype.close", "DeflateRaw.prototype.close", "InflateRaw.prototype.close", "Unzip.prototype.close", "Deflate.prototype.flush", "Inflate.prototype.flush", "Gzip.prototype.flush", "Gunzip.prototype.flush", "DeflateRaw.prototype.flush", "InflateRaw.prototype.flush", "Unzip.prototype.flush", "Deflate.prototype.params", "deflate", "deflateRaw", "gunzip", "gzip", "inflate", "inflateRaw", "unzip" ], component: componentName } ], child_process: [ { functionName: ["exec", "execFile"], component: componentName } ], cluster: [ { functionName: ["disconnect", "Worker.prototype.send"], component: componentName } ], process: [ { functionName: "send", component: componentName }, { functionName: "nextTick", wrapper: nextTickWrapper, component: componentName } ], dgram: [ { functionName: [ "Socket.prototype.bind", "Socket.prototype.close", "Socket.prototype.send", "createSocket" ], component: componentName } ], tls: [ { functionName: ["connect", "TLSSocket.prototype.renegotiate"], component: componentName } ], stream: [ { functionName: [ "Writable.prototype.write", "Writable.prototype.end", "finished", "pipeline" ], component: componentName } ], global: [ { functionName: ["setImmediate", "setTimeout"], callBackIndex: 0, component: componentName } ], inspector: [ { functionName: ["Session.prototype.post"], component: componentName } ], worker_threads: [ { functionName: ["Worker.prototype.terminate"], component: componentName } ], readline: [ { functionName: ["Interface.prototype.question"], component: componentName } ] }; // Need to check about async_hooks modules function createServerWrap(original, funcInfo, moduleName, compName) { return function (option, listener) { if (utils.isFunction(option)) { arguments[0] = requestListener(option, moduleName, compName); } else if (utils.isFunction(listener)) { arguments[1] = requestListener(listener, moduleName, compName); } var server = original.apply(this, arguments); wrapRequestEvent(server); return server; }; } function wrapRequestEvent(server) { if (server && utils.isFunction(server.on)) { var actualOn = server.on; server.on = function (event, listener) { if (event === "request" && utils.isFunction(listener)) { arguments[1] = requestListener(listener); } return actualOn.apply(this, arguments); }; } //Hapi framework middlewares taken from server obj if (server && utils.isFunction(server.route)) { var actualRoute = server.route; server.route = function (options) { if (options && utils.isFunction(options.handler)) { var actualHandler = options.handler; var trackerName = "Hapi - Middleware - Route"; arguments[0].handler = requestListener( actualHandler, trackerName, "HAPI" ); } return actualRoute.apply(this, arguments); }; } if (server && utils.isFunction(server.ext)) { var actualExt = server.ext; server.ext = function (extPoint, handler) { if (extPoint && handler && utils.isFunction(handler)) { var actualHandler = handler; var trackerName = "Hapi - Middleware - Ext - " + extPoint; arguments[1] = requestListener( actualHandler, trackerName, "HAPI" ); } return actualExt.apply(this, arguments); }; } if (server && utils.isFunction(server.decorate)) { var actualDecor = server.decorate; server.decorate = function (type, property, handler) { if (type && property && handler && utils.isFunction(handler)) { var actualHandler = handler; var trackerName = "Hapi - Middleware - " + type + " - " + property; arguments[2] = requestListener( actualHandler, trackerName, "HAPI" ); } return actualDecor.apply(this, arguments); }; } } function http2ServerWrap(original) { return function (option, listener) { if (utils.isFunction(listener)) { arguments[1] = requestListener(listener); } var server = original.apply(this, arguments); if (server && server.on) { server.on("request", http2Info.handleHttp2Request); var actualOn = server.on; server.on = function (event, handler) { if ( (event === "stream" || event === "session") && utils.isFunction(handler) ) { arguments[1] = http2Info.handleHttp2StreamOrSession( handler, event ); } return actualOn.apply(this, arguments); }; } return server; }; } function requestListener(actualListener, tName, compName) { return function (req, res) { if (!req) { return actualListener.apply(this, arguments); } var rootTrackerName; var trackerName; var listenerName = utils.isEmpty(actualListener.name) ? "Anonymous" : actualListener.name; //Hapi framework assigns actual req object to raw, so our apminsightTxn is inside req object of raw var curTransaction = req.apminsightTxn || (req.raw && req.raw.req.apminsightTxn) || apmInsightAgentInstance.getCurTxn(); if (curTransaction) { try { if (!utils.isEmpty(tName)) { trackerName = tName + " - " + listenerName; } else { trackerName = "emit - request - " + listenerName; } var currentTracker = apmInsightAgentInstance.getCurTracker(); var trackerInfo = { trackerName: trackerName, sync: true, parent: currentTracker }; trackerInfo.component = compName ? compName : "NODEJS-CORE"; var syncOpnInfo = { txn: curTransaction, trackerInfo: trackerInfo }; return apmwrapper.invokeSyncOpn( actualListener, this, arguments, syncOpnInfo ); } catch (e) { logger.error("Error happend in the tracker : " + trackerName); throw e; } finally { apmInsightAgentInstance.clearCurContext(); } } rootTrackerName = "HTTP - Request"; if (req.method && req.url) { rootTrackerName += " - " + req.method + " - " + req.url; } rootTrackerName += " - " + listenerName; var txn = apmInsightAgentInstance.createTxn(req, res, rootTrackerName); req.apminsightTxn = txn; if (req.stream && req.stream.session) { req.stream.apminsightTxn = txn; req.stream.session.apminsightTxn = txn; } try { var result = actualListener.apply(this, arguments); apmInsightAgentInstance.endRootTracker(); return result; } catch (err) { apmInsightAgentInstance.endRootTracker(err); } finally { apmInsightAgentInstance.clearCurContext(); } }; } function handleWebRequest( actualCaller, functionInfo, moduleName, functionName ) { return function () { var curTxn = apmInsightAgentInstance.getCurTxn(); if (!curTxn || curTxn.isCompleted()) { return actualCaller.apply(this, arguments); } var parentTracker = apmInsightAgentInstance.getCurTracker(); var isWebRequest = parentTracker && parentTracker.getComponent() === "HTTP"; if (isWebRequest) { return actualCaller.apply(this, arguments); } var AsyncFuncInfo = { curTxn: curTxn, parentTracker: parentTracker, moduleName: moduleName, functionInfo: functionInfo, functionName: functionName }; checkAndInjectCrossAppRequest(arguments, curTxn); return apmwrapper.invokeAsyncOpn( actualCaller, this, arguments, AsyncFuncInfo ); }; } function handleListeningPort(actual) { return function (connectionObj) { if (!apmInsightAgentInstance.getConfig().isPortConfiguredProperly()) { var port; if (utils.isPositiveNumber(connectionObj)) { port = connectionObj; } else if (utils.isNonEmptyString(connectionObj)) { port = Number(connectionObj); } else if (utils.isObject(connectionObj) && connectionObj.port) { port = connectionObj.port; } apmInsightAgentInstance.getConfig().setApplicationPort(port); logger.info("Application port number is auto-detected as :: " + port); } return actual.apply(this, arguments); }; } function checkAndInjectCrossAppRequest(args, curTxn) { const isDistributedTracingEnabled = apmInsightAgentInstance.getConfig().isDataExporterEnabled() || utils.getGenericThreshold(curTxn.getUrl()).isDistributedTracingEnabled(); if (!isDistributedTracingEnabled) { return; } if (args && args.length > 0 && typeof args[0] === "object") { var reqInfo = args[0]; if (!reqInfo.headers) { reqInfo.headers = {}; } reqInfo.headers[constants.crossAppReqHeader] = apmInsightAgentInstance .getConfig() .getLicenseKeySuffix(); } } function validateCrossAppResponse(requestObj, tracker) { if (requestObj && typeof requestObj.on === "function") { requestObj.on("response", function (response) { if (response && response.headers) { var dtInfo = response.headers[constants.crossAppResHeaderLower]; if (utils.isNonEmptyString(dtInfo)) { try { dtInfo = JSON.parse(dtInfo); tracker.updateDtInfo(dtInfo); } catch (e) { logger.error("Error occured in parsing dtInfo."); } } } }); } } function getUrl(invoker, params, returnObj, tracker, asyncOpnInfo) { validateCrossAppResponse(returnObj, tracker); if (!params || params.length == 0) { return; } var infoParam = params[0], info = {}; if (typeof infoParam === "string") { info.url = infoParam; var parsedUrl = url.parse(infoParam); if (parsedUrl && parsedUrl.hostname) { info.host = parsedUrl.hostname; info.port = parsedUrl.port ? parsedUrl.port : asyncOpnInfo.moduleName === "http" ? 80 : 443; } if (parsedUrl && parsedUrl.pathname) { info.pathname = parsedUrl.pathname; } } else if (typeof infoParam === "object") { info.host = infoParam.host ? infoParam.host : infoParam.hostname; info.port = infoParam.port ? infoParam.port : asyncOpnInfo.moduleName === "http" ? 80 : 443; info.method = infoParam.method; info.pathname = infoParam.pathname ? infoParam.pathname : ""; info.url = infoParam.host + infoParam.path + info.pathname; } tracker.updateInfo(info); } /* eslint-disable no-unused-vars */ function registerEvent(original) { return function (event, listener) { var curTxn = apmInsightAgentInstance.getCurTxn(); if (!curTxn || curTxn.isCompleted()) { this._apmInsightAgentTxnContext = null; return original.apply(this, arguments); } this._apmInsightAgentTxnContext = curTxn; if (!this._apmEmitWrapped && utils.isFunction(this.emit)) { this.emit = setContext(this.emit); this._apmEmitWrapped = true; } return original.apply(this, arguments); }; } /* eslint-enable no-unused-vars */ function setContext(actualListener) { return function (event) { var curTxn = this._apmInsightAgentTxnContext; if (!curTxn || curTxn.isCompleted()) { return actualListener.apply(this, arguments); } const eventName = typeof event == "symbol" ? event.description : event; var trackerName = "emit - " + eventName; var trackerInfo = { trackerName: trackerName, sync: true, component: componentName }; var parentTracker = curTxn.getRootTracker(); var syncOpnInfo = { txn: curTxn, trackerInfo: trackerInfo, syncParentTracker: parentTracker }; return apmwrapper.invokeSyncOpn( actualListener, this, arguments, syncOpnInfo ); }; } function nextTickWrapper(actualNextTick) { return function (fn) { var curTxn = apmInsightAgentInstance.getCurTxn(); if (!curTxn || curTxn.isCompleted()) { return actualNextTick.apply(this, arguments); } if (!utils.isFunction(fn)) { return actualNextTick.apply(this, arguments); } var curTracker = apmInsightAgentInstance.getCurTracker(); arguments[0] = nextTickFnWrapper(arguments[0], curTxn, curTracker); return actualNextTick.apply(this, arguments); }; } function nextTickFnWrapper(fn, curTxn, curTracker) { return function () { try { apmInsightAgentInstance.setCurContext(curTxn, curTracker); return fn.apply(this, arguments); } finally { apmInsightAgentInstance.clearCurContext(); } }; } module.exports = { MODULES_INFO: MODULES_INFO, serverWrap: createServerWrap };