signalfx-tracing
Version:
Provides auto-instrumentation for JavaScript libraries and frameworks
164 lines (131 loc) • 4.34 kB
JavaScript
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')
}
}