UNPKG

atatus-nodejs

Version:

Atatus APM agent for Node.js

261 lines (216 loc) 8.23 kB
'use strict'; const semver = require('semver'); const constants = require('../../../constants'); const shimmer = require('../../shimmer'); const { redactKeysFromObject } = require('../../../filters/sanitize-field-names'); const { shouldIgnoreTopic } = require('./utils'); const NAME = 'RabbitMQ' const TYPE = 'messaging'; const SUBTYPE = 'rabbitmq'; const CHANNEL_METHODS = [ 'close', 'open', 'assertQueue', 'checkQueue', 'deleteQueue', 'bindQueue', 'unbindQueue', 'assertExchange', 'checkExchange', 'deleteExchange', 'bindExchange', 'unbindExchange', 'purgeQueue', 'cancel', 'prefetch', 'recover' ] module.exports = function instrumentAMQPModel(mod, agent, { version, enabled }) { if (!enabled || !semver.satisfies(version, '>=0.5')) { return mod; } const config = agent._conf; const ins = agent._instrumentation; agent.logger.debug('shimming AMQP Channel Model'); const proto = mod.Channel.prototype; shimmer.wrap(proto, 'consume', function (orgConsume) { return function wrappedConsume() { const [topic, cb] = arguments; agent.logger.debug(`Consuming from queue: ${topic}`); if (shouldIgnoreTopic(topic, config)) { return orgConsume.apply(this, arguments) } arguments[1] = function wrappedCallback() { const cbArgs = arguments const [msg] = cbArgs const traceHeaders = msg.properties.headers || {}; const traceparent = traceHeaders['traceparent']; const tracestate = traceHeaders['tracestate']; const opts = {}; if (typeof traceparent === 'string') { opts.childOf = traceparent; } else if (traceparent instanceof Buffer) { opts.childOf = traceparent.toString('utf-8'); } if (typeof tracestate === 'string') { opts.tracestate = tracestate; } else if (tracestate instanceof Buffer) { opts.tracestate = tracestate.toString('utf-8'); } const trans = ins.startTransaction( `${NAME} RECEIVE from ${topic}`, TYPE, opts, ); const messageCtx = { queue: { name: topic } }; if ( config.captureBody === 'all' || config.captureBody === 'transactions' ) { messageCtx.body = msg.content?.toString(); } if (traceHeaders && config.captureHeaders) { // Make sure there is no sensitive data // and transform non-redacted buffers messageCtx.headers = redactKeysFromObject( traceHeaders, config.sanitizeFieldNamesRegExp, ); Object.keys(messageCtx.headers).forEach((key) => { const value = messageCtx.headers[key]; if (value instanceof Buffer) { messageCtx.headers[key] = value.toString('utf-8'); } }); } if (msg.properties.timestamp) { messageCtx.age = { ms: Date.now() - Number(msg.properties.timestamp), }; } trans.setMessageContext(messageCtx); agent.logger.debug(`Message consumed from queue: ${topic}`); let result, err; try { result = cb.apply(this, cbArgs) } catch (ex) { // Save the error for use in `finally` below, but re-throw it to // not impact code flow. err = ex; agent.logger.error(`Error consuming message from ${topic}:`, err); throw ex; } finally { if (trans) { trans.setOutcome( err ? constants.OUTCOME_FAILURE : constants.OUTCOME_SUCCESS, ); trans.end() } } return result } let result, err; try { result = orgConsume.apply(this, arguments); } catch (ex) { // Save the error for use in `finally` below, but re-throw it to // not impact code flow. err = ex; agent.logger.error(`Error consuming message from ${topic}:`, err); throw ex; } return result; } }) customMassWrap(proto, CHANNEL_METHODS, getWrapper); shimmer.wrap(proto, 'get', wrapperGet); return mod; function wrapperGet(orig) { return function wrappedGet() { const queue = arguments[0] const span = ins.createSpan( `Channel#get for ${queue}`, TYPE, SUBTYPE, 'get', { exitSpan: true }, ); // console.log(span._parentSpan.type) if (!span) { return orig.apply(this, arguments); } agent.logger.debug(`Channel#get in ${NAME}`); let result, err; try { result = orig.apply(this, arguments); } catch (ex) { // Save the error for use in `finally` below, but re-throw it to // not impact code flow. err = ex; agent.logger.error(`Error in Channel#get:`, err); throw ex; } finally { span.setOutcome( err ? constants.OUTCOME_FAILURE : constants.OUTCOME_SUCCESS, ); span.end(); } return result; } } function getWrapper(methodName) { return function wrapper(orig) { return function wrappedMethod() { let topic = '' if (methodName.endsWith("Queue") || methodName.endsWith("Exchange")) { topic = ` of ${arguments[0]}` } const span = ins.createSpan( `Channel#${methodName}${topic}`, TYPE, SUBTYPE, methodName, { exitSpan: true }, ); // console.log(span._parentSpan.type) if (!span) { return orig.apply(this, arguments); } agent.logger.debug(`Channel#${methodName} in ${NAME}`); let result, err; try { result = orig.apply(this, arguments); } catch (ex) { // Save the error for use in `finally` below, but re-throw it to // not impact code flow. err = ex; agent.logger.error(`Error in Channel#${methodName}:`, err); throw ex; } finally { span.setOutcome( err ? constants.OUTCOME_FAILURE : constants.OUTCOME_SUCCESS, ); span.end(); } return result; } } } function customMassWrap(nodule, names, getWrapper) { if (!nodule) { logger().debug( { stack: new Error().stack }, 'must provide one or more modules to patch', ); return; } if (!(names && Array.isArray(names))) { logger().debug('must provide one or more functions to wrap on modules'); return; } for (const name of names) { const wrapper = getWrapper(name) shimmer.wrap(nodule, name, wrapper); } } }