UNPKG

signalfx-tracing

Version:

Provides auto-instrumentation for JavaScript libraries and frameworks

164 lines (131 loc) 4.34 kB
'use strict' const METHODS = require('methods').concat('all') const pathToRegExp = require('path-to-regexp') const web = require('./util/web') function createWrapHandle (tracer, config) { return function wrapHandle (handle) { return function handleWithTracer (req, res, done) { web.patch(req) return handle.apply(this, arguments) } } } function createWrapProcessParams (tracer, config) { return function wrapProcessParams (processParams) { return function processParamsWithTrace (layer, called, req, res, done) { const matchers = layer._datadog_matchers req = done ? req : called if (web.active(req) && matchers) { // Try to guess which path actually matched for (let i = 0; i < matchers.length; i++) { if (matchers[i].test(layer)) { web.enterRoute(req, matchers[i].path) break } } } return processParams.apply(this, arguments) } } } function wrapRouterMethod (original) { return function methodWithTrace (fn) { const offset = this.stack ? [].concat(this.stack).length : 0 const router = original.apply(this, arguments) if (typeof this.stack === 'function') { this.stack = [{ handle: this.stack }] } wrapStack(this.stack, offset, extractMatchers(fn)) return router } } function wrapLayerHandle (layer, handle) { if (handle.length === 4) { return function (error, req, res, next) { return callHandle(layer, handle, req, [error, req, res, wrapNext(layer, req, next)]) } } else { return function (req, res, next) { return callHandle(layer, handle, req, [req, res, wrapNext(layer, req, next)]) } } } function wrapStack (stack, offset, matchers) { [].concat(stack).slice(offset).forEach(layer => { if (layer.__handle) { // express-async-errors layer.__handle = wrapLayerHandle(layer, layer.__handle) } else { layer.handle = wrapLayerHandle(layer, layer.handle) } layer._datadog_matchers = matchers if (layer.route) { METHODS.forEach(method => { if (typeof layer.route.stack === 'function') { layer.route.stack = [{ handle: layer.route.stack }] } layer.route[method] = wrapRouterMethod(layer.route[method]) }) } }) } function wrapNext (layer, req, next) { if (!next || !web.active(req)) return next const originalNext = next return function (error) { if (!error && layer.path && !isFastStar(layer) && !isFastSlash(layer)) { web.exitRoute(req) } web.finish(req, error) originalNext.apply(null, arguments) } } function callHandle (layer, handle, req, args) { return web.wrapMiddleware(req, handle, 'express.middleware', () => { const span = web.active(req) if (span) { span.setTag('component', 'express') } return handle.apply(layer, args) }) } function extractMatchers (fn) { const arg = flatten([].concat(fn)) if (typeof arg[0] === 'function') { return [] } return arg.map(pattern => ({ path: pattern instanceof RegExp ? `(${pattern})` : pattern, test: layer => !isFastStar(layer) && !isFastSlash(layer) && pathToRegExp(pattern).test(layer.path) })) } function isFastStar (layer) { if (layer.regexp.fast_star !== undefined) { return layer.regexp.fast_star } return layer._datadog_matchers.some(matcher => matcher.path === '*') } function isFastSlash (layer) { if (layer.regexp.fast_slash !== undefined) { return layer.regexp.fast_slash } return layer._datadog_matchers.some(matcher => matcher.path === '/') } function flatten (arr) { return arr.reduce((acc, val) => Array.isArray(val) ? acc.concat(flatten(val)) : acc.concat(val), []) } module.exports = { name: 'router', versions: ['>=1'], patch (Router, tracer, config) { this.wrap(Router.prototype, 'handle', createWrapHandle(tracer, config)) this.wrap(Router.prototype, 'process_params', createWrapProcessParams(tracer, config)) this.wrap(Router.prototype, 'use', wrapRouterMethod) this.wrap(Router.prototype, 'route', wrapRouterMethod) }, unpatch (Router) { this.unwrap(Router.prototype, 'handle') this.unwrap(Router.prototype, 'process_params') this.unwrap(Router.prototype, 'use') this.unwrap(Router.prototype, 'route') } }