UNPKG

signalfx-tracing

Version:

Provides auto-instrumentation for JavaScript libraries and frameworks

147 lines (116 loc) 3.72 kB
'use strict' 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') } }