UNPKG

signalfx-tracing

Version:

Provides auto-instrumentation for JavaScript libraries and frameworks

166 lines (142 loc) 4.43 kB
'use strict' const kebabCase = require('lodash.kebabcase') const analyticsSampler = require('../analytics_sampler') const tx = require('./util/tx') let methods = {} function createWrapSendImmediately (tracer, config) { return function wrapSendImmediately (sendImmediately) { return function sendImmediatelyWithTrace (method, fields) { return sendWithTrace(sendImmediately, this, arguments, tracer, config, methods[method], fields) } } } function createWrapSendMessage (tracer, config) { return function wrapSendMessage (sendMessage) { return function sendMessageWithTrace (fields) { return sendWithTrace(sendMessage, this, arguments, tracer, config, 'basic.publish', fields) } } } function createWrapDispatchMessage (tracer, config) { return function wrapDispatchMessage (dispatchMessage) { return function dispatchMessageWithTrace (fields, message) { const span = tracer.startSpan('amqp.command') addTags(this, tracer, config, span, 'basic.deliver', fields) analyticsSampler.sample(span, config.analytics, true) tracer.scope().activate(span, () => { try { dispatchMessage.apply(this, arguments) } catch (e) { throw addError(span, e) } finally { span.finish() } }) } } } function sendWithTrace (send, channel, args, tracer, config, method, fields) { const childOf = tracer.scope().active() const span = tracer.startSpan('amqp.command', { childOf }) addTags(channel, tracer, config, span, method, fields) analyticsSampler.sample(span, config.analytics) return tracer.scope().activate(span, () => { try { return send.apply(channel, args) } catch (e) { throw addError(span, e) } finally { span.finish() } }) } function isCamelCase (str) { return /([A-Z][a-z0-9]+)+/.test(str) } function getResourceName (method, fields) { return [ method, fields.exchange, fields.routingKey, fields.queue, fields.source, fields.destination ].filter(val => val).join(' ') } function addError (span, error) { span.addTags({ 'sfx.error.kind': error.name, 'sfx.error.message': error.message, 'sfx.error.stack': error.stack }) return error } function addTags (channel, tracer, config, span, method, fields) { const fieldNames = [ 'queue', 'exchange', 'routingKey', 'consumerTag', 'source', 'destination' ] let destinationName = fields.exchange if (destinationName === undefined) { destinationName = fields.queue } if (destinationName) { span.setTag('message_bus.destination', destinationName) } span.addTags({ 'service.name': config.service || `${tracer._service}-amqp`, 'resource.name': getResourceName(method, fields), 'component': 'amqplib' }) if (channel.connection && channel.connection.stream) { tx.setHost(span, channel.connection.stream._host, channel.connection.stream.remotePort) } switch (method) { case 'basic.publish': span.setTag('span.kind', 'producer') break case 'basic.consume': case 'basic.get': case 'basic.deliver': span.setTag('span.kind', 'consumer') break } fieldNames.forEach(field => { fields[field] !== undefined && span.setTag(`amqp.${field}`, fields[field]) }) } module.exports = [ { name: 'amqplib', file: 'lib/defs.js', versions: ['>=0.5'], patch (defs, tracer, config) { methods = Object.keys(defs) .filter(key => Number.isInteger(defs[key])) .filter(key => isCamelCase(key)) .reduce((acc, key) => Object.assign(acc, { [defs[key]]: kebabCase(key).replace('-', '.') }), {}) }, unpatch (defs) { methods = {} } }, { name: 'amqplib', file: 'lib/channel.js', versions: ['>=0.5'], patch (channel, tracer, config) { this.wrap(channel.Channel.prototype, 'sendImmediately', createWrapSendImmediately(tracer, config)) this.wrap(channel.Channel.prototype, 'sendMessage', createWrapSendMessage(tracer, config)) this.wrap(channel.BaseChannel.prototype, 'dispatchMessage', createWrapDispatchMessage(tracer, config)) }, unpatch (channel) { this.unwrap(channel.Channel.prototype, 'sendImmediately') this.unwrap(channel.Channel.prototype, 'sendMessage') this.unwrap(channel.BaseChannel.prototype, 'dispatchMessage') } } ]