ca-apm-probe
Version:
CA APM Node.js Agent monitors real-time health and performance of Node.js applications
231 lines (191 loc) • 7.1 kB
JavaScript
/**
* Copyright (c) 2015 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 path = require('path');
var agent = require('../agent');
var proxy = require('../proxy');
var bagent = require('../browser-agent');
var util = require('util');
var logger = require("../logger.js");
var targetModule = new Object;
var eventNameFormatted = 'route.handler';
var { baSnippet, isResponseDecEnabled, isAutoInjectionEnabled, isCorGuidEnabled, maxCookieExpInMs, allowedResContentType } = bagent.initializeBrowserAgentParameters();
module.exports = function (fastify) {
targetModule.fastify = fastify;
targetModule.reply = findReplyjs(targetModule.fastify, require);
targetModule.kReplyHeaders = findjs(targetModule.fastify, require, 'symbols.js')
proxy.before(targetModule.reply.prototype, 'send', function (obj, args, storage) {
if (isResponseDecEnabled && isAutoInjectionEnabled && obj.raw._headerSent === false) {
var contentType = obj[targetModule.kReplyHeaders.kReplyHeaders]['content-type'];
if(!contentType){
contentType = 'text/html; charset=utf-8';
obj[targetModule.kReplyHeaders.kReplyHeaders]['content-type'] = contentType;
}
if (args[0] && contentType.includes('text/html')) {
var originalHeaders = args[0];
if(typeof originalHeaders !== 'string'){
var actualBody = obj.serialize(originalHeaders);
if(!bagent.validateRespIfJson(actualBody)){
originalHeaders = actualBody;
}
}
args[0] = bagent.prepareScript(storage.get('ctx')) + originalHeaders;
}
}
});
const wrappedFastify = wrapConstructor(fastify, proxyFastifyFunction);
wrappedFastify.fastify = fastify.fastify
if (fastify.fastify) {
wrappedFastify.fastify = wrappedFastify
}
if (fastify.default) {
wrappedFastify.default = wrappedFastify
}
return wrappedFastify;
};
function proxyFastifyFunction() {
return function proxyFastifyFunction() {
const wrappedFastify = targetModule.fastify.apply(this, arguments)
proxy.before(wrappedFastify, 'addHook', handleHook);
wrappedFastify.addHook('onRoute', (routeOptions) => {
if (!routeOptions.handler) {
return
}
proxy.before(routeOptions, 'handler', routeOptionHandler);
});
proxy.around(wrappedFastify, 'decorate', decorateHook, decorateHook);
return wrappedFastify;
}
}
function wrapConstructor(module, wrappedConstructor, args) {
if (!module) {
return module
}
if (typeof wrappedConstructor === 'function') {
wrappedConstructor = {
wrapper: wrappedConstructor
}
}
const name = String(!module || module === true ? module : module.name || 'undefined');
const wrappedConstructorArgs = [this, module, name];
if (args && args.length) {
wrappedConstructorArgs.push.apply(wrappedConstructorArgs, args)
}
let wrappedModule = wrappedConstructor.wrapper.apply(null, wrappedConstructorArgs)
if (wrappedModule && wrappedModule !== module) {
return wrappedModule;
}
return module;
}
function handleHook(obj, args, storage) {
// var path = obj.path;
var req = args[0];
var res = args[1];
}
function routeOptionHandler(obj, args, storage) {
if (agent.paused) return;
var request = args[0];
var response = args[1];
if (request && request.raw) {
request = request.raw;
}
if (response && response.raw) {
response = response.raw;
}
var version = obj.version;
var ctx = storage.get('ctx') || request.__CA_ctx;
if (logger.isDebug()) {
if (ctx) {
logger.debug('%s[%d %d %d]: path - %s', eventNameFormatted, ctx.txid, ctx.lane, ctx.evtid, path);
}
else {
logger.debug('%s - no context', eventNameFormatted);
logger.debug((new Error('No context')).stack);
}
}
ctx = agent.asynchEventStart(ctx, eventNameFormatted, {
route: request.url,
http_method: request.method,
version: version
});
storage.set('ctx', ctx);
var errorObject = new Object();
var end_save = response.end;
response.end = function () {
if (response.finished === false && ctx) {
ctx = agent.asynchEventDone(ctx, eventNameFormatted, null,
errorObject);
if (ctx) {
agent.asynchEventFinish(ctx);
}
}
end_save.apply(response, arguments);
};
}
function decorateHook(obj, args, storage) {
var path = obj.path;
var req = args[0];
var res = args[1];
}
function findReplyjs(express, require) {
var cache = require.cache;
for (var key in cache) {
var candidate = cache[key];
if (candidate.exports !== express) {
continue;
}
var dirname = path.dirname(candidate.filename);
var filename;
filename = path.join(dirname, 'lib', 'reply.js');
try {
return require(filename);
} catch (e) {
return null;
}
}
}
function findjs(express, require, javascript) {
var cache = require.cache;
for (var key in cache) {
var candidate = cache[key];
if (candidate.exports !== express) {
continue;
}
var dirname = path.dirname(candidate.filename);
var filename;
filename = path.join(dirname, 'lib', javascript);
try {
return require(filename);
} catch (e) {
return null;
}
}
}
function getMethodsWithProbes() {
if (!targetModule.methodMap) {
var mt = new Object;
mt[0] = 'route#handler';
targetModule.methodMap = mt;
}
return targetModule.methodMap;
}
function instrument(methodMap) {
targetModule.methodMap = methodMap;
}
module.exports.getMethodsWithProbes = getMethodsWithProbes;
module.exports.instrument = instrument.bind(module);