Skip to content

Commit

Permalink
chore(hapi): add v17 support
Browse files Browse the repository at this point in the history
  • Loading branch information
Stephen Belanger committed Jan 2, 2018
1 parent 826efc9 commit 8b68ed7
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 169 deletions.
8 changes: 7 additions & 1 deletion .tav.yml
Expand Up @@ -66,6 +66,12 @@ elasticsearch:
handlebars:
versions: '*'
commands: node test/instrumentation/modules/handlebars.js
hapi:
hapi-no-async-await:
name: hapi
versions: '>=9.0.1 <17.0.0'
commands: node test/instrumentation/modules/hapi.js
hapi-async-await:
name: hapi
node: '>=8.2'
versions: '^17.0.0'
commands: node test/instrumentation/modules/hapi.js
218 changes: 125 additions & 93 deletions lib/instrumentation/modules/hapi.js
Expand Up @@ -14,113 +14,145 @@ module.exports = function (hapi, agent, version) {

debug('shimming hapi.Server.prototype.initialize')

shimmer.wrap(hapi.Server.prototype, 'initialize', function (orig) {
return function () {
// Hooks that are always allowed
if (typeof this.on === 'function') {
this.on('request-error', function (req, err) {
agent.captureError(err, {request: req.raw && req.raw.req})
})
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)
}
})
}

this.on('log', function (event, tags) {
if (!event || !tags.error) return
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 {
debug('unable to enable hapi error tracking')
}

var payload = {custom: event} // TODO: Find better location to put this than custom
// When the hapi server has no connections we don't make connection
// lifecycle hooks
var conns = server.connections
if (conns && conns.length === 0) {
debug('unable to enable hapi instrumentation on connectionless server')
return
}

var err = event.data
if (!(err instanceof Error) && typeof err !== 'string') {
err = 'hapi server emitted a log event tagged error'
}
// Hooks that are only allowed when the hapi server has connections
if (typeof server.ext === 'function') {
server.ext('onPreAuth', onPreAuth)
server.ext('onPreResponse', onPreResponse)
} else {
debug('unable to enable automatic hapi transaction naming')
}
}

agent.captureError(err, payload)
function attachEvents (emitter) {
if (semver.satisfies(version, '<17')) {
emitter.on('request-error', function (request, error) {
agent.captureError(error, {
request: request.raw && request.raw.req
})
})
}

this.on('request', function (req, event, tags) {
if (!event || !tags.error) return

var payload = {
custom: event, // TODO: Find better location to put this than custom
request: req.raw && req.raw.req
}
emitter.on('log', function (event, tags) {
captureError('log', null, event, tags)
})

var err = event.data
if (!(err instanceof Error) && typeof err !== 'string') {
err = 'hapi server emitted a request event tagged error'
}
emitter.on('request', function (req, event, tags) {
captureError('request', req, event, tags)
})
}

agent.captureError(err, payload)
})
} else {
debug('unable to enable hapi error tracking')
}
function captureError (type, req, event, tags) {
if (!event || !tags.error) 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
}

// When the hapi server has no connections we don't make connection
// lifecycle hooks
if (this.connections.length === 0) {
debug('unable to enable hapi instrumentation on connectionless server')
return orig.apply(this, arguments)
}
var err = payload.custom.data
if (!(err instanceof Error) && typeof err !== 'string') {
err = 'hapi server emitted a ' + type + ' event tagged error'
}

// Hooks that are only allowed when the hapi server has connections
if (typeof this.ext === 'function') {
this.ext('onPreAuth', function (request, reply) {
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._elastic_apm_onPreAuth = 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()
})
agent.captureError(err, payload)
}

this.ext('onPreResponse', function (request, reply) {
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._elastic_apm_onPreAuth &&
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()
})
} else {
debug('unable to enable automatic hapi transaction naming')
function onPreAuth (request, reply) {
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._elastic_apm_onPreAuth = 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()
}

return orig.apply(this, arguments)
function onPreResponse (request, reply) {
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._elastic_apm_onPreAuth &&
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
}
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -95,7 +95,7 @@
"get-port": "^2.1.0",
"graphql": "^0.11.2",
"handlebars": "^4.0.11",
"hapi": "^16.5.2",
"hapi": "^16.6.2",
"https-pem": "^2.0.0",
"inquirer": "^0.12.0",
"ioredis": "^3.0.0",
Expand Down

0 comments on commit 8b68ed7

Please sign in to comment.