elastic-apm-node
Version:
The official Elastic APM agent for Node.js
134 lines (109 loc) • 3.97 kB
JavaScript
var isError = require('core-util-is').isError
var semver = require('semver')
var shimmer = require('../shimmer')
var symbols = require('../../symbols')
module.exports = function (express, agent, { version, enabled }) {
if (!enabled) return express
agent.setFramework({ name: 'express', version, overwrite: false })
if (!semver.satisfies(version, '^4.0.0')) {
agent.logger.debug('express version %s not supported - aborting...', version)
return express
}
// express 5 moves the router methods onto a prototype
var routerProto = semver.satisfies(version, '^5')
? (express.Router && express.Router.prototype)
: express.Router
var layerPatchedSymbol = Symbol('layer-patched')
function shouldReport (err) {
if (!agent._conf.captureExceptions) return false
if (typeof err === 'string') return true
if (isError(err) && !err[symbols.errorReportedSymbol]) {
err[symbols.errorReportedSymbol] = true
return true
}
return false
}
function safePush (obj, prop, value) {
if (!obj[prop]) obj[prop] = []
obj[prop].push(value)
}
function patchLayer (layer, layerPath) {
if (!layer[layerPatchedSymbol]) {
layer[layerPatchedSymbol] = true
agent.logger.debug('shimming express.Router.Layer.handle function:', layer.name)
shimmer.wrap(layer, 'handle', function (orig) {
let handle
if (orig.length !== 4) {
handle = function (req, res, next) {
if (!layer.route && layerPath && typeof next === 'function') {
safePush(req, symbols.expressMountStack, layerPath)
arguments[2] = function () {
if (!(req.route && arguments[0] instanceof Error)) {
req[symbols.expressMountStack].pop()
}
return next.apply(this, arguments)
}
}
return orig.apply(this, arguments)
}
} else {
handle = function (err, req, res, next) {
if (shouldReport(err)) {
agent.captureError(err, { request: req })
}
return orig.apply(this, arguments)
}
}
for (const prop in orig) {
if (orig.hasOwnProperty(prop)) {
handle[prop] = orig[prop]
}
}
return handle
})
}
}
agent.logger.debug('shimming express.Router.use function')
shimmer.wrap(routerProto, 'route', orig => {
return function route (path) {
var route = orig.apply(this, arguments)
var layer = this.stack[this.stack.length - 1]
patchLayer(layer, path)
return route
}
})
shimmer.wrap(routerProto, 'use', orig => {
return function use (path) {
var route = orig.apply(this, arguments)
var layer = this.stack[this.stack.length - 1]
patchLayer(layer, typeof path === 'string' && path)
return route
}
})
agent.logger.debug('shimming express.static function')
shimmer.wrap(express, 'static', function wrapStatic (orig) {
// By the time of this writing, Express adds a `mime` property to the
// `static` function that needs to be copied to the wrapped function.
// Instead of only copying the `mime` function, let's loop over all
// properties in case new properties are added in later versions of
// Express.
Object.keys(orig).forEach(function (prop) {
agent.logger.debug('copying property %s from express.static', prop)
wrappedStatic[prop] = orig[prop]
})
return wrappedStatic
function wrappedStatic () {
var origServeStatic = orig.apply(this, arguments)
return function serveStatic (req, res, next) {
req[symbols.staticFile] = true
return origServeStatic(req, res, nextHook)
function nextHook (err) {
if (!err) req[symbols.staticFile] = false
return next.apply(this, arguments)
}
}
}
})
return express
}