signalfx-tracing
Version:
Provides auto-instrumentation for JavaScript libraries and frameworks
147 lines (116 loc) • 3.72 kB
JavaScript
const opentracing = require('opentracing')
const tx = require('./util/tx')
const analyticsSampler = require('../analytics_sampler')
const constants = require('../constants')
const Reference = opentracing.Reference
const REFERENCE_CHILD_OF = opentracing.REFERENCE_CHILD_OF
const REFERENCE_NOOP = constants.REFERENCE_NOOP
function createWrapConnect (tracer, config) {
return function wrapConnect (connect) {
return function connectWithTrace () {
const scope = tracer.scope()
const options = getOptions(arguments)
if (!options) return connect.apply(this, arguments)
const span = options.path
? wrapIpc(tracer, config, this, options)
: wrapTcp(tracer, config, this, options)
analyticsSampler.sample(span, config.analytics)
return scope.bind(connect, span).apply(this, arguments)
}
}
}
function wrapTcp (tracer, config, socket, options) {
const host = options.host || 'localhost'
const port = options.port || 0
const family = options.family || 4
const span = startSpan(tracer, config, 'tcp', {
'resource.name': [host, port].filter(val => val).join(':'),
'tcp.remote.host': host,
'tcp.remote.port': port,
'tcp.family': `IPv${family}`
})
tx.setHost(span, host, port)
setupListeners(socket, span, 'tcp')
return span
}
function wrapIpc (tracer, config, socket, options) {
const span = startSpan(tracer, config, 'ipc', {
'resource.name': options.path,
'ipc.path': options.path
})
setupListeners(socket, span, 'ipc')
return span
}
function startSpan (tracer, config, protocol, tags) {
const childOf = tracer.scope().active()
const type = (childOf !== null) ? REFERENCE_CHILD_OF : REFERENCE_NOOP
const references = [ new Reference(type, childOf) ]
let operationName = `${protocol}.connect`
const resourceName = tags['resource.name']
if (resourceName) {
delete tags['resource.name']
operationName = `${operationName}: ${resourceName}`
}
const span = tracer.startSpan(operationName, {
references,
tags: Object.assign({
'span.kind': 'client',
'service.name': config.service || `${tracer._service}-${protocol}`
}, tags)
})
return span
}
function getOptions (args) {
if (!args[0]) return
switch (typeof args[0]) {
case 'object':
if (Array.isArray(args[0])) return getOptions(args[0])
return args[0]
case 'string':
if (isNaN(parseFloat(args[0]))) {
return {
path: args[0]
}
}
case 'number': // eslint-disable-line no-fallthrough
return {
port: args[0],
host: typeof args[1] === 'string' ? args[1] : 'localhost'
}
}
}
function setupListeners (socket, span, protocol) {
const events = ['connect', 'error', 'close', 'timeout']
const wrapListener = tx.wrap(span)
const localListener = () => {
span.addTags({
'tcp.local.address': socket.localAddress,
'tcp.local.port': socket.localPort
})
}
const cleanupListener = () => {
socket.removeListener('connect', localListener)
events.forEach(event => {
socket.removeListener(event, wrapListener)
socket.removeListener(event, cleanupListener)
})
}
if (protocol === 'tcp') {
socket.once('connect', localListener)
}
events.forEach(event => {
socket.once(event, wrapListener)
socket.once(event, cleanupListener)
})
}
module.exports = {
name: 'net',
patch (net, tracer, config) {
require('dns') // net will otherwise get an unpatched version for DNS lookups
this.wrap(net.Socket.prototype, 'connect', createWrapConnect(tracer, config))
},
unpatch (net) {
this.unwrap(net.Socket.prototype, 'connect')
}
}