dd-trace
Version:
Datadog APM tracing client for JavaScript
175 lines (150 loc) • 4.65 kB
JavaScript
const kebabCase = require('lodash.kebabcase')
const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
const { TEXT_MAP } = require('../../../ext/formats')
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 childOf = extract(tracer, message)
const span = tracer.startSpan('amqp.command', { childOf })
addTags(this, tracer, config, span, 'basic.deliver', fields)
analyticsSampler.sample(span, config.measured, 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 })
fields.headers = fields.headers || {}
addTags(channel, tracer, config, span, method, fields)
tracer.inject(span, TEXT_MAP, fields.headers)
analyticsSampler.sample(span, config.measured)
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({
'error.type': error.name,
'error.msg': error.message,
'error.stack': error.stack
})
return error
}
function addTags (channel, tracer, config, span, method, fields) {
const fieldNames = [
'queue',
'exchange',
'routingKey',
'consumerTag',
'source',
'destination'
]
span.addTags({
'service.name': config.service || `${tracer._service}-amqp`,
'resource.name': getResourceName(method, fields)
})
if (channel && channel.connection && channel.connection.stream) {
span.addTags({
'out.host': channel.connection.stream._host,
'out.port': 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.addTags({
'span.kind': 'consumer',
'span.type': 'worker'
})
break
default:
span.setTag('span.kind', 'client')
}
fieldNames.forEach(field => {
fields[field] !== undefined && span.setTag(`amqp.${field}`, fields[field])
})
}
function extract (tracer, message) {
return message
? tracer.extract(TEXT_MAP, message.properties.headers)
: null
}
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')
}
}
]