elastic-apm-node
Version:
The official Elastic APM agent for Node.js
154 lines (126 loc) • 4.62 kB
JavaScript
/*
* 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.
*/
;
var semver = require('semver');
var shimmer = require('../../shimmer');
var onPreAuthSym = Symbol('ElasticAPMOnPreAuth');
module.exports = function (hapi, agent, { version, enabled }) {
if (!enabled) {
return hapi;
}
if (!semver.satisfies(version, '>=17.9.0 <22.0.0')) {
agent.logger.debug('@hapi/hapi@%s not supported, skipping', version);
return hapi;
}
agent.setFramework({ name: 'hapi', version, overwrite: false });
agent.logger.debug('shimming hapi.Server, hapi.server');
shimmer.massWrap(hapi, ['Server', 'server'], function (orig) {
return function (options) {
var res = orig.apply(this, arguments);
patchServer(res);
return res;
};
});
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');
}
server.ext('onPreAuth', onPreAuth);
server.ext('onPreResponse', onPreResponse);
if (agent._conf.captureBody !== 'off') {
server.ext('onPostAuth', onPostAuth);
}
}
function attachEvents(emitter) {
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 reply.continue;
}
function onPostAuth(request, reply) {
if (request.payload && request.raw && request.raw.req) {
// Save the parsed req body to be picked up by getContextFromRequest().
request.raw.req.payload = request.payload;
}
return 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 reply.continue;
}
return hapi;
};