UNPKG

elastic-apm-node

Version:

The official Elastic APM agent for Node.js

168 lines (138 loc) 5.3 kB
'use strict' var semver = require('semver') var shimmer = require('../shimmer') var onPreAuthSym = Symbol('ElasticAPMOnPreAuth') module.exports = function (hapi, agent, { version, enabled }) { if (!enabled) return hapi agent.setFramework({ name: 'hapi', version, overwrite: false }) if (!semver.satisfies(version, '>=9.0.0')) { agent.logger.debug('hapi version %s not supported - aborting...', version) return hapi } agent.logger.debug('shimming hapi.Server.prototype.initialize') if (semver.satisfies(version, '>=17')) { shimmer.massWrap(hapi, ['Server', 'server'], function (orig) { return function (options) { var res = orig.apply(this, arguments) patchServer(res) return res } }) } else { shimmer.wrap(hapi.Server.prototype, 'initialize', function (orig) { return function () { patchServer(this) return orig.apply(this, arguments) } }) } function patchServer (server) { // Hooks that are always allowed if (typeof server.on === 'function') { attachEvents(server) } else if (typeof server.events.on === 'function') { attachEvents(server.events) } else { agent.logger.debug('unable to enable hapi error tracking') } // Prior to hapi 17, when the server has no connections we can't make // connection lifecycle hooks (in hapi 17+ the server always has // connections, though the `server.connections` property doesn't exists, // so this if-statement wont fire) var conns = server.connections if (conns && conns.length === 0) { agent.logger.debug('unable to enable hapi instrumentation on connectionless server') return } // Hooks that are only allowed when the hapi server has connections // (with hapi 17+ this is always the case) if (typeof server.ext === 'function') { server.ext('onPreAuth', onPreAuth) server.ext('onPreResponse', onPreResponse) } else { agent.logger.debug('unable to enable automatic hapi transaction naming') } } function attachEvents (emitter) { if (semver.satisfies(version, '<17')) { emitter.on('request-error', function (request, error) { agent.captureError(error, { request: request.raw && request.raw.req }) }) } emitter.on('log', function (event, tags) { captureError('log', null, event, tags) }) emitter.on('request', function (req, event, tags) { captureError('request', req, event, tags) }) } function captureError (type, req, event, tags) { if (!event || !tags.error || event.channel === 'internal') { return } // TODO: Find better location to put this than custom var payload = { custom: { tags: event.tags, internals: event.internals, // Moved from data to error in hapi 17 data: event.data || event.error }, request: req && req.raw && req.raw.req } var err = payload.custom.data if (!(err instanceof Error) && typeof err !== 'string') { err = 'hapi server emitted a ' + type + ' event tagged error' } agent.captureError(err, payload) } function onPreAuth (request, reply) { agent.logger.debug('received hapi onPreAuth event') // Record the fact that the preAuth extension have been called. This // info is useful later to know if this is a CORS preflight request // that is automatically handled by hapi (as those will not trigger // the onPreAuth extention) request[onPreAuthSym] = true if (request.route) { // fingerprint was introduced in hapi 11 and is a little more // stable in case the param names change // - path example: /foo/{bar*2} // - fingerprint example: /foo/?/? var fingerprint = request.route.fingerprint || request.route.path if (fingerprint) { var name = (request.raw && request.raw.req && request.raw.req.method) || (request.route.method && request.route.method.toUpperCase()) if (typeof name === 'string') { name = name + ' ' + fingerprint } else { name = fingerprint } agent._instrumentation.setDefaultTransactionName(name) } } return semver.satisfies(version, '>=17') ? reply.continue : reply.continue() } function onPreResponse (request, reply) { agent.logger.debug('received hapi onPreResponse event') // Detection of CORS preflight requests: // There is no easy way in hapi to get the matched route for a // CORS preflight request that matches any of the autogenerated // routes created by hapi when `cors: true`. The best solution is to // detect the request "fingerprint" using the magic if-sentence below // and group all those requests into on type of transaction if (!request[onPreAuthSym] && request.route && request.route.path === '/{p*}' && request.raw && request.raw.req && request.raw.req.method === 'OPTIONS' && request.raw.req.headers['access-control-request-method']) { agent._instrumentation.setDefaultTransactionName('CORS preflight') } return semver.satisfies(version, '>=17') ? reply.continue : reply.continue() } return hapi }