lib/instrumentation/modules/express.js (117 lines of code) (raw):

/* * Copyright Elasticsearch B.V. and other contributors where applicable. * Licensed under the BSD 2-Clause License; you may not use this file except in * compliance with the BSD 2-Clause License. */ 'use strict'; 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 <6')) { agent.logger.debug('cannot instrument express version %s', version); return express; } if ( semver.satisfies(version, '>5') && semver.satisfies(process.version, '<18') ) { agent.logger.debug( 'express version %s not supported for node version %s, skipping express instrumentation', version, process.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: %s', 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 (nextArg) { // https://github.com/expressjs/express/blob/4.18.1/lib/router/route.js#L116-L149 // The argument to an Express handler's `next()` can be: // falsey (call the next handler), 'route' (skip handlers for // this route), 'router' (skip handlers for this Router), or // any other value is considered an error (it doesn't have to // be an instance of Error). For all but the last one, Express // will consider other routes, so we want to pop the mount // stack. if (!nextArg || nextArg === 'route' || nextArg === 'router') { 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 (Object.prototype.hasOwnProperty.call(orig, 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. for (const prop of Object.keys(orig)) { 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; };