UNPKG

dd-trace

Version:

Datadog APM tracing client for JavaScript

196 lines (152 loc) 5.08 kB
'use strict' const METHODS = require('methods').concat('all') const pathToRegExp = require('path-to-regexp') const shimmer = require('../../datadog-shimmer') const web = require('../../dd-trace/src/plugins/util/web') // TODO: clean this up to not use web util internals // TODO: stop checking for fast star and fast slash const regexpCache = Object.create(null) function createWrapHandle (tracer, config) { return function wrapHandle (handle) { return function handleWithTrace (req, res, done) { web.patch(req) if (!req._datadog.router) { const context = { route: '', stack: [] } web.beforeEnd(req, () => { req._datadog.paths = [context.route] }) req._datadog.router = context } return handle.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) { handle._name = handle._name || layer.name let wrapCallHandle if (handle.length === 4) { wrapCallHandle = shimmer.wrap(handle, function (error, req, res, next) { return callHandle(layer, handle, req, [error, req, res, wrapNext(layer, req, next)]) }) } else { wrapCallHandle = shimmer.wrap(handle, function (req, res, next) { return callHandle(layer, handle, req, [req, res, wrapNext(layer, req, next)]) }) } // This is a workaround for the `loopback` library so that it can find the correct express layer // that contains the real handle function wrapCallHandle._datadog_orig = handle return wrapCallHandle } 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 (layer.path && !isFastStar(layer) && !isFastSlash(layer)) { req._datadog.router.stack.pop() } web.finish(req, error) originalNext.apply(null, arguments) } } function callHandle (layer, handle, req, args) { const matchers = layer._datadog_matchers 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)) { const context = req._datadog.router context.stack.push(matchers[i].path) const route = context.stack.join('') // Longer route is more likely to be the actual route handler route. if (route.length > context.route.length) { context.route = route } break } } } return web.wrapMiddleware(req, handle, 'express.middleware', () => { 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) && cachedPathToRegExp(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), []) } function cachedPathToRegExp (pattern) { const maybeCached = regexpCache[pattern] if (maybeCached) { return maybeCached } const regexp = pathToRegExp(pattern) regexpCache[pattern] = regexp return regexp } module.exports = { name: 'router', versions: ['>=1'], patch (Router, tracer, config) { this.wrap(Router.prototype, 'handle', createWrapHandle(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, 'use') this.unwrap(Router.prototype, 'route') } }