From 1173d00c87182bb539107972d754d9dd0f12e49d Mon Sep 17 00:00:00 2001 From: Brad Gardner Date: Wed, 29 May 2019 08:50:47 -0400 Subject: [PATCH 01/11] added @hapi/hapi module for instrumentation, copied and slimmed down the existing hapi implementations --- lib/instrumentation/index.js | 1 + lib/instrumentation/modules/@hapi/hapi.js | 141 +++++ package.json | 5 +- .../modules/@hapi/hapi/basic.js | 581 ++++++++++++++++++ .../modules/@hapi/hapi/set-framework.js | 29 + 5 files changed, 755 insertions(+), 2 deletions(-) create mode 100644 lib/instrumentation/modules/@hapi/hapi.js create mode 100644 test/instrumentation/modules/@hapi/hapi/basic.js create mode 100644 test/instrumentation/modules/@hapi/hapi/set-framework.js diff --git a/lib/instrumentation/index.js b/lib/instrumentation/index.js index acbe95aa1d..9ec6cff56b 100644 --- a/lib/instrumentation/index.js +++ b/lib/instrumentation/index.js @@ -24,6 +24,7 @@ var MODULES = [ 'graphql', 'handlebars', 'hapi', + '@hapi/hapi', 'http', 'https', 'http2', diff --git a/lib/instrumentation/modules/@hapi/hapi.js b/lib/instrumentation/modules/@hapi/hapi.js new file mode 100644 index 0000000000..d39bfea091 --- /dev/null +++ b/lib/instrumentation/modules/@hapi/hapi.js @@ -0,0 +1,141 @@ +'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 }) + + agent.logger.debug('shimming hapi.Server.prototype.initialize') + + 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') + } + + // 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) { + 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 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 +} diff --git a/package.json b/package.json index d6dc1ea330..0a9cf45e9d 100644 --- a/package.json +++ b/package.json @@ -127,6 +127,7 @@ "graphql": "^14.3.1", "handlebars": "^4.0.12", "hapi": "^18.1.0", + "@hapi/hapi": "^18.2.0", "https-pem": "^2.0.0", "inquirer": "^0.12.0", "ioredis": "^4.10.0", @@ -182,7 +183,7 @@ ] }, "coordinates": [ - 55.77826, - 12.593255 + 55.778264, + 12.59313 ] } diff --git a/test/instrumentation/modules/@hapi/hapi/basic.js b/test/instrumentation/modules/@hapi/hapi/basic.js new file mode 100644 index 0000000000..4cc669a62a --- /dev/null +++ b/test/instrumentation/modules/@hapi/hapi/basic.js @@ -0,0 +1,581 @@ +'use strict' + +var agent = require('../../../../..').start({ + serviceName: 'test', + secretToken: 'test', + captureExceptions: false, + logLevel: 'fatal', + metricsInterval: 0 +}) + +var pkg = require('@hapi/hapi/package.json') +var semver = require('semver') + +// hapi 17+ requires Node.js 8.9.0 or higher +if (semver.lt(process.version, '8.9.0') && semver.gte(pkg.version, '17.0.0')) process.exit() +// hapi 16.7.0+ requires Node.js 6.0.0 or higher +if (semver.lt(process.version, '6.0.0') && semver.gte(pkg.version, '16.7.0')) process.exit() + +// hapi does not work on early versions of Node.js 10 because of https://github.com/nodejs/node/issues/20516 +// NOTE: Do not use semver.satisfies, as it does not match prereleases +var parsed = semver.parse(process.version) +if (parsed.major === 10 && parsed.minor >= 0 && parsed.minor < 8) process.exit() + +var http = require('http') + +var Hapi = require('@hapi/hapi') +var test = require('tape') + +var mockClient = require('../../../../_mock_http_client') + +var originalCaptureError = agent.captureError + +function noop () {} + +test('extract URL from request', function (t) { + resetAgent(2, function (data) { + t.equal(data.transactions.length, 1) + t.equal(data.errors.length, 1) + var request = data.errors[0].context.request + t.equal(request.method, 'GET') + t.equal(request.url.pathname, '/captureError') + t.equal(request.url.search, '?foo=bar') + t.equal(request.url.raw, '/captureError?foo=bar') + t.equal(request.url.hostname, 'localhost') + t.equal(request.url.port, String(server.info.port)) + t.equal(request.socket.encrypted, false) + server.stop(noop) + t.end() + }) + + agent.captureError = originalCaptureError + + var server = startServer(function (err, port) { + t.error(err) + http.get('http://localhost:' + port + '/captureError?foo=bar') + }) +}) + +test('route naming', function (t) { + t.plan(8) + + resetAgent(1, function (data) { + assert(t, data) + server.stop(noop) + }) + + var server = startServer(function (err, port) { + t.error(err) + http.get('http://localhost:' + port + '/hello', function (res) { + t.equal(res.statusCode, 200) + res.on('data', function (chunk) { + t.equal(chunk.toString(), 'hello world') + }) + res.on('end', function () { + agent.flush() + }) + }) + }) +}) + +test('connectionless', function (t) { + t.plan(1) + + resetAgent() + + var server = makeServer() + initServer(server, function (err) { + server.stop(noop) + t.error(err, 'start error') + }) +}) + +test('connectionless server error logging with Error', function (t) { + t.plan(6) + + var customError = new Error('custom error') + + resetAgent() + + agent.captureError = function (err, opts) { + server.stop(noop) + + t.equal(err, customError) + t.ok(opts.custom) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.ok(opts.custom.data instanceof Error) + } + + var server = makeServer() + initServer(server, function (err) { + t.error(err, 'start error') + + server.log(['error'], customError) + }) +}) + +test('connectionless server error logging with String', function (t) { + t.plan(6) + + var customError = 'custom error' + + resetAgent() + + agent.captureError = function (err, opts) { + server.stop(noop) + + t.equal(err, customError) + t.ok(opts.custom) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.ok(typeof opts.custom.data === 'string') + } + + var server = makeServer() + initServer(server, function (err) { + t.error(err, 'start error') + + server.log(['error'], customError) + }) +}) + +test('connectionless server error logging with Object', function (t) { + t.plan(6) + + var customError = { + error: 'I forgot to turn this into an actual Error' + } + + resetAgent() + + agent.captureError = function (err, opts) { + server.stop(noop) + + t.equal(err, 'hapi server emitted a log event tagged error') + t.ok(opts.custom) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.deepEqual(opts.custom.data, customError) + } + + var server = makeServer() + initServer(server, function (err) { + t.error(err, 'start error') + + server.log(['error'], customError) + }) +}) + +test('server error logging with Error', function (t) { + t.plan(6) + + var customError = new Error('custom error') + + resetAgent() + + agent.captureError = function (err, opts) { + server.stop(noop) + + t.equal(err, customError) + t.ok(opts.custom) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.ok(opts.custom.data instanceof Error) + } + + var server = startServer(function (err) { + t.error(err, 'start error') + + server.log(['error'], customError) + }) +}) + +test('server error logging with Error does not affect event tags', function (t) { + t.plan(8) + + var customError = new Error('custom error') + + resetAgent() + + agent.captureError = function (err, opts) { + server.stop(noop) + + t.equal(err, customError) + t.ok(opts.custom) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.ok(opts.custom.data instanceof Error) + } + + var server = makeServer() + + var emitter = server.events || server + emitter.on('log', function (event, tags) { + t.deepEqual(event.tags, ['error']) + }) + + runServer(server, function (err) { + t.error(err, 'start error') + + emitter.on('log', function (event, tags) { + t.deepEqual(event.tags, ['error']) + }) + + server.log(['error'], customError) + }) +}) + +test('server error logging with String', function (t) { + t.plan(6) + + var customError = 'custom error' + + resetAgent() + + agent.captureError = function (err, opts) { + server.stop(noop) + + t.equal(err, customError) + t.ok(opts.custom) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.ok(typeof opts.custom.data === 'string') + } + + var server = startServer(function (err) { + t.error(err, 'start error') + + server.log(['error'], customError) + }) +}) + +test('server error logging with Object', function (t) { + t.plan(6) + + var customError = { + error: 'I forgot to turn this into an actual Error' + } + + resetAgent() + + agent.captureError = function (err, opts) { + server.stop(noop) + + t.equal(err, 'hapi server emitted a log event tagged error') + t.ok(opts.custom) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.deepEqual(opts.custom.data, customError) + } + + var server = startServer(function (err) { + t.error(err, 'start error') + + server.log(['error'], customError) + }) +}) + +test('request error logging with Error', function (t) { + t.plan(13) + + var customError = new Error('custom error') + + resetAgent(1, function (data) { + assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) + + server.stop(noop) + }) + + agent.captureError = function (err, opts) { + t.equal(err, customError) + t.ok(opts.custom) + t.ok(opts.request) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.ok(opts.custom.data instanceof Error) + } + + var server = makeServer() + + server.route({ + method: 'GET', + path: '/error', + handler: handler(function (request) { + request.log(['error'], customError) + + return 'hello world' + }) + }) + + runServer(server, function (err) { + t.error(err, 'start error') + + http.get('http://localhost:' + server.info.port + '/error', function (res) { + t.equal(res.statusCode, 200) + + res.resume().on('end', function () { + agent.flush() + }) + }) + }) +}) + +test('request error logging with Error does not affect event tags', function (t) { + t.plan(15) + + var customError = new Error('custom error') + + resetAgent(1, function (data) { + assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) + + server.stop(noop) + }) + + agent.captureError = function (err, opts) { + t.equal(err, customError) + t.ok(opts.custom) + t.ok(opts.request) + t.deepEqual(opts.custom.tags, ['elastic-apm', 'error']) + t.false(opts.custom.internals) + t.ok(opts.custom.data instanceof Error) + } + + var server = makeServer() + + server.route({ + method: 'GET', + path: '/error', + handler: handler(function (request) { + request.log(['elastic-apm', 'error'], customError) + + return 'hello world' + }) + }) + + var emitter = server.events || server + emitter.on('request', function (req, event, tags) { + if (event.channel === 'internal') return + t.deepEqual(event.tags, ['elastic-apm', 'error']) + }) + + runServer(server, function (err) { + t.error(err, 'start error') + + emitter.on('request', function (req, event, tags) { + if (event.channel === 'internal') return + t.deepEqual(event.tags, ['elastic-apm', 'error']) + }) + + http.get('http://localhost:' + server.info.port + '/error', function (res) { + t.equal(res.statusCode, 200) + + res.resume().on('end', function () { + agent.flush() + }) + }) + }) +}) + +test('request error logging with String', function (t) { + t.plan(13) + + var customError = 'custom error' + + resetAgent(1, function (data) { + assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) + + server.stop(noop) + }) + + agent.captureError = function (err, opts) { + t.equal(err, customError) + t.ok(opts.custom) + t.ok(opts.request) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.ok(typeof opts.custom.data === 'string') + } + + var server = makeServer() + + server.route({ + method: 'GET', + path: '/error', + handler: handler(function (request) { + request.log(['error'], customError) + + return 'hello world' + }) + }) + + runServer(server, function (err) { + t.error(err, 'start error') + + http.get('http://localhost:' + server.info.port + '/error', function (res) { + t.equal(res.statusCode, 200) + + res.resume().on('end', function () { + agent.flush() + }) + }) + }) +}) + +test('request error logging with Object', function (t) { + t.plan(13) + + var customError = { + error: 'I forgot to turn this into an actual Error' + } + + resetAgent(1, function (data) { + assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) + + server.stop(noop) + }) + + agent.captureError = function (err, opts) { + t.equal(err, 'hapi server emitted a request event tagged error') + t.ok(opts.custom) + t.ok(opts.request) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.deepEqual(opts.custom.data, customError) + } + + var server = makeServer() + + server.route({ + method: 'GET', + path: '/error', + handler: handler(function (request) { + request.log(['error'], customError) + + return 'hello world' + }) + }) + + runServer(server, function (err) { + t.error(err, 'start error') + + http.get('http://localhost:' + server.info.port + '/error', function (res) { + t.equal(res.statusCode, 200) + + res.resume().on('end', function () { + agent.flush() + }) + }) + }) +}) + +test('error handling', function (t) { + t.plan(10) + + resetAgent(1, function (data) { + assert(t, data, { status: 'HTTP 5xx', name: 'GET /error' }) + server.stop(noop) + }) + + agent.captureError = function (err, opts) { + t.equal(err.message, 'foo') + t.ok(opts.request instanceof http.IncomingMessage) + } + + var server = startServer(function (err, port) { + t.error(err) + http.get('http://localhost:' + port + '/error', function (res) { + t.equal(res.statusCode, 500) + res.on('data', function (chunk) { + var data = JSON.parse(chunk.toString()) + t.deepEqual(data, { + statusCode: 500, + error: 'Internal Server Error', + message: 'An internal server error occurred' + }) + }) + res.on('end', function () { + agent.flush() + }) + }) + }) +}) + +function makeServer (opts) { + var server = new Hapi.Server() + + return server +} + +function initServer (server, cb) { + server.initialize().then( + cb.bind(null, null), + cb + ) +} + +function runServer (server, cb) { + server.start().then( + () => cb(null, server.info.port), + cb + ) +} + +function startServer (cb) { + var server = buildServer() + runServer(server, cb) + return server +} + +function handler (fn) { + return fn +} + +function buildServer () { + var server = makeServer() + + server.route({ + method: 'GET', + path: '/hello', + handler: handler(function (request) { + return 'hello world' + }) + }) + server.route({ + method: 'GET', + path: '/error', + handler: handler(function (request) { + throw new Error('foo') + }) + }) + server.route({ + method: 'GET', + path: '/captureError', + handler: handler(function (request) { + agent.captureError(new Error()) + return '' + }) + }) + return server +} + +function assert (t, data, results) { + if (!results) results = {} + results.status = results.status || 'HTTP 2xx' + results.name = results.name || 'GET /hello' + + t.equal(data.transactions.length, 1) + + var trans = data.transactions[0] + + t.equal(trans.name, results.name) + t.equal(trans.type, 'request') + t.equal(trans.result, results.status) + t.equal(trans.context.request.method, 'GET') +} + +function resetAgent (expected, cb) { + agent._instrumentation.currentTransaction = null + agent._transport = mockClient(expected, cb) + agent.captureError = function (err) { throw err } +} diff --git a/test/instrumentation/modules/@hapi/hapi/set-framework.js b/test/instrumentation/modules/@hapi/hapi/set-framework.js new file mode 100644 index 0000000000..51bb2d0b00 --- /dev/null +++ b/test/instrumentation/modules/@hapi/hapi/set-framework.js @@ -0,0 +1,29 @@ +'use strict' + +const agent = require('../../../../..').start({ + captureExceptions: true, + metricsInterval: 0 +}) + +const pkg = require('@hapi/hapi/package') +const semver = require('semver') + +// hapi 17+ requires Node.js 8.9.0 or higher +if (semver.lt(process.version, '8.9.0') && semver.gte(pkg.version, '17.0.0')) process.exit() +// hapi 16.7.0+ requires Node.js 6.0.0 or higher +if (semver.lt(process.version, '6.0.0') && semver.gte(pkg.version, '16.7.0')) process.exit() + +let asserts = 0 + +agent.setFramework = function ({ name, version, overwrite }) { + asserts++ + assert.strictEqual(name, 'hapi') + assert.strictEqual(version, require('@hapi/hapi/package').version) + assert.strictEqual(overwrite, false) +} + +const assert = require('assert') + +require('@hapi/hapi') + +assert.strictEqual(asserts, 1) From 6760e2bcb17a8ca33e56994773aed1c6b5767635 Mon Sep 17 00:00:00 2001 From: Brad Gardner Date: Wed, 29 May 2019 10:25:11 -0400 Subject: [PATCH 02/11] added @hapi/hapi to tests --- test/test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test.js b/test/test.js index f68fddc402..cc7a76f2b5 100644 --- a/test/test.js +++ b/test/test.js @@ -77,6 +77,7 @@ var directories = [ 'test/instrumentation/modules/express', 'test/instrumentation/modules/fastify', 'test/instrumentation/modules/hapi', + 'test/instrumentation/modules/@hapi/hapi', 'test/instrumentation/modules/http', 'test/instrumentation/modules/koa', 'test/instrumentation/modules/koa-router', From 567022e35cb6593244b23d1e4e6e756ca8a33657 Mon Sep 17 00:00:00 2001 From: Brad Gardner Date: Wed, 29 May 2019 12:31:41 -0400 Subject: [PATCH 03/11] refactored modules to load with aliases, updated supported technologies, updated tests --- docs/supported-technologies.asciidoc | 1 + lib/instrumentation/index.js | 20 +- lib/instrumentation/modules/@hapi/hapi.js | 141 -- .../modules/@hapi/hapi/basic.js | 581 --------- test/instrumentation/modules/hapi/basic.js | 1139 +++++++++-------- .../set-framework-2.js} | 2 +- test/test.js | 1 - 7 files changed, 588 insertions(+), 1297 deletions(-) delete mode 100644 lib/instrumentation/modules/@hapi/hapi.js delete mode 100644 test/instrumentation/modules/@hapi/hapi/basic.js rename test/instrumentation/modules/{@hapi/hapi/set-framework.js => hapi/set-framework-2.js} (93%) diff --git a/docs/supported-technologies.asciidoc b/docs/supported-technologies.asciidoc index 96033353dd..3b368d2977 100644 --- a/docs/supported-technologies.asciidoc +++ b/docs/supported-technologies.asciidoc @@ -46,6 +46,7 @@ These are the frameworks that we officially support: |Framework |Version |Note |<> |^4.0.0 | |<> |>=9.0.0 <19.0.0 | +|<> |>=17.9.0 <19.0.0 | |<> via koa-router |>=5.2.0 <8.0.0 |Koa doesn't have a built in router, so we can't support Koa directly since we rely on router information for full support. We currently support the most popular Koa router called https://github.com/alexmingoia/koa-router[koa-router] diff --git a/lib/instrumentation/index.js b/lib/instrumentation/index.js index 9ec6cff56b..f0748654a5 100644 --- a/lib/instrumentation/index.js +++ b/lib/instrumentation/index.js @@ -23,8 +23,7 @@ var MODULES = [ 'generic-pool', 'graphql', 'handlebars', - 'hapi', - '@hapi/hapi', + ['hapi', '@hapi/hapi'], 'http', 'https', 'http2', @@ -71,12 +70,17 @@ function Instrumentation (agent) { this._patches = new NamedArray() for (let mod of MODULES) { - this.addPatch(mod, (exports, name, version, enabled) => { - // Lazy require so that we don't have to use `require.resolve` which - // would fail in combination with Webpack. For more info see: - // https://github.com/elastic/apm-agent-nodejs/pull/957 - const patch = require(`./modules/${mod}`) - return patch(exports, name, version, enabled) + if (!Array.isArray(mod)) mod = [mod] + const pathName = mod[0] + + mod.forEach(mod => { + this.addPatch(mod, (exports, name, version, enabled) => { + // Lazy require so that we don't have to use `require.resolve` which + // would fail in combination with Webpack. For more info see: + // https://github.com/elastic/apm-agent-nodejs/pull/957 + const patch = require(`./modules/${pathName}`) + return patch(exports, name, version, enabled) + }) }) } } diff --git a/lib/instrumentation/modules/@hapi/hapi.js b/lib/instrumentation/modules/@hapi/hapi.js deleted file mode 100644 index d39bfea091..0000000000 --- a/lib/instrumentation/modules/@hapi/hapi.js +++ /dev/null @@ -1,141 +0,0 @@ -'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 }) - - agent.logger.debug('shimming hapi.Server.prototype.initialize') - - 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') - } - - // 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) { - 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 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 -} diff --git a/test/instrumentation/modules/@hapi/hapi/basic.js b/test/instrumentation/modules/@hapi/hapi/basic.js deleted file mode 100644 index 4cc669a62a..0000000000 --- a/test/instrumentation/modules/@hapi/hapi/basic.js +++ /dev/null @@ -1,581 +0,0 @@ -'use strict' - -var agent = require('../../../../..').start({ - serviceName: 'test', - secretToken: 'test', - captureExceptions: false, - logLevel: 'fatal', - metricsInterval: 0 -}) - -var pkg = require('@hapi/hapi/package.json') -var semver = require('semver') - -// hapi 17+ requires Node.js 8.9.0 or higher -if (semver.lt(process.version, '8.9.0') && semver.gte(pkg.version, '17.0.0')) process.exit() -// hapi 16.7.0+ requires Node.js 6.0.0 or higher -if (semver.lt(process.version, '6.0.0') && semver.gte(pkg.version, '16.7.0')) process.exit() - -// hapi does not work on early versions of Node.js 10 because of https://github.com/nodejs/node/issues/20516 -// NOTE: Do not use semver.satisfies, as it does not match prereleases -var parsed = semver.parse(process.version) -if (parsed.major === 10 && parsed.minor >= 0 && parsed.minor < 8) process.exit() - -var http = require('http') - -var Hapi = require('@hapi/hapi') -var test = require('tape') - -var mockClient = require('../../../../_mock_http_client') - -var originalCaptureError = agent.captureError - -function noop () {} - -test('extract URL from request', function (t) { - resetAgent(2, function (data) { - t.equal(data.transactions.length, 1) - t.equal(data.errors.length, 1) - var request = data.errors[0].context.request - t.equal(request.method, 'GET') - t.equal(request.url.pathname, '/captureError') - t.equal(request.url.search, '?foo=bar') - t.equal(request.url.raw, '/captureError?foo=bar') - t.equal(request.url.hostname, 'localhost') - t.equal(request.url.port, String(server.info.port)) - t.equal(request.socket.encrypted, false) - server.stop(noop) - t.end() - }) - - agent.captureError = originalCaptureError - - var server = startServer(function (err, port) { - t.error(err) - http.get('http://localhost:' + port + '/captureError?foo=bar') - }) -}) - -test('route naming', function (t) { - t.plan(8) - - resetAgent(1, function (data) { - assert(t, data) - server.stop(noop) - }) - - var server = startServer(function (err, port) { - t.error(err) - http.get('http://localhost:' + port + '/hello', function (res) { - t.equal(res.statusCode, 200) - res.on('data', function (chunk) { - t.equal(chunk.toString(), 'hello world') - }) - res.on('end', function () { - agent.flush() - }) - }) - }) -}) - -test('connectionless', function (t) { - t.plan(1) - - resetAgent() - - var server = makeServer() - initServer(server, function (err) { - server.stop(noop) - t.error(err, 'start error') - }) -}) - -test('connectionless server error logging with Error', function (t) { - t.plan(6) - - var customError = new Error('custom error') - - resetAgent() - - agent.captureError = function (err, opts) { - server.stop(noop) - - t.equal(err, customError) - t.ok(opts.custom) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.ok(opts.custom.data instanceof Error) - } - - var server = makeServer() - initServer(server, function (err) { - t.error(err, 'start error') - - server.log(['error'], customError) - }) -}) - -test('connectionless server error logging with String', function (t) { - t.plan(6) - - var customError = 'custom error' - - resetAgent() - - agent.captureError = function (err, opts) { - server.stop(noop) - - t.equal(err, customError) - t.ok(opts.custom) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.ok(typeof opts.custom.data === 'string') - } - - var server = makeServer() - initServer(server, function (err) { - t.error(err, 'start error') - - server.log(['error'], customError) - }) -}) - -test('connectionless server error logging with Object', function (t) { - t.plan(6) - - var customError = { - error: 'I forgot to turn this into an actual Error' - } - - resetAgent() - - agent.captureError = function (err, opts) { - server.stop(noop) - - t.equal(err, 'hapi server emitted a log event tagged error') - t.ok(opts.custom) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.deepEqual(opts.custom.data, customError) - } - - var server = makeServer() - initServer(server, function (err) { - t.error(err, 'start error') - - server.log(['error'], customError) - }) -}) - -test('server error logging with Error', function (t) { - t.plan(6) - - var customError = new Error('custom error') - - resetAgent() - - agent.captureError = function (err, opts) { - server.stop(noop) - - t.equal(err, customError) - t.ok(opts.custom) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.ok(opts.custom.data instanceof Error) - } - - var server = startServer(function (err) { - t.error(err, 'start error') - - server.log(['error'], customError) - }) -}) - -test('server error logging with Error does not affect event tags', function (t) { - t.plan(8) - - var customError = new Error('custom error') - - resetAgent() - - agent.captureError = function (err, opts) { - server.stop(noop) - - t.equal(err, customError) - t.ok(opts.custom) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.ok(opts.custom.data instanceof Error) - } - - var server = makeServer() - - var emitter = server.events || server - emitter.on('log', function (event, tags) { - t.deepEqual(event.tags, ['error']) - }) - - runServer(server, function (err) { - t.error(err, 'start error') - - emitter.on('log', function (event, tags) { - t.deepEqual(event.tags, ['error']) - }) - - server.log(['error'], customError) - }) -}) - -test('server error logging with String', function (t) { - t.plan(6) - - var customError = 'custom error' - - resetAgent() - - agent.captureError = function (err, opts) { - server.stop(noop) - - t.equal(err, customError) - t.ok(opts.custom) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.ok(typeof opts.custom.data === 'string') - } - - var server = startServer(function (err) { - t.error(err, 'start error') - - server.log(['error'], customError) - }) -}) - -test('server error logging with Object', function (t) { - t.plan(6) - - var customError = { - error: 'I forgot to turn this into an actual Error' - } - - resetAgent() - - agent.captureError = function (err, opts) { - server.stop(noop) - - t.equal(err, 'hapi server emitted a log event tagged error') - t.ok(opts.custom) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.deepEqual(opts.custom.data, customError) - } - - var server = startServer(function (err) { - t.error(err, 'start error') - - server.log(['error'], customError) - }) -}) - -test('request error logging with Error', function (t) { - t.plan(13) - - var customError = new Error('custom error') - - resetAgent(1, function (data) { - assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) - - server.stop(noop) - }) - - agent.captureError = function (err, opts) { - t.equal(err, customError) - t.ok(opts.custom) - t.ok(opts.request) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.ok(opts.custom.data instanceof Error) - } - - var server = makeServer() - - server.route({ - method: 'GET', - path: '/error', - handler: handler(function (request) { - request.log(['error'], customError) - - return 'hello world' - }) - }) - - runServer(server, function (err) { - t.error(err, 'start error') - - http.get('http://localhost:' + server.info.port + '/error', function (res) { - t.equal(res.statusCode, 200) - - res.resume().on('end', function () { - agent.flush() - }) - }) - }) -}) - -test('request error logging with Error does not affect event tags', function (t) { - t.plan(15) - - var customError = new Error('custom error') - - resetAgent(1, function (data) { - assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) - - server.stop(noop) - }) - - agent.captureError = function (err, opts) { - t.equal(err, customError) - t.ok(opts.custom) - t.ok(opts.request) - t.deepEqual(opts.custom.tags, ['elastic-apm', 'error']) - t.false(opts.custom.internals) - t.ok(opts.custom.data instanceof Error) - } - - var server = makeServer() - - server.route({ - method: 'GET', - path: '/error', - handler: handler(function (request) { - request.log(['elastic-apm', 'error'], customError) - - return 'hello world' - }) - }) - - var emitter = server.events || server - emitter.on('request', function (req, event, tags) { - if (event.channel === 'internal') return - t.deepEqual(event.tags, ['elastic-apm', 'error']) - }) - - runServer(server, function (err) { - t.error(err, 'start error') - - emitter.on('request', function (req, event, tags) { - if (event.channel === 'internal') return - t.deepEqual(event.tags, ['elastic-apm', 'error']) - }) - - http.get('http://localhost:' + server.info.port + '/error', function (res) { - t.equal(res.statusCode, 200) - - res.resume().on('end', function () { - agent.flush() - }) - }) - }) -}) - -test('request error logging with String', function (t) { - t.plan(13) - - var customError = 'custom error' - - resetAgent(1, function (data) { - assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) - - server.stop(noop) - }) - - agent.captureError = function (err, opts) { - t.equal(err, customError) - t.ok(opts.custom) - t.ok(opts.request) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.ok(typeof opts.custom.data === 'string') - } - - var server = makeServer() - - server.route({ - method: 'GET', - path: '/error', - handler: handler(function (request) { - request.log(['error'], customError) - - return 'hello world' - }) - }) - - runServer(server, function (err) { - t.error(err, 'start error') - - http.get('http://localhost:' + server.info.port + '/error', function (res) { - t.equal(res.statusCode, 200) - - res.resume().on('end', function () { - agent.flush() - }) - }) - }) -}) - -test('request error logging with Object', function (t) { - t.plan(13) - - var customError = { - error: 'I forgot to turn this into an actual Error' - } - - resetAgent(1, function (data) { - assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) - - server.stop(noop) - }) - - agent.captureError = function (err, opts) { - t.equal(err, 'hapi server emitted a request event tagged error') - t.ok(opts.custom) - t.ok(opts.request) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.deepEqual(opts.custom.data, customError) - } - - var server = makeServer() - - server.route({ - method: 'GET', - path: '/error', - handler: handler(function (request) { - request.log(['error'], customError) - - return 'hello world' - }) - }) - - runServer(server, function (err) { - t.error(err, 'start error') - - http.get('http://localhost:' + server.info.port + '/error', function (res) { - t.equal(res.statusCode, 200) - - res.resume().on('end', function () { - agent.flush() - }) - }) - }) -}) - -test('error handling', function (t) { - t.plan(10) - - resetAgent(1, function (data) { - assert(t, data, { status: 'HTTP 5xx', name: 'GET /error' }) - server.stop(noop) - }) - - agent.captureError = function (err, opts) { - t.equal(err.message, 'foo') - t.ok(opts.request instanceof http.IncomingMessage) - } - - var server = startServer(function (err, port) { - t.error(err) - http.get('http://localhost:' + port + '/error', function (res) { - t.equal(res.statusCode, 500) - res.on('data', function (chunk) { - var data = JSON.parse(chunk.toString()) - t.deepEqual(data, { - statusCode: 500, - error: 'Internal Server Error', - message: 'An internal server error occurred' - }) - }) - res.on('end', function () { - agent.flush() - }) - }) - }) -}) - -function makeServer (opts) { - var server = new Hapi.Server() - - return server -} - -function initServer (server, cb) { - server.initialize().then( - cb.bind(null, null), - cb - ) -} - -function runServer (server, cb) { - server.start().then( - () => cb(null, server.info.port), - cb - ) -} - -function startServer (cb) { - var server = buildServer() - runServer(server, cb) - return server -} - -function handler (fn) { - return fn -} - -function buildServer () { - var server = makeServer() - - server.route({ - method: 'GET', - path: '/hello', - handler: handler(function (request) { - return 'hello world' - }) - }) - server.route({ - method: 'GET', - path: '/error', - handler: handler(function (request) { - throw new Error('foo') - }) - }) - server.route({ - method: 'GET', - path: '/captureError', - handler: handler(function (request) { - agent.captureError(new Error()) - return '' - }) - }) - return server -} - -function assert (t, data, results) { - if (!results) results = {} - results.status = results.status || 'HTTP 2xx' - results.name = results.name || 'GET /hello' - - t.equal(data.transactions.length, 1) - - var trans = data.transactions[0] - - t.equal(trans.name, results.name) - t.equal(trans.type, 'request') - t.equal(trans.result, results.status) - t.equal(trans.context.request.method, 'GET') -} - -function resetAgent (expected, cb) { - agent._instrumentation.currentTransaction = null - agent._transport = mockClient(expected, cb) - agent.captureError = function (err) { throw err } -} diff --git a/test/instrumentation/modules/hapi/basic.js b/test/instrumentation/modules/hapi/basic.js index 88470b8c9a..0aa8037f77 100644 --- a/test/instrumentation/modules/hapi/basic.js +++ b/test/instrumentation/modules/hapi/basic.js @@ -1,5 +1,7 @@ 'use strict' +const moduleNames = ['hapi', '@hapi/hapi']; + var agent = require('../../../..').start({ serviceName: 'test', secretToken: 'test', @@ -8,617 +10,624 @@ var agent = require('../../../..').start({ metricsInterval: 0 }) -var pkg = require('hapi/package.json') -var semver = require('semver') - -// hapi 17+ requires Node.js 8.9.0 or higher -if (semver.lt(process.version, '8.9.0') && semver.gte(pkg.version, '17.0.0')) process.exit() -// hapi 16.7.0+ requires Node.js 6.0.0 or higher -if (semver.lt(process.version, '6.0.0') && semver.gte(pkg.version, '16.7.0')) process.exit() - -// hapi does not work on early versions of Node.js 10 because of https://github.com/nodejs/node/issues/20516 -// NOTE: Do not use semver.satisfies, as it does not match prereleases -var parsed = semver.parse(process.version) -if (parsed.major === 10 && parsed.minor >= 0 && parsed.minor < 8) process.exit() - -var http = require('http') - -var Hapi = require('hapi') -var test = require('tape') - -var mockClient = require('../../../_mock_http_client') - -var originalCaptureError = agent.captureError - -function noop () {} - -test('extract URL from request', function (t) { - resetAgent(2, function (data) { - t.equal(data.transactions.length, 1) - t.equal(data.errors.length, 1) - var request = data.errors[0].context.request - t.equal(request.method, 'GET') - t.equal(request.url.pathname, '/captureError') - t.equal(request.url.search, '?foo=bar') - t.equal(request.url.raw, '/captureError?foo=bar') - t.equal(request.url.hostname, 'localhost') - t.equal(request.url.port, String(server.info.port)) - t.equal(request.socket.encrypted, false) - server.stop(noop) - t.end() - }) - - agent.captureError = originalCaptureError - - var server = startServer(function (err, port) { - t.error(err) - http.get('http://localhost:' + port + '/captureError?foo=bar') - }) -}) - -test('route naming', function (t) { - t.plan(8) - - resetAgent(1, function (data) { - assert(t, data) - server.stop(noop) +moduleNames.forEach((moduleName) => { + + + var pkg = require(`${moduleName}/package.json`) + var semver = require('semver') + + // hapi 17+ requires Node.js 8.9.0 or higher + if (semver.lt(process.version, '8.9.0') && semver.gte(pkg.version, '17.0.0')) process.exit() + // hapi 16.7.0+ requires Node.js 6.0.0 or higher + if (semver.lt(process.version, '6.0.0') && semver.gte(pkg.version, '16.7.0')) process.exit() + + // hapi does not work on early versions of Node.js 10 because of https://github.com/nodejs/node/issues/20516 + // NOTE: Do not use semver.satisfies, as it does not match prereleases + var parsed = semver.parse(process.version) + if (parsed.major === 10 && parsed.minor >= 0 && parsed.minor < 8) process.exit() + + var http = require('http') + + var Hapi = require(moduleName) + var test = require('tape') + + var mockClient = require('../../../_mock_http_client') + + var originalCaptureError = agent.captureError + + function noop () {} + + test('extract URL from request', function (t) { + resetAgent(2, function (data) { + t.equal(data.transactions.length, 1) + t.equal(data.errors.length, 1) + var request = data.errors[0].context.request + t.equal(request.method, 'GET') + t.equal(request.url.pathname, '/captureError') + t.equal(request.url.search, '?foo=bar') + t.equal(request.url.raw, '/captureError?foo=bar') + t.equal(request.url.hostname, 'localhost') + t.equal(request.url.port, String(server.info.port)) + t.equal(request.socket.encrypted, false) + server.stop(noop) + t.end() + }) + + agent.captureError = originalCaptureError + + var server = startServer(function (err, port) { + t.error(err) + http.get('http://localhost:' + port + '/captureError?foo=bar') + }) }) - - var server = startServer(function (err, port) { - t.error(err) - http.get('http://localhost:' + port + '/hello', function (res) { - t.equal(res.statusCode, 200) - res.on('data', function (chunk) { - t.equal(chunk.toString(), 'hello world') - }) - res.on('end', function () { - agent.flush() + + test('route naming', function (t) { + t.plan(8) + + resetAgent(1, function (data) { + assert(t, data) + server.stop(noop) + }) + + var server = startServer(function (err, port) { + t.error(err) + http.get('http://localhost:' + port + '/hello', function (res) { + t.equal(res.statusCode, 200) + res.on('data', function (chunk) { + t.equal(chunk.toString(), 'hello world') + }) + res.on('end', function () { + agent.flush() + }) }) }) }) -}) - -test('connectionless', function (t) { - if (semver.satisfies(pkg.version, '<15.0.2')) { - t.pass('skipping') - t.end() - return - } - - t.plan(1) - - resetAgent() - - var server = makeServer() - initServer(server, function (err) { - server.stop(noop) - t.error(err, 'start error') - }) -}) - -test('connectionless server error logging with Error', function (t) { - if (semver.satisfies(pkg.version, '<15.0.2')) { - t.pass('skipping') - t.end() - return - } - - t.plan(6) - - var customError = new Error('custom error') - - resetAgent() - - agent.captureError = function (err, opts) { - server.stop(noop) - - t.equal(err, customError) - t.ok(opts.custom) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.ok(opts.custom.data instanceof Error) - } - - var server = makeServer() - initServer(server, function (err) { - t.error(err, 'start error') - - server.log(['error'], customError) + + test('connectionless', function (t) { + if (semver.satisfies(pkg.version, '<15.0.2')) { + t.pass('skipping') + t.end() + return + } + + t.plan(1) + + resetAgent() + + var server = makeServer() + initServer(server, function (err) { + server.stop(noop) + t.error(err, 'start error') + }) }) -}) - -test('connectionless server error logging with String', function (t) { - if (semver.satisfies(pkg.version, '<15.0.2')) { - t.pass('skipping') - t.end() - return - } - - t.plan(6) - - var customError = 'custom error' - - resetAgent() - - agent.captureError = function (err, opts) { - server.stop(noop) - - t.equal(err, customError) - t.ok(opts.custom) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.ok(typeof opts.custom.data === 'string') - } - - var server = makeServer() - initServer(server, function (err) { - t.error(err, 'start error') - - server.log(['error'], customError) + + test('connectionless server error logging with Error', function (t) { + if (semver.satisfies(pkg.version, '<15.0.2')) { + t.pass('skipping') + t.end() + return + } + + t.plan(6) + + var customError = new Error('custom error') + + resetAgent() + + agent.captureError = function (err, opts) { + server.stop(noop) + + t.equal(err, customError) + t.ok(opts.custom) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.ok(opts.custom.data instanceof Error) + } + + var server = makeServer() + initServer(server, function (err) { + t.error(err, 'start error') + + server.log(['error'], customError) + }) }) -}) - -test('connectionless server error logging with Object', function (t) { - if (semver.satisfies(pkg.version, '<15.0.2')) { - t.pass('skipping') - t.end() - return - } - - t.plan(6) - - var customError = { - error: 'I forgot to turn this into an actual Error' - } - - resetAgent() - - agent.captureError = function (err, opts) { - server.stop(noop) - - t.equal(err, 'hapi server emitted a log event tagged error') - t.ok(opts.custom) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.deepEqual(opts.custom.data, customError) - } - - var server = makeServer() - initServer(server, function (err) { - t.error(err, 'start error') - - server.log(['error'], customError) + + test('connectionless server error logging with String', function (t) { + if (semver.satisfies(pkg.version, '<15.0.2')) { + t.pass('skipping') + t.end() + return + } + + t.plan(6) + + var customError = 'custom error' + + resetAgent() + + agent.captureError = function (err, opts) { + server.stop(noop) + + t.equal(err, customError) + t.ok(opts.custom) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.ok(typeof opts.custom.data === 'string') + } + + var server = makeServer() + initServer(server, function (err) { + t.error(err, 'start error') + + server.log(['error'], customError) + }) }) -}) - -test('server error logging with Error', function (t) { - t.plan(6) - - var customError = new Error('custom error') - - resetAgent() - - agent.captureError = function (err, opts) { - server.stop(noop) - - t.equal(err, customError) - t.ok(opts.custom) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.ok(opts.custom.data instanceof Error) - } - - var server = startServer(function (err) { - t.error(err, 'start error') - - server.log(['error'], customError) + + test('connectionless server error logging with Object', function (t) { + if (semver.satisfies(pkg.version, '<15.0.2')) { + t.pass('skipping') + t.end() + return + } + + t.plan(6) + + var customError = { + error: 'I forgot to turn this into an actual Error' + } + + resetAgent() + + agent.captureError = function (err, opts) { + server.stop(noop) + + t.equal(err, 'hapi server emitted a log event tagged error') + t.ok(opts.custom) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.deepEqual(opts.custom.data, customError) + } + + var server = makeServer() + initServer(server, function (err) { + t.error(err, 'start error') + + server.log(['error'], customError) + }) }) -}) - -test('server error logging with Error does not affect event tags', function (t) { - t.plan(8) - - var customError = new Error('custom error') - - resetAgent() - - agent.captureError = function (err, opts) { - server.stop(noop) - - t.equal(err, customError) - t.ok(opts.custom) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.ok(opts.custom.data instanceof Error) - } - - var server = makeServer() - - var emitter = server.events || server - emitter.on('log', function (event, tags) { - t.deepEqual(event.tags, ['error']) + + test('server error logging with Error', function (t) { + t.plan(6) + + var customError = new Error('custom error') + + resetAgent() + + agent.captureError = function (err, opts) { + server.stop(noop) + + t.equal(err, customError) + t.ok(opts.custom) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.ok(opts.custom.data instanceof Error) + } + + var server = startServer(function (err) { + t.error(err, 'start error') + + server.log(['error'], customError) + }) }) - - runServer(server, function (err) { - t.error(err, 'start error') - + + test('server error logging with Error does not affect event tags', function (t) { + t.plan(8) + + var customError = new Error('custom error') + + resetAgent() + + agent.captureError = function (err, opts) { + server.stop(noop) + + t.equal(err, customError) + t.ok(opts.custom) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.ok(opts.custom.data instanceof Error) + } + + var server = makeServer() + + var emitter = server.events || server emitter.on('log', function (event, tags) { t.deepEqual(event.tags, ['error']) }) - - server.log(['error'], customError) - }) -}) - -test('server error logging with String', function (t) { - t.plan(6) - - var customError = 'custom error' - - resetAgent() - - agent.captureError = function (err, opts) { - server.stop(noop) - - t.equal(err, customError) - t.ok(opts.custom) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.ok(typeof opts.custom.data === 'string') - } - - var server = startServer(function (err) { - t.error(err, 'start error') - - server.log(['error'], customError) - }) -}) - -test('server error logging with Object', function (t) { - t.plan(6) - - var customError = { - error: 'I forgot to turn this into an actual Error' - } - - resetAgent() - - agent.captureError = function (err, opts) { - server.stop(noop) - - t.equal(err, 'hapi server emitted a log event tagged error') - t.ok(opts.custom) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.deepEqual(opts.custom.data, customError) - } - - var server = startServer(function (err) { - t.error(err, 'start error') - - server.log(['error'], customError) + + runServer(server, function (err) { + t.error(err, 'start error') + + emitter.on('log', function (event, tags) { + t.deepEqual(event.tags, ['error']) + }) + + server.log(['error'], customError) + }) }) -}) - -test('request error logging with Error', function (t) { - t.plan(13) - - var customError = new Error('custom error') - - resetAgent(1, function (data) { - assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) - - server.stop(noop) + + test('server error logging with String', function (t) { + t.plan(6) + + var customError = 'custom error' + + resetAgent() + + agent.captureError = function (err, opts) { + server.stop(noop) + + t.equal(err, customError) + t.ok(opts.custom) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.ok(typeof opts.custom.data === 'string') + } + + var server = startServer(function (err) { + t.error(err, 'start error') + + server.log(['error'], customError) + }) }) - - agent.captureError = function (err, opts) { - t.equal(err, customError) - t.ok(opts.custom) - t.ok(opts.request) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.ok(opts.custom.data instanceof Error) - } - - var server = makeServer() - - server.route({ - method: 'GET', - path: '/error', - handler: handler(function (request) { - request.log(['error'], customError) - - return 'hello world' + + test('server error logging with Object', function (t) { + t.plan(6) + + var customError = { + error: 'I forgot to turn this into an actual Error' + } + + resetAgent() + + agent.captureError = function (err, opts) { + server.stop(noop) + + t.equal(err, 'hapi server emitted a log event tagged error') + t.ok(opts.custom) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.deepEqual(opts.custom.data, customError) + } + + var server = startServer(function (err) { + t.error(err, 'start error') + + server.log(['error'], customError) }) }) - - runServer(server, function (err) { - t.error(err, 'start error') - - http.get('http://localhost:' + server.info.port + '/error', function (res) { - t.equal(res.statusCode, 200) - - res.resume().on('end', function () { - agent.flush() + + test('request error logging with Error', function (t) { + t.plan(13) + + var customError = new Error('custom error') + + resetAgent(1, function (data) { + assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) + + server.stop(noop) + }) + + agent.captureError = function (err, opts) { + t.equal(err, customError) + t.ok(opts.custom) + t.ok(opts.request) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.ok(opts.custom.data instanceof Error) + } + + var server = makeServer() + + server.route({ + method: 'GET', + path: '/error', + handler: handler(function (request) { + request.log(['error'], customError) + + return 'hello world' }) }) - }) -}) - -test('request error logging with Error does not affect event tags', function (t) { - t.plan(15) - - var customError = new Error('custom error') - - resetAgent(1, function (data) { - assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) - - server.stop(noop) - }) - - agent.captureError = function (err, opts) { - t.equal(err, customError) - t.ok(opts.custom) - t.ok(opts.request) - t.deepEqual(opts.custom.tags, ['elastic-apm', 'error']) - t.false(opts.custom.internals) - t.ok(opts.custom.data instanceof Error) - } - - var server = makeServer() - - server.route({ - method: 'GET', - path: '/error', - handler: handler(function (request) { - request.log(['elastic-apm', 'error'], customError) - - return 'hello world' + + runServer(server, function (err) { + t.error(err, 'start error') + + http.get('http://localhost:' + server.info.port + '/error', function (res) { + t.equal(res.statusCode, 200) + + res.resume().on('end', function () { + agent.flush() + }) + }) }) }) - - var emitter = server.events || server - emitter.on('request', function (req, event, tags) { - if (event.channel === 'internal') return - t.deepEqual(event.tags, ['elastic-apm', 'error']) - }) - - runServer(server, function (err) { - t.error(err, 'start error') - + + test('request error logging with Error does not affect event tags', function (t) { + t.plan(15) + + var customError = new Error('custom error') + + resetAgent(1, function (data) { + assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) + + server.stop(noop) + }) + + agent.captureError = function (err, opts) { + t.equal(err, customError) + t.ok(opts.custom) + t.ok(opts.request) + t.deepEqual(opts.custom.tags, ['elastic-apm', 'error']) + t.false(opts.custom.internals) + t.ok(opts.custom.data instanceof Error) + } + + var server = makeServer() + + server.route({ + method: 'GET', + path: '/error', + handler: handler(function (request) { + request.log(['elastic-apm', 'error'], customError) + + return 'hello world' + }) + }) + + var emitter = server.events || server emitter.on('request', function (req, event, tags) { if (event.channel === 'internal') return t.deepEqual(event.tags, ['elastic-apm', 'error']) }) - - http.get('http://localhost:' + server.info.port + '/error', function (res) { - t.equal(res.statusCode, 200) - - res.resume().on('end', function () { - agent.flush() + + runServer(server, function (err) { + t.error(err, 'start error') + + emitter.on('request', function (req, event, tags) { + if (event.channel === 'internal') return + t.deepEqual(event.tags, ['elastic-apm', 'error']) + }) + + http.get('http://localhost:' + server.info.port + '/error', function (res) { + t.equal(res.statusCode, 200) + + res.resume().on('end', function () { + agent.flush() + }) }) }) }) -}) - -test('request error logging with String', function (t) { - t.plan(13) - - var customError = 'custom error' - - resetAgent(1, function (data) { - assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) - - server.stop(noop) - }) - - agent.captureError = function (err, opts) { - t.equal(err, customError) - t.ok(opts.custom) - t.ok(opts.request) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.ok(typeof opts.custom.data === 'string') - } - - var server = makeServer() - - server.route({ - method: 'GET', - path: '/error', - handler: handler(function (request) { - request.log(['error'], customError) - - return 'hello world' + + test('request error logging with String', function (t) { + t.plan(13) + + var customError = 'custom error' + + resetAgent(1, function (data) { + assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) + + server.stop(noop) }) - }) - - runServer(server, function (err) { - t.error(err, 'start error') - - http.get('http://localhost:' + server.info.port + '/error', function (res) { - t.equal(res.statusCode, 200) - - res.resume().on('end', function () { - agent.flush() + + agent.captureError = function (err, opts) { + t.equal(err, customError) + t.ok(opts.custom) + t.ok(opts.request) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.ok(typeof opts.custom.data === 'string') + } + + var server = makeServer() + + server.route({ + method: 'GET', + path: '/error', + handler: handler(function (request) { + request.log(['error'], customError) + + return 'hello world' }) }) - }) -}) - -test('request error logging with Object', function (t) { - t.plan(13) - - var customError = { - error: 'I forgot to turn this into an actual Error' - } - - resetAgent(1, function (data) { - assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) - - server.stop(noop) - }) - - agent.captureError = function (err, opts) { - t.equal(err, 'hapi server emitted a request event tagged error') - t.ok(opts.custom) - t.ok(opts.request) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.deepEqual(opts.custom.data, customError) - } - - var server = makeServer() - - server.route({ - method: 'GET', - path: '/error', - handler: handler(function (request) { - request.log(['error'], customError) - - return 'hello world' + + runServer(server, function (err) { + t.error(err, 'start error') + + http.get('http://localhost:' + server.info.port + '/error', function (res) { + t.equal(res.statusCode, 200) + + res.resume().on('end', function () { + agent.flush() + }) + }) }) }) - - runServer(server, function (err) { - t.error(err, 'start error') - - http.get('http://localhost:' + server.info.port + '/error', function (res) { - t.equal(res.statusCode, 200) - - res.resume().on('end', function () { - agent.flush() + + test('request error logging with Object', function (t) { + t.plan(13) + + var customError = { + error: 'I forgot to turn this into an actual Error' + } + + resetAgent(1, function (data) { + assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) + + server.stop(noop) + }) + + agent.captureError = function (err, opts) { + t.equal(err, 'hapi server emitted a request event tagged error') + t.ok(opts.custom) + t.ok(opts.request) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.deepEqual(opts.custom.data, customError) + } + + var server = makeServer() + + server.route({ + method: 'GET', + path: '/error', + handler: handler(function (request) { + request.log(['error'], customError) + + return 'hello world' }) }) - }) -}) - -test('error handling', function (t) { - t.plan(10) - - resetAgent(1, function (data) { - assert(t, data, { status: 'HTTP 5xx', name: 'GET /error' }) - server.stop(noop) - }) - - agent.captureError = function (err, opts) { - t.equal(err.message, 'foo') - t.ok(opts.request instanceof http.IncomingMessage) - } - - var server = startServer(function (err, port) { - t.error(err) - http.get('http://localhost:' + port + '/error', function (res) { - t.equal(res.statusCode, 500) - res.on('data', function (chunk) { - var data = JSON.parse(chunk.toString()) - t.deepEqual(data, { - statusCode: 500, - error: 'Internal Server Error', - message: 'An internal server error occurred' + + runServer(server, function (err) { + t.error(err, 'start error') + + http.get('http://localhost:' + server.info.port + '/error', function (res) { + t.equal(res.statusCode, 200) + + res.resume().on('end', function () { + agent.flush() }) }) - res.on('end', function () { - agent.flush() + }) + }) + + test('error handling', function (t) { + t.plan(10) + + resetAgent(1, function (data) { + assert(t, data, { status: 'HTTP 5xx', name: 'GET /error' }) + server.stop(noop) + }) + + agent.captureError = function (err, opts) { + t.equal(err.message, 'foo') + t.ok(opts.request instanceof http.IncomingMessage) + } + + var server = startServer(function (err, port) { + t.error(err) + http.get('http://localhost:' + port + '/error', function (res) { + t.equal(res.statusCode, 500) + res.on('data', function (chunk) { + var data = JSON.parse(chunk.toString()) + t.deepEqual(data, { + statusCode: 500, + error: 'Internal Server Error', + message: 'An internal server error occurred' + }) + }) + res.on('end', function () { + agent.flush() + }) }) }) }) -}) - -function makeServer (opts) { - var server = new Hapi.Server() - if (semver.satisfies(pkg.version, '<17')) { - server.connection(opts) + + function makeServer (opts) { + var server = new Hapi.Server() + if (semver.satisfies(pkg.version, '<17')) { + server.connection(opts) + } + return server } - return server -} - -function initServer (server, cb) { - if (semver.satisfies(pkg.version, '<17')) { - server.initialize(cb) - } else { - server.initialize().then( - cb.bind(null, null), - cb - ) + + function initServer (server, cb) { + if (semver.satisfies(pkg.version, '<17')) { + server.initialize(cb) + } else { + server.initialize().then( + cb.bind(null, null), + cb + ) + } } -} - -function runServer (server, cb) { - if (semver.satisfies(pkg.version, '<17')) { - server.start(function (err) { - if (err) throw err - cb(null, server.info.port) - }) - } else { - server.start().then( - () => cb(null, server.info.port), - cb - ) + + function runServer (server, cb) { + if (semver.satisfies(pkg.version, '<17')) { + server.start(function (err) { + if (err) throw err + cb(null, server.info.port) + }) + } else { + server.start().then( + () => cb(null, server.info.port), + cb + ) + } } -} - -function startServer (cb) { - var server = buildServer() - runServer(server, cb) - return server -} - -function handler (fn) { - if (semver.satisfies(pkg.version, '>=17')) return fn - return function (request, reply) { - var p = new Promise(function (resolve, reject) { - resolve(fn(request)) - }) - p.then(reply, reply) + + function startServer (cb) { + var server = buildServer() + runServer(server, cb) + return server } -} - -function buildServer () { - var server = makeServer() - - server.route({ - method: 'GET', - path: '/hello', - handler: handler(function (request) { - return 'hello world' + + function handler (fn) { + if (semver.satisfies(pkg.version, '>=17')) return fn + return function (request, reply) { + var p = new Promise(function (resolve, reject) { + resolve(fn(request)) + }) + p.then(reply, reply) + } + } + + function buildServer () { + var server = makeServer() + + server.route({ + method: 'GET', + path: '/hello', + handler: handler(function (request) { + return 'hello world' + }) }) - }) - server.route({ - method: 'GET', - path: '/error', - handler: handler(function (request) { - throw new Error('foo') + server.route({ + method: 'GET', + path: '/error', + handler: handler(function (request) { + throw new Error('foo') + }) }) - }) - server.route({ - method: 'GET', - path: '/captureError', - handler: handler(function (request) { - agent.captureError(new Error()) - return '' + server.route({ + method: 'GET', + path: '/captureError', + handler: handler(function (request) { + agent.captureError(new Error()) + return '' + }) }) - }) - return server -} - -function assert (t, data, results) { - if (!results) results = {} - results.status = results.status || 'HTTP 2xx' - results.name = results.name || 'GET /hello' - - t.equal(data.transactions.length, 1) + return server + } + + function assert (t, data, results) { + if (!results) results = {} + results.status = results.status || 'HTTP 2xx' + results.name = results.name || 'GET /hello' + + t.equal(data.transactions.length, 1) + + var trans = data.transactions[0] + + t.equal(trans.name, results.name) + t.equal(trans.type, 'request') + t.equal(trans.result, results.status) + t.equal(trans.context.request.method, 'GET') + } + + function resetAgent (expected, cb) { + agent._instrumentation.currentTransaction = null + agent._transport = mockClient(expected, cb) + agent.captureError = function (err) { throw err } + } +}); - var trans = data.transactions[0] - t.equal(trans.name, results.name) - t.equal(trans.type, 'request') - t.equal(trans.result, results.status) - t.equal(trans.context.request.method, 'GET') -} -function resetAgent (expected, cb) { - agent._instrumentation.currentTransaction = null - agent._transport = mockClient(expected, cb) - agent.captureError = function (err) { throw err } -} diff --git a/test/instrumentation/modules/@hapi/hapi/set-framework.js b/test/instrumentation/modules/hapi/set-framework-2.js similarity index 93% rename from test/instrumentation/modules/@hapi/hapi/set-framework.js rename to test/instrumentation/modules/hapi/set-framework-2.js index 51bb2d0b00..2df8c15567 100644 --- a/test/instrumentation/modules/@hapi/hapi/set-framework.js +++ b/test/instrumentation/modules/hapi/set-framework-2.js @@ -1,6 +1,6 @@ 'use strict' -const agent = require('../../../../..').start({ +const agent = require('../../../..').start({ captureExceptions: true, metricsInterval: 0 }) diff --git a/test/test.js b/test/test.js index cc7a76f2b5..f68fddc402 100644 --- a/test/test.js +++ b/test/test.js @@ -77,7 +77,6 @@ var directories = [ 'test/instrumentation/modules/express', 'test/instrumentation/modules/fastify', 'test/instrumentation/modules/hapi', - 'test/instrumentation/modules/@hapi/hapi', 'test/instrumentation/modules/http', 'test/instrumentation/modules/koa', 'test/instrumentation/modules/koa-router', From ccae340bf7d6915af82e6ec4995e92e9abdf4206 Mon Sep 17 00:00:00 2001 From: Brad Gardner Date: Wed, 29 May 2019 13:08:13 -0400 Subject: [PATCH 04/11] test updates --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index fbf6125788..8acaa6a0d3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -100,7 +100,7 @@ jobs: - node_js: '12' if: type IN (cron, pull_request) AND NOT branch =~ ^greenkeeper/.* - env: TAV=ws,graphql,express-graphql,elasticsearch,hapi,express,express-queue + env: TAV=ws,graphql,express-graphql,elasticsearch,hapi,@hapi/hapi,express,express-queue script: tav --quiet - node_js: '12' @@ -127,7 +127,7 @@ jobs: - node_js: '11' if: type IN (cron, pull_request) AND NOT branch =~ ^greenkeeper/.* - env: TAV=ws,graphql,express-graphql,elasticsearch,hapi,express,express-queue + env: TAV=ws,graphql,express-graphql,elasticsearch,hapi,@hapi/hapi,express,express-queue script: tav --quiet - node_js: '11' @@ -154,7 +154,7 @@ jobs: - node_js: '10' if: type IN (cron, pull_request) AND NOT branch =~ ^greenkeeper/.* - env: TAV=ws,graphql,express-graphql,elasticsearch,hapi,express,express-queue + env: TAV=ws,graphql,express-graphql,elasticsearch,hapi,@hapi/hapi,express,express-queue script: tav --quiet - node_js: '10' @@ -181,7 +181,7 @@ jobs: - node_js: '8' if: type IN (cron, pull_request) AND NOT branch =~ ^greenkeeper/.* - env: TAV=ws,graphql,express-graphql,elasticsearch,hapi,express,express-queue + env: TAV=ws,graphql,express-graphql,elasticsearch,hapi,@hapi/hapi,express,express-queue script: tav --quiet - node_js: '8' @@ -208,7 +208,7 @@ jobs: - node_js: '6' if: type IN (cron, pull_request) AND NOT branch =~ ^greenkeeper/.* - env: TAV=ws,graphql,express-graphql,elasticsearch,hapi,express,express-queue + env: TAV=ws,graphql,express-graphql,elasticsearch,hapi,@hapi/hapi,express,express-queue script: tav --quiet - node_js: '6' From 329f5bcf851c34a0907f6b01a4213fee3276308f Mon Sep 17 00:00:00 2001 From: Brad Gardner Date: Wed, 29 May 2019 13:18:04 -0400 Subject: [PATCH 05/11] linting corrections --- test/instrumentation/modules/hapi/basic.js | 277 ++++++++++----------- 1 file changed, 136 insertions(+), 141 deletions(-) diff --git a/test/instrumentation/modules/hapi/basic.js b/test/instrumentation/modules/hapi/basic.js index 0aa8037f77..5598598765 100644 --- a/test/instrumentation/modules/hapi/basic.js +++ b/test/instrumentation/modules/hapi/basic.js @@ -1,6 +1,6 @@ 'use strict' -const moduleNames = ['hapi', '@hapi/hapi']; +const moduleNames = ['hapi', '@hapi/hapi'] var agent = require('../../../..').start({ serviceName: 'test', @@ -11,32 +11,30 @@ var agent = require('../../../..').start({ }) moduleNames.forEach((moduleName) => { - - var pkg = require(`${moduleName}/package.json`) var semver = require('semver') - + // hapi 17+ requires Node.js 8.9.0 or higher if (semver.lt(process.version, '8.9.0') && semver.gte(pkg.version, '17.0.0')) process.exit() // hapi 16.7.0+ requires Node.js 6.0.0 or higher if (semver.lt(process.version, '6.0.0') && semver.gte(pkg.version, '16.7.0')) process.exit() - + // hapi does not work on early versions of Node.js 10 because of https://github.com/nodejs/node/issues/20516 // NOTE: Do not use semver.satisfies, as it does not match prereleases var parsed = semver.parse(process.version) if (parsed.major === 10 && parsed.minor >= 0 && parsed.minor < 8) process.exit() - + var http = require('http') - + var Hapi = require(moduleName) var test = require('tape') - + var mockClient = require('../../../_mock_http_client') - + var originalCaptureError = agent.captureError - + function noop () {} - + test('extract URL from request', function (t) { resetAgent(2, function (data) { t.equal(data.transactions.length, 1) @@ -52,23 +50,23 @@ moduleNames.forEach((moduleName) => { server.stop(noop) t.end() }) - + agent.captureError = originalCaptureError - + var server = startServer(function (err, port) { t.error(err) http.get('http://localhost:' + port + '/captureError?foo=bar') }) }) - + test('route naming', function (t) { t.plan(8) - + resetAgent(1, function (data) { assert(t, data) server.stop(noop) }) - + var server = startServer(function (err, port) { t.error(err) http.get('http://localhost:' + port + '/hello', function (res) { @@ -82,240 +80,240 @@ moduleNames.forEach((moduleName) => { }) }) }) - + test('connectionless', function (t) { if (semver.satisfies(pkg.version, '<15.0.2')) { t.pass('skipping') t.end() return } - + t.plan(1) - + resetAgent() - + var server = makeServer() initServer(server, function (err) { server.stop(noop) t.error(err, 'start error') }) }) - + test('connectionless server error logging with Error', function (t) { if (semver.satisfies(pkg.version, '<15.0.2')) { t.pass('skipping') t.end() return } - + t.plan(6) - + var customError = new Error('custom error') - + resetAgent() - + agent.captureError = function (err, opts) { server.stop(noop) - + t.equal(err, customError) t.ok(opts.custom) t.deepEqual(opts.custom.tags, ['error']) t.false(opts.custom.internals) t.ok(opts.custom.data instanceof Error) } - + var server = makeServer() initServer(server, function (err) { t.error(err, 'start error') - + server.log(['error'], customError) }) }) - + test('connectionless server error logging with String', function (t) { if (semver.satisfies(pkg.version, '<15.0.2')) { t.pass('skipping') t.end() return } - + t.plan(6) - + var customError = 'custom error' - + resetAgent() - + agent.captureError = function (err, opts) { server.stop(noop) - + t.equal(err, customError) t.ok(opts.custom) t.deepEqual(opts.custom.tags, ['error']) t.false(opts.custom.internals) t.ok(typeof opts.custom.data === 'string') } - + var server = makeServer() initServer(server, function (err) { t.error(err, 'start error') - + server.log(['error'], customError) }) }) - + test('connectionless server error logging with Object', function (t) { if (semver.satisfies(pkg.version, '<15.0.2')) { t.pass('skipping') t.end() return } - + t.plan(6) - + var customError = { error: 'I forgot to turn this into an actual Error' } - + resetAgent() - + agent.captureError = function (err, opts) { server.stop(noop) - + t.equal(err, 'hapi server emitted a log event tagged error') t.ok(opts.custom) t.deepEqual(opts.custom.tags, ['error']) t.false(opts.custom.internals) t.deepEqual(opts.custom.data, customError) } - + var server = makeServer() initServer(server, function (err) { t.error(err, 'start error') - + server.log(['error'], customError) }) }) - + test('server error logging with Error', function (t) { t.plan(6) - + var customError = new Error('custom error') - + resetAgent() - + agent.captureError = function (err, opts) { server.stop(noop) - + t.equal(err, customError) t.ok(opts.custom) t.deepEqual(opts.custom.tags, ['error']) t.false(opts.custom.internals) t.ok(opts.custom.data instanceof Error) } - + var server = startServer(function (err) { t.error(err, 'start error') - + server.log(['error'], customError) }) }) - + test('server error logging with Error does not affect event tags', function (t) { t.plan(8) - + var customError = new Error('custom error') - + resetAgent() - + agent.captureError = function (err, opts) { server.stop(noop) - + t.equal(err, customError) t.ok(opts.custom) t.deepEqual(opts.custom.tags, ['error']) t.false(opts.custom.internals) t.ok(opts.custom.data instanceof Error) } - + var server = makeServer() - + var emitter = server.events || server emitter.on('log', function (event, tags) { t.deepEqual(event.tags, ['error']) }) - + runServer(server, function (err) { t.error(err, 'start error') - + emitter.on('log', function (event, tags) { t.deepEqual(event.tags, ['error']) }) - + server.log(['error'], customError) }) }) - + test('server error logging with String', function (t) { t.plan(6) - + var customError = 'custom error' - + resetAgent() - + agent.captureError = function (err, opts) { server.stop(noop) - + t.equal(err, customError) t.ok(opts.custom) t.deepEqual(opts.custom.tags, ['error']) t.false(opts.custom.internals) t.ok(typeof opts.custom.data === 'string') } - + var server = startServer(function (err) { t.error(err, 'start error') - + server.log(['error'], customError) }) }) - + test('server error logging with Object', function (t) { t.plan(6) - + var customError = { error: 'I forgot to turn this into an actual Error' } - + resetAgent() - + agent.captureError = function (err, opts) { server.stop(noop) - + t.equal(err, 'hapi server emitted a log event tagged error') t.ok(opts.custom) t.deepEqual(opts.custom.tags, ['error']) t.false(opts.custom.internals) t.deepEqual(opts.custom.data, customError) } - + var server = startServer(function (err) { t.error(err, 'start error') - + server.log(['error'], customError) }) }) - + test('request error logging with Error', function (t) { t.plan(13) - + var customError = new Error('custom error') - + resetAgent(1, function (data) { assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) - + server.stop(noop) }) - + agent.captureError = function (err, opts) { t.equal(err, customError) t.ok(opts.custom) @@ -324,43 +322,43 @@ moduleNames.forEach((moduleName) => { t.false(opts.custom.internals) t.ok(opts.custom.data instanceof Error) } - + var server = makeServer() - + server.route({ method: 'GET', path: '/error', handler: handler(function (request) { request.log(['error'], customError) - + return 'hello world' }) }) - + runServer(server, function (err) { t.error(err, 'start error') - + http.get('http://localhost:' + server.info.port + '/error', function (res) { t.equal(res.statusCode, 200) - + res.resume().on('end', function () { agent.flush() }) }) }) }) - + test('request error logging with Error does not affect event tags', function (t) { t.plan(15) - + var customError = new Error('custom error') - + resetAgent(1, function (data) { assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) - + server.stop(noop) }) - + agent.captureError = function (err, opts) { t.equal(err, customError) t.ok(opts.custom) @@ -369,54 +367,54 @@ moduleNames.forEach((moduleName) => { t.false(opts.custom.internals) t.ok(opts.custom.data instanceof Error) } - + var server = makeServer() - + server.route({ method: 'GET', path: '/error', handler: handler(function (request) { request.log(['elastic-apm', 'error'], customError) - + return 'hello world' }) }) - + var emitter = server.events || server emitter.on('request', function (req, event, tags) { if (event.channel === 'internal') return t.deepEqual(event.tags, ['elastic-apm', 'error']) }) - + runServer(server, function (err) { t.error(err, 'start error') - + emitter.on('request', function (req, event, tags) { if (event.channel === 'internal') return t.deepEqual(event.tags, ['elastic-apm', 'error']) }) - + http.get('http://localhost:' + server.info.port + '/error', function (res) { t.equal(res.statusCode, 200) - + res.resume().on('end', function () { agent.flush() }) }) }) }) - + test('request error logging with String', function (t) { t.plan(13) - + var customError = 'custom error' - + resetAgent(1, function (data) { assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) - + server.stop(noop) }) - + agent.captureError = function (err, opts) { t.equal(err, customError) t.ok(opts.custom) @@ -425,45 +423,45 @@ moduleNames.forEach((moduleName) => { t.false(opts.custom.internals) t.ok(typeof opts.custom.data === 'string') } - + var server = makeServer() - + server.route({ method: 'GET', path: '/error', handler: handler(function (request) { request.log(['error'], customError) - + return 'hello world' }) }) - + runServer(server, function (err) { t.error(err, 'start error') - + http.get('http://localhost:' + server.info.port + '/error', function (res) { t.equal(res.statusCode, 200) - + res.resume().on('end', function () { agent.flush() }) }) }) }) - + test('request error logging with Object', function (t) { t.plan(13) - + var customError = { error: 'I forgot to turn this into an actual Error' } - + resetAgent(1, function (data) { assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) - + server.stop(noop) }) - + agent.captureError = function (err, opts) { t.equal(err, 'hapi server emitted a request event tagged error') t.ok(opts.custom) @@ -472,45 +470,45 @@ moduleNames.forEach((moduleName) => { t.false(opts.custom.internals) t.deepEqual(opts.custom.data, customError) } - + var server = makeServer() - + server.route({ method: 'GET', path: '/error', handler: handler(function (request) { request.log(['error'], customError) - + return 'hello world' }) }) - + runServer(server, function (err) { t.error(err, 'start error') - + http.get('http://localhost:' + server.info.port + '/error', function (res) { t.equal(res.statusCode, 200) - + res.resume().on('end', function () { agent.flush() }) }) }) }) - + test('error handling', function (t) { t.plan(10) - + resetAgent(1, function (data) { assert(t, data, { status: 'HTTP 5xx', name: 'GET /error' }) server.stop(noop) }) - + agent.captureError = function (err, opts) { t.equal(err.message, 'foo') t.ok(opts.request instanceof http.IncomingMessage) } - + var server = startServer(function (err, port) { t.error(err) http.get('http://localhost:' + port + '/error', function (res) { @@ -529,7 +527,7 @@ moduleNames.forEach((moduleName) => { }) }) }) - + function makeServer (opts) { var server = new Hapi.Server() if (semver.satisfies(pkg.version, '<17')) { @@ -537,7 +535,7 @@ moduleNames.forEach((moduleName) => { } return server } - + function initServer (server, cb) { if (semver.satisfies(pkg.version, '<17')) { server.initialize(cb) @@ -548,7 +546,7 @@ moduleNames.forEach((moduleName) => { ) } } - + function runServer (server, cb) { if (semver.satisfies(pkg.version, '<17')) { server.start(function (err) { @@ -562,13 +560,13 @@ moduleNames.forEach((moduleName) => { ) } } - + function startServer (cb) { var server = buildServer() runServer(server, cb) return server } - + function handler (fn) { if (semver.satisfies(pkg.version, '>=17')) return fn return function (request, reply) { @@ -578,10 +576,10 @@ moduleNames.forEach((moduleName) => { p.then(reply, reply) } } - + function buildServer () { var server = makeServer() - + server.route({ method: 'GET', path: '/hello', @@ -606,28 +604,25 @@ moduleNames.forEach((moduleName) => { }) return server } - + function assert (t, data, results) { if (!results) results = {} results.status = results.status || 'HTTP 2xx' results.name = results.name || 'GET /hello' - + t.equal(data.transactions.length, 1) - + var trans = data.transactions[0] - + t.equal(trans.name, results.name) t.equal(trans.type, 'request') t.equal(trans.result, results.status) t.equal(trans.context.request.method, 'GET') } - + function resetAgent (expected, cb) { agent._instrumentation.currentTransaction = null agent._transport = mockClient(expected, cb) agent.captureError = function (err) { throw err } } -}); - - - +}) From 97324d6522c5768d193b13dc0c2a84f31cdbabc1 Mon Sep 17 00:00:00 2001 From: Brad Gardner Date: Wed, 29 May 2019 13:42:02 -0400 Subject: [PATCH 06/11] updated config test --- test/config.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/config.js b/test/config.js index fc8039bd06..04134b0533 100644 --- a/test/config.js +++ b/test/config.js @@ -637,7 +637,13 @@ test('disableInstrumentations', function (t) { } for (const mod of modules) { - require(mod) + if (Array.isArray(mod)) { + for (const subMod of mod) { + require(subMod) + } + } else { + require(mod) + } } t.deepEqual(selectionSet, found, 'disabled all selected modules') From 08b13c9e2e842488ad8267c91a4e7fa70605b5be Mon Sep 17 00:00:00 2001 From: Brad Gardner Date: Sun, 30 Jun 2019 08:50:03 -0400 Subject: [PATCH 07/11] split hapi basic file into 2 with a shared implementation --- .tav.yml | 9 +- .../modules/hapi/basic-legacy-path.js | 3 + test/instrumentation/modules/hapi/basic.js | 627 +----------------- test/instrumentation/modules/hapi/shared.js | 625 +++++++++++++++++ 4 files changed, 635 insertions(+), 629 deletions(-) create mode 100644 test/instrumentation/modules/hapi/basic-legacy-path.js create mode 100644 test/instrumentation/modules/hapi/shared.js diff --git a/.tav.yml b/.tav.yml index 8207c0f0da..14a6cb0e5b 100644 --- a/.tav.yml +++ b/.tav.yml @@ -210,16 +210,19 @@ hapi-no-async-await: name: hapi versions: '>=9.0.1 <17.0.0' commands: - - node test/instrumentation/modules/hapi/basic.js + - node test/instrumentation/modules/hapi/basic-legacy-path.js - node test/instrumentation/modules/hapi/set-framework.js hapi-async-await: name: hapi node: '>=8.2' versions: '>=17.0.0' commands: - - node test/instrumentation/modules/hapi/basic.js + - node test/instrumentation/modules/hapi/basic-legacy-path.js - node test/instrumentation/modules/hapi/set-framework.js - +'@hapi/hapi': + node: '>=8.2' + versions: '>=17.0.0' + commands: node test/instrumentation/modules/hapi/basic.js tedious: name: tedious versions: '>=1.9 <2.7 || 3.x || >4.0.0' diff --git a/test/instrumentation/modules/hapi/basic-legacy-path.js b/test/instrumentation/modules/hapi/basic-legacy-path.js new file mode 100644 index 0000000000..da1cd2050d --- /dev/null +++ b/test/instrumentation/modules/hapi/basic-legacy-path.js @@ -0,0 +1,3 @@ +'use strict' + +require('./shared')('hapi') diff --git a/test/instrumentation/modules/hapi/basic.js b/test/instrumentation/modules/hapi/basic.js index 5598598765..8e422da9db 100644 --- a/test/instrumentation/modules/hapi/basic.js +++ b/test/instrumentation/modules/hapi/basic.js @@ -1,628 +1,3 @@ 'use strict' -const moduleNames = ['hapi', '@hapi/hapi'] - -var agent = require('../../../..').start({ - serviceName: 'test', - secretToken: 'test', - captureExceptions: false, - logLevel: 'fatal', - metricsInterval: 0 -}) - -moduleNames.forEach((moduleName) => { - var pkg = require(`${moduleName}/package.json`) - var semver = require('semver') - - // hapi 17+ requires Node.js 8.9.0 or higher - if (semver.lt(process.version, '8.9.0') && semver.gte(pkg.version, '17.0.0')) process.exit() - // hapi 16.7.0+ requires Node.js 6.0.0 or higher - if (semver.lt(process.version, '6.0.0') && semver.gte(pkg.version, '16.7.0')) process.exit() - - // hapi does not work on early versions of Node.js 10 because of https://github.com/nodejs/node/issues/20516 - // NOTE: Do not use semver.satisfies, as it does not match prereleases - var parsed = semver.parse(process.version) - if (parsed.major === 10 && parsed.minor >= 0 && parsed.minor < 8) process.exit() - - var http = require('http') - - var Hapi = require(moduleName) - var test = require('tape') - - var mockClient = require('../../../_mock_http_client') - - var originalCaptureError = agent.captureError - - function noop () {} - - test('extract URL from request', function (t) { - resetAgent(2, function (data) { - t.equal(data.transactions.length, 1) - t.equal(data.errors.length, 1) - var request = data.errors[0].context.request - t.equal(request.method, 'GET') - t.equal(request.url.pathname, '/captureError') - t.equal(request.url.search, '?foo=bar') - t.equal(request.url.raw, '/captureError?foo=bar') - t.equal(request.url.hostname, 'localhost') - t.equal(request.url.port, String(server.info.port)) - t.equal(request.socket.encrypted, false) - server.stop(noop) - t.end() - }) - - agent.captureError = originalCaptureError - - var server = startServer(function (err, port) { - t.error(err) - http.get('http://localhost:' + port + '/captureError?foo=bar') - }) - }) - - test('route naming', function (t) { - t.plan(8) - - resetAgent(1, function (data) { - assert(t, data) - server.stop(noop) - }) - - var server = startServer(function (err, port) { - t.error(err) - http.get('http://localhost:' + port + '/hello', function (res) { - t.equal(res.statusCode, 200) - res.on('data', function (chunk) { - t.equal(chunk.toString(), 'hello world') - }) - res.on('end', function () { - agent.flush() - }) - }) - }) - }) - - test('connectionless', function (t) { - if (semver.satisfies(pkg.version, '<15.0.2')) { - t.pass('skipping') - t.end() - return - } - - t.plan(1) - - resetAgent() - - var server = makeServer() - initServer(server, function (err) { - server.stop(noop) - t.error(err, 'start error') - }) - }) - - test('connectionless server error logging with Error', function (t) { - if (semver.satisfies(pkg.version, '<15.0.2')) { - t.pass('skipping') - t.end() - return - } - - t.plan(6) - - var customError = new Error('custom error') - - resetAgent() - - agent.captureError = function (err, opts) { - server.stop(noop) - - t.equal(err, customError) - t.ok(opts.custom) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.ok(opts.custom.data instanceof Error) - } - - var server = makeServer() - initServer(server, function (err) { - t.error(err, 'start error') - - server.log(['error'], customError) - }) - }) - - test('connectionless server error logging with String', function (t) { - if (semver.satisfies(pkg.version, '<15.0.2')) { - t.pass('skipping') - t.end() - return - } - - t.plan(6) - - var customError = 'custom error' - - resetAgent() - - agent.captureError = function (err, opts) { - server.stop(noop) - - t.equal(err, customError) - t.ok(opts.custom) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.ok(typeof opts.custom.data === 'string') - } - - var server = makeServer() - initServer(server, function (err) { - t.error(err, 'start error') - - server.log(['error'], customError) - }) - }) - - test('connectionless server error logging with Object', function (t) { - if (semver.satisfies(pkg.version, '<15.0.2')) { - t.pass('skipping') - t.end() - return - } - - t.plan(6) - - var customError = { - error: 'I forgot to turn this into an actual Error' - } - - resetAgent() - - agent.captureError = function (err, opts) { - server.stop(noop) - - t.equal(err, 'hapi server emitted a log event tagged error') - t.ok(opts.custom) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.deepEqual(opts.custom.data, customError) - } - - var server = makeServer() - initServer(server, function (err) { - t.error(err, 'start error') - - server.log(['error'], customError) - }) - }) - - test('server error logging with Error', function (t) { - t.plan(6) - - var customError = new Error('custom error') - - resetAgent() - - agent.captureError = function (err, opts) { - server.stop(noop) - - t.equal(err, customError) - t.ok(opts.custom) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.ok(opts.custom.data instanceof Error) - } - - var server = startServer(function (err) { - t.error(err, 'start error') - - server.log(['error'], customError) - }) - }) - - test('server error logging with Error does not affect event tags', function (t) { - t.plan(8) - - var customError = new Error('custom error') - - resetAgent() - - agent.captureError = function (err, opts) { - server.stop(noop) - - t.equal(err, customError) - t.ok(opts.custom) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.ok(opts.custom.data instanceof Error) - } - - var server = makeServer() - - var emitter = server.events || server - emitter.on('log', function (event, tags) { - t.deepEqual(event.tags, ['error']) - }) - - runServer(server, function (err) { - t.error(err, 'start error') - - emitter.on('log', function (event, tags) { - t.deepEqual(event.tags, ['error']) - }) - - server.log(['error'], customError) - }) - }) - - test('server error logging with String', function (t) { - t.plan(6) - - var customError = 'custom error' - - resetAgent() - - agent.captureError = function (err, opts) { - server.stop(noop) - - t.equal(err, customError) - t.ok(opts.custom) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.ok(typeof opts.custom.data === 'string') - } - - var server = startServer(function (err) { - t.error(err, 'start error') - - server.log(['error'], customError) - }) - }) - - test('server error logging with Object', function (t) { - t.plan(6) - - var customError = { - error: 'I forgot to turn this into an actual Error' - } - - resetAgent() - - agent.captureError = function (err, opts) { - server.stop(noop) - - t.equal(err, 'hapi server emitted a log event tagged error') - t.ok(opts.custom) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.deepEqual(opts.custom.data, customError) - } - - var server = startServer(function (err) { - t.error(err, 'start error') - - server.log(['error'], customError) - }) - }) - - test('request error logging with Error', function (t) { - t.plan(13) - - var customError = new Error('custom error') - - resetAgent(1, function (data) { - assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) - - server.stop(noop) - }) - - agent.captureError = function (err, opts) { - t.equal(err, customError) - t.ok(opts.custom) - t.ok(opts.request) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.ok(opts.custom.data instanceof Error) - } - - var server = makeServer() - - server.route({ - method: 'GET', - path: '/error', - handler: handler(function (request) { - request.log(['error'], customError) - - return 'hello world' - }) - }) - - runServer(server, function (err) { - t.error(err, 'start error') - - http.get('http://localhost:' + server.info.port + '/error', function (res) { - t.equal(res.statusCode, 200) - - res.resume().on('end', function () { - agent.flush() - }) - }) - }) - }) - - test('request error logging with Error does not affect event tags', function (t) { - t.plan(15) - - var customError = new Error('custom error') - - resetAgent(1, function (data) { - assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) - - server.stop(noop) - }) - - agent.captureError = function (err, opts) { - t.equal(err, customError) - t.ok(opts.custom) - t.ok(opts.request) - t.deepEqual(opts.custom.tags, ['elastic-apm', 'error']) - t.false(opts.custom.internals) - t.ok(opts.custom.data instanceof Error) - } - - var server = makeServer() - - server.route({ - method: 'GET', - path: '/error', - handler: handler(function (request) { - request.log(['elastic-apm', 'error'], customError) - - return 'hello world' - }) - }) - - var emitter = server.events || server - emitter.on('request', function (req, event, tags) { - if (event.channel === 'internal') return - t.deepEqual(event.tags, ['elastic-apm', 'error']) - }) - - runServer(server, function (err) { - t.error(err, 'start error') - - emitter.on('request', function (req, event, tags) { - if (event.channel === 'internal') return - t.deepEqual(event.tags, ['elastic-apm', 'error']) - }) - - http.get('http://localhost:' + server.info.port + '/error', function (res) { - t.equal(res.statusCode, 200) - - res.resume().on('end', function () { - agent.flush() - }) - }) - }) - }) - - test('request error logging with String', function (t) { - t.plan(13) - - var customError = 'custom error' - - resetAgent(1, function (data) { - assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) - - server.stop(noop) - }) - - agent.captureError = function (err, opts) { - t.equal(err, customError) - t.ok(opts.custom) - t.ok(opts.request) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.ok(typeof opts.custom.data === 'string') - } - - var server = makeServer() - - server.route({ - method: 'GET', - path: '/error', - handler: handler(function (request) { - request.log(['error'], customError) - - return 'hello world' - }) - }) - - runServer(server, function (err) { - t.error(err, 'start error') - - http.get('http://localhost:' + server.info.port + '/error', function (res) { - t.equal(res.statusCode, 200) - - res.resume().on('end', function () { - agent.flush() - }) - }) - }) - }) - - test('request error logging with Object', function (t) { - t.plan(13) - - var customError = { - error: 'I forgot to turn this into an actual Error' - } - - resetAgent(1, function (data) { - assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) - - server.stop(noop) - }) - - agent.captureError = function (err, opts) { - t.equal(err, 'hapi server emitted a request event tagged error') - t.ok(opts.custom) - t.ok(opts.request) - t.deepEqual(opts.custom.tags, ['error']) - t.false(opts.custom.internals) - t.deepEqual(opts.custom.data, customError) - } - - var server = makeServer() - - server.route({ - method: 'GET', - path: '/error', - handler: handler(function (request) { - request.log(['error'], customError) - - return 'hello world' - }) - }) - - runServer(server, function (err) { - t.error(err, 'start error') - - http.get('http://localhost:' + server.info.port + '/error', function (res) { - t.equal(res.statusCode, 200) - - res.resume().on('end', function () { - agent.flush() - }) - }) - }) - }) - - test('error handling', function (t) { - t.plan(10) - - resetAgent(1, function (data) { - assert(t, data, { status: 'HTTP 5xx', name: 'GET /error' }) - server.stop(noop) - }) - - agent.captureError = function (err, opts) { - t.equal(err.message, 'foo') - t.ok(opts.request instanceof http.IncomingMessage) - } - - var server = startServer(function (err, port) { - t.error(err) - http.get('http://localhost:' + port + '/error', function (res) { - t.equal(res.statusCode, 500) - res.on('data', function (chunk) { - var data = JSON.parse(chunk.toString()) - t.deepEqual(data, { - statusCode: 500, - error: 'Internal Server Error', - message: 'An internal server error occurred' - }) - }) - res.on('end', function () { - agent.flush() - }) - }) - }) - }) - - function makeServer (opts) { - var server = new Hapi.Server() - if (semver.satisfies(pkg.version, '<17')) { - server.connection(opts) - } - return server - } - - function initServer (server, cb) { - if (semver.satisfies(pkg.version, '<17')) { - server.initialize(cb) - } else { - server.initialize().then( - cb.bind(null, null), - cb - ) - } - } - - function runServer (server, cb) { - if (semver.satisfies(pkg.version, '<17')) { - server.start(function (err) { - if (err) throw err - cb(null, server.info.port) - }) - } else { - server.start().then( - () => cb(null, server.info.port), - cb - ) - } - } - - function startServer (cb) { - var server = buildServer() - runServer(server, cb) - return server - } - - function handler (fn) { - if (semver.satisfies(pkg.version, '>=17')) return fn - return function (request, reply) { - var p = new Promise(function (resolve, reject) { - resolve(fn(request)) - }) - p.then(reply, reply) - } - } - - function buildServer () { - var server = makeServer() - - server.route({ - method: 'GET', - path: '/hello', - handler: handler(function (request) { - return 'hello world' - }) - }) - server.route({ - method: 'GET', - path: '/error', - handler: handler(function (request) { - throw new Error('foo') - }) - }) - server.route({ - method: 'GET', - path: '/captureError', - handler: handler(function (request) { - agent.captureError(new Error()) - return '' - }) - }) - return server - } - - function assert (t, data, results) { - if (!results) results = {} - results.status = results.status || 'HTTP 2xx' - results.name = results.name || 'GET /hello' - - t.equal(data.transactions.length, 1) - - var trans = data.transactions[0] - - t.equal(trans.name, results.name) - t.equal(trans.type, 'request') - t.equal(trans.result, results.status) - t.equal(trans.context.request.method, 'GET') - } - - function resetAgent (expected, cb) { - agent._instrumentation.currentTransaction = null - agent._transport = mockClient(expected, cb) - agent.captureError = function (err) { throw err } - } -}) +require('./shared')('@hapi/hapi') diff --git a/test/instrumentation/modules/hapi/shared.js b/test/instrumentation/modules/hapi/shared.js new file mode 100644 index 0000000000..6a9eae0a27 --- /dev/null +++ b/test/instrumentation/modules/hapi/shared.js @@ -0,0 +1,625 @@ +'use strict' + +module.exports = (moduleName) => { + var agent = require('../../../..').start({ + serviceName: 'test', + secretToken: 'test', + captureExceptions: false, + logLevel: 'fatal', + metricsInterval: 0 + }) + var pkg = require(`${moduleName}/package.json`) + var semver = require('semver') + + // hapi 17+ requires Node.js 8.9.0 or higher + if (semver.lt(process.version, '8.9.0') && semver.gte(pkg.version, '17.0.0')) process.exit() + // hapi 16.7.0+ requires Node.js 6.0.0 or higher + if (semver.lt(process.version, '6.0.0') && semver.gte(pkg.version, '16.7.0')) process.exit() + + // hapi does not work on early versions of Node.js 10 because of https://github.com/nodejs/node/issues/20516 + // NOTE: Do not use semver.satisfies, as it does not match prereleases + var parsed = semver.parse(process.version) + if (parsed.major === 10 && parsed.minor >= 0 && parsed.minor < 8) process.exit() + + var http = require('http') + + var Hapi = require(moduleName) + var test = require('tape') + + var mockClient = require('../../../_mock_http_client') + + var originalCaptureError = agent.captureError + + function noop () {} + + test('extract URL from request', function (t) { + resetAgent(2, function (data) { + t.equal(data.transactions.length, 1) + t.equal(data.errors.length, 1) + var request = data.errors[0].context.request + t.equal(request.method, 'GET') + t.equal(request.url.pathname, '/captureError') + t.equal(request.url.search, '?foo=bar') + t.equal(request.url.raw, '/captureError?foo=bar') + t.equal(request.url.hostname, 'localhost') + t.equal(request.url.port, String(server.info.port)) + t.equal(request.socket.encrypted, false) + server.stop(noop) + t.end() + }) + + agent.captureError = originalCaptureError + + var server = startServer(function (err, port) { + t.error(err) + http.get('http://localhost:' + port + '/captureError?foo=bar') + }) + }) + + test('route naming', function (t) { + t.plan(8) + + resetAgent(1, function (data) { + assert(t, data) + server.stop(noop) + }) + + var server = startServer(function (err, port) { + t.error(err) + http.get('http://localhost:' + port + '/hello', function (res) { + t.equal(res.statusCode, 200) + res.on('data', function (chunk) { + t.equal(chunk.toString(), 'hello world') + }) + res.on('end', function () { + agent.flush() + }) + }) + }) + }) + + test('connectionless', function (t) { + if (semver.satisfies(pkg.version, '<15.0.2')) { + t.pass('skipping') + t.end() + return + } + + t.plan(1) + + resetAgent() + + var server = makeServer() + initServer(server, function (err) { + server.stop(noop) + t.error(err, 'start error') + }) + }) + + test('connectionless server error logging with Error', function (t) { + if (semver.satisfies(pkg.version, '<15.0.2')) { + t.pass('skipping') + t.end() + return + } + + t.plan(6) + + var customError = new Error('custom error') + + resetAgent() + + agent.captureError = function (err, opts) { + server.stop(noop) + + t.equal(err, customError) + t.ok(opts.custom) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.ok(opts.custom.data instanceof Error) + } + + var server = makeServer() + initServer(server, function (err) { + t.error(err, 'start error') + + server.log(['error'], customError) + }) + }) + + test('connectionless server error logging with String', function (t) { + if (semver.satisfies(pkg.version, '<15.0.2')) { + t.pass('skipping') + t.end() + return + } + + t.plan(6) + + var customError = 'custom error' + + resetAgent() + + agent.captureError = function (err, opts) { + server.stop(noop) + + t.equal(err, customError) + t.ok(opts.custom) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.ok(typeof opts.custom.data === 'string') + } + + var server = makeServer() + initServer(server, function (err) { + t.error(err, 'start error') + + server.log(['error'], customError) + }) + }) + + test('connectionless server error logging with Object', function (t) { + if (semver.satisfies(pkg.version, '<15.0.2')) { + t.pass('skipping') + t.end() + return + } + + t.plan(6) + + var customError = { + error: 'I forgot to turn this into an actual Error' + } + + resetAgent() + + agent.captureError = function (err, opts) { + server.stop(noop) + + t.equal(err, 'hapi server emitted a log event tagged error') + t.ok(opts.custom) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.deepEqual(opts.custom.data, customError) + } + + var server = makeServer() + initServer(server, function (err) { + t.error(err, 'start error') + + server.log(['error'], customError) + }) + }) + + test('server error logging with Error', function (t) { + t.plan(6) + + var customError = new Error('custom error') + + resetAgent() + + agent.captureError = function (err, opts) { + server.stop(noop) + + t.equal(err, customError) + t.ok(opts.custom) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.ok(opts.custom.data instanceof Error) + } + + var server = startServer(function (err) { + t.error(err, 'start error') + + server.log(['error'], customError) + }) + }) + + test('server error logging with Error does not affect event tags', function (t) { + t.plan(8) + + var customError = new Error('custom error') + + resetAgent() + + agent.captureError = function (err, opts) { + server.stop(noop) + + t.equal(err, customError) + t.ok(opts.custom) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.ok(opts.custom.data instanceof Error) + } + + var server = makeServer() + + var emitter = server.events || server + emitter.on('log', function (event, tags) { + t.deepEqual(event.tags, ['error']) + }) + + runServer(server, function (err) { + t.error(err, 'start error') + + emitter.on('log', function (event, tags) { + t.deepEqual(event.tags, ['error']) + }) + + server.log(['error'], customError) + }) + }) + + test('server error logging with String', function (t) { + t.plan(6) + + var customError = 'custom error' + + resetAgent() + + agent.captureError = function (err, opts) { + server.stop(noop) + + t.equal(err, customError) + t.ok(opts.custom) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.ok(typeof opts.custom.data === 'string') + } + + var server = startServer(function (err) { + t.error(err, 'start error') + + server.log(['error'], customError) + }) + }) + + test('server error logging with Object', function (t) { + t.plan(6) + + var customError = { + error: 'I forgot to turn this into an actual Error' + } + + resetAgent() + + agent.captureError = function (err, opts) { + server.stop(noop) + + t.equal(err, 'hapi server emitted a log event tagged error') + t.ok(opts.custom) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.deepEqual(opts.custom.data, customError) + } + + var server = startServer(function (err) { + t.error(err, 'start error') + + server.log(['error'], customError) + }) + }) + + test('request error logging with Error', function (t) { + t.plan(13) + + var customError = new Error('custom error') + + resetAgent(1, function (data) { + assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) + + server.stop(noop) + }) + + agent.captureError = function (err, opts) { + t.equal(err, customError) + t.ok(opts.custom) + t.ok(opts.request) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.ok(opts.custom.data instanceof Error) + } + + var server = makeServer() + + server.route({ + method: 'GET', + path: '/error', + handler: handler(function (request) { + request.log(['error'], customError) + + return 'hello world' + }) + }) + + runServer(server, function (err) { + t.error(err, 'start error') + + http.get('http://localhost:' + server.info.port + '/error', function (res) { + t.equal(res.statusCode, 200) + + res.resume().on('end', function () { + agent.flush() + }) + }) + }) + }) + + test('request error logging with Error does not affect event tags', function (t) { + t.plan(15) + + var customError = new Error('custom error') + + resetAgent(1, function (data) { + assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) + + server.stop(noop) + }) + + agent.captureError = function (err, opts) { + t.equal(err, customError) + t.ok(opts.custom) + t.ok(opts.request) + t.deepEqual(opts.custom.tags, ['elastic-apm', 'error']) + t.false(opts.custom.internals) + t.ok(opts.custom.data instanceof Error) + } + + var server = makeServer() + + server.route({ + method: 'GET', + path: '/error', + handler: handler(function (request) { + request.log(['elastic-apm', 'error'], customError) + + return 'hello world' + }) + }) + + var emitter = server.events || server + emitter.on('request', function (req, event, tags) { + if (event.channel === 'internal') return + t.deepEqual(event.tags, ['elastic-apm', 'error']) + }) + + runServer(server, function (err) { + t.error(err, 'start error') + + emitter.on('request', function (req, event, tags) { + if (event.channel === 'internal') return + t.deepEqual(event.tags, ['elastic-apm', 'error']) + }) + + http.get('http://localhost:' + server.info.port + '/error', function (res) { + t.equal(res.statusCode, 200) + + res.resume().on('end', function () { + agent.flush() + }) + }) + }) + }) + + test('request error logging with String', function (t) { + t.plan(13) + + var customError = 'custom error' + + resetAgent(1, function (data) { + assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) + + server.stop(noop) + }) + + agent.captureError = function (err, opts) { + t.equal(err, customError) + t.ok(opts.custom) + t.ok(opts.request) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.ok(typeof opts.custom.data === 'string') + } + + var server = makeServer() + + server.route({ + method: 'GET', + path: '/error', + handler: handler(function (request) { + request.log(['error'], customError) + + return 'hello world' + }) + }) + + runServer(server, function (err) { + t.error(err, 'start error') + + http.get('http://localhost:' + server.info.port + '/error', function (res) { + t.equal(res.statusCode, 200) + + res.resume().on('end', function () { + agent.flush() + }) + }) + }) + }) + + test('request error logging with Object', function (t) { + t.plan(13) + + var customError = { + error: 'I forgot to turn this into an actual Error' + } + + resetAgent(1, function (data) { + assert(t, data, { status: 'HTTP 2xx', name: 'GET /error' }) + + server.stop(noop) + }) + + agent.captureError = function (err, opts) { + t.equal(err, 'hapi server emitted a request event tagged error') + t.ok(opts.custom) + t.ok(opts.request) + t.deepEqual(opts.custom.tags, ['error']) + t.false(opts.custom.internals) + t.deepEqual(opts.custom.data, customError) + } + + var server = makeServer() + + server.route({ + method: 'GET', + path: '/error', + handler: handler(function (request) { + request.log(['error'], customError) + + return 'hello world' + }) + }) + + runServer(server, function (err) { + t.error(err, 'start error') + + http.get('http://localhost:' + server.info.port + '/error', function (res) { + t.equal(res.statusCode, 200) + + res.resume().on('end', function () { + agent.flush() + }) + }) + }) + }) + + test('error handling', function (t) { + t.plan(10) + + resetAgent(1, function (data) { + assert(t, data, { status: 'HTTP 5xx', name: 'GET /error' }) + server.stop(noop) + }) + + agent.captureError = function (err, opts) { + t.equal(err.message, 'foo') + t.ok(opts.request instanceof http.IncomingMessage) + } + + var server = startServer(function (err, port) { + t.error(err) + http.get('http://localhost:' + port + '/error', function (res) { + t.equal(res.statusCode, 500) + res.on('data', function (chunk) { + var data = JSON.parse(chunk.toString()) + t.deepEqual(data, { + statusCode: 500, + error: 'Internal Server Error', + message: 'An internal server error occurred' + }) + }) + res.on('end', function () { + agent.flush() + }) + }) + }) + }) + + function makeServer (opts) { + var server = new Hapi.Server() + if (semver.satisfies(pkg.version, '<17')) { + server.connection(opts) + } + return server + } + + function initServer (server, cb) { + if (semver.satisfies(pkg.version, '<17')) { + server.initialize(cb) + } else { + server.initialize().then( + cb.bind(null, null), + cb + ) + } + } + + function runServer (server, cb) { + if (semver.satisfies(pkg.version, '<17')) { + server.start(function (err) { + if (err) throw err + cb(null, server.info.port) + }) + } else { + server.start().then( + () => cb(null, server.info.port), + cb + ) + } + } + + function startServer (cb) { + var server = buildServer() + runServer(server, cb) + return server + } + + function handler (fn) { + if (semver.satisfies(pkg.version, '>=17')) return fn + return function (request, reply) { + var p = new Promise(function (resolve, reject) { + resolve(fn(request)) + }) + p.then(reply, reply) + } + } + + function buildServer () { + var server = makeServer() + + server.route({ + method: 'GET', + path: '/hello', + handler: handler(function (request) { + return 'hello world' + }) + }) + server.route({ + method: 'GET', + path: '/error', + handler: handler(function (request) { + throw new Error('foo') + }) + }) + server.route({ + method: 'GET', + path: '/captureError', + handler: handler(function (request) { + agent.captureError(new Error()) + return '' + }) + }) + return server + } + + function assert (t, data, results) { + if (!results) results = {} + results.status = results.status || 'HTTP 2xx' + results.name = results.name || 'GET /hello' + + t.equal(data.transactions.length, 1) + + var trans = data.transactions[0] + + t.equal(trans.name, results.name) + t.equal(trans.type, 'request') + t.equal(trans.result, results.status) + t.equal(trans.context.request.method, 'GET') + } + + function resetAgent (expected, cb) { + agent._instrumentation.currentTransaction = null + agent._transport = mockClient(expected, cb) + agent.captureError = function (err) { throw err } + } +} From 71c0c6f28f3a2b21cc6408c7f2dcf2dcd01b3656 Mon Sep 17 00:00:00 2001 From: Brad Gardner Date: Mon, 1 Jul 2019 09:02:43 -0400 Subject: [PATCH 08/11] refactored addPatch and removePatch to allow lists --- lib/instrumentation/index.js | 44 ++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/lib/instrumentation/index.js b/lib/instrumentation/index.js index f0748654a5..a23bae64d6 100644 --- a/lib/instrumentation/index.js +++ b/lib/instrumentation/index.js @@ -73,32 +73,38 @@ function Instrumentation (agent) { if (!Array.isArray(mod)) mod = [mod] const pathName = mod[0] - mod.forEach(mod => { - this.addPatch(mod, (exports, name, version, enabled) => { - // Lazy require so that we don't have to use `require.resolve` which - // would fail in combination with Webpack. For more info see: - // https://github.com/elastic/apm-agent-nodejs/pull/957 - const patch = require(`./modules/${pathName}`) - return patch(exports, name, version, enabled) - }) + this.addPatch(mod, (exports, name, version, enabled) => { + // Lazy require so that we don't have to use `require.resolve` which + // would fail in combination with Webpack. For more info see: + // https://github.com/elastic/apm-agent-nodejs/pull/957 + const patch = require(`./modules/${pathName}`) + return patch(exports, name, version, enabled) }) } } -Instrumentation.prototype.addPatch = function (name, handler) { - const type = typeof handler - if (type !== 'function' && type !== 'string') { - this._agent.logger.error('Invalid patch handler type:', type) - return - } +Instrumentation.prototype.addPatch = function (modules, handler) { + if (!Array.isArray(modules)) modules = [modules] - this._patches.add(name, handler) - this._startHook() + modules.forEach(mod => { + const type = typeof handler + if (type !== 'function' && type !== 'string') { + this._agent.logger.error('Invalid patch handler type:', type) + return + } + + this._patches.add(mod, handler) + this._startHook() + }) } -Instrumentation.prototype.removePatch = function (name, handler) { - this._patches.delete(name, handler) - this._startHook() +Instrumentation.prototype.removePatch = function (modules, handler) { + if (!Array.isArray(modules)) modules = [modules] + + modules.forEach(mod => { + this._patches.delete(mod, handler) + this._startHook() + }) } Instrumentation.prototype.clearPatches = function (name) { From 8c1a80fddc586f7044663dd4513f74c026b42770 Mon Sep 17 00:00:00 2001 From: Brad Gardner Date: Mon, 1 Jul 2019 09:03:15 -0400 Subject: [PATCH 09/11] added set framework command for @hapi --- .tav.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.tav.yml b/.tav.yml index 14a6cb0e5b..68debb604f 100644 --- a/.tav.yml +++ b/.tav.yml @@ -222,7 +222,9 @@ hapi-async-await: '@hapi/hapi': node: '>=8.2' versions: '>=17.0.0' - commands: node test/instrumentation/modules/hapi/basic.js + commands: + - node test/instrumentation/modules/hapi/basic.js + - node test/instrumentation/modules/hapi/set-framework-2.js tedious: name: tedious versions: '>=1.9 <2.7 || 3.x || >4.0.0' From a5224f8abdf67ddf05017266d77a84cea8cf9266 Mon Sep 17 00:00:00 2001 From: Brad Gardner Date: Mon, 1 Jul 2019 09:03:26 -0400 Subject: [PATCH 10/11] linting fix --- test/instrumentation/modules/hapi/shared.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/instrumentation/modules/hapi/shared.js b/test/instrumentation/modules/hapi/shared.js index 6a9eae0a27..75dd8b423d 100644 --- a/test/instrumentation/modules/hapi/shared.js +++ b/test/instrumentation/modules/hapi/shared.js @@ -2,12 +2,12 @@ module.exports = (moduleName) => { var agent = require('../../../..').start({ - serviceName: 'test', - secretToken: 'test', - captureExceptions: false, - logLevel: 'fatal', - metricsInterval: 0 - }) + serviceName: 'test', + secretToken: 'test', + captureExceptions: false, + logLevel: 'fatal', + metricsInterval: 0 + }) var pkg = require(`${moduleName}/package.json`) var semver = require('semver') From 82d71e5fb0113ce9bee3bcefe4042d2eef12ea0b Mon Sep 17 00:00:00 2001 From: Brad Gardner Date: Mon, 1 Jul 2019 09:10:07 -0400 Subject: [PATCH 11/11] doc updates --- docs/agent-api.asciidoc | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/docs/agent-api.asciidoc b/docs/agent-api.asciidoc index 68c8feed08..e480f2b3a4 100644 --- a/docs/agent-api.asciidoc +++ b/docs/agent-api.asciidoc @@ -690,12 +690,12 @@ Optionally, a type may also be provided to group lambdas together. By default, Read more lambda support in the <> article. [[apm-add-patch]] -==== `apm.addPatch(name, handler)` +==== `apm.addPatch(modules, handler)` [small]#Added in: v2.7.0# -* `name` +{type-string}+ -Name of module to apply the patch to, when required. +* `modules` +{type-string or list of strings}+ +Name of module(s) to apply the patch to, when required. * `handler` +{type-function}+ | +{type-string}+ Must be a patch function or a path to a module exporting a patch function ** `exports` +{type-object}+ The original export object of the module @@ -726,11 +726,26 @@ apm.addPatch('timers', (exports, agent, { version, enabled }) => { // or ... +apm.addPatch(['hapi', '@hapi/hapi'], (exports, agent, { version, enabled }) => { + const setTimeout = exports.setTimeout + exports.setTimeout = (fn, ms) => { + const span = agent.createSpan('set-timeout') + return setTimeout(() => { + span.end() + fn() + }, ms) + } + + return exports +}) + +// or ... + apm.addPatch('timers', './timer-patch') ---- [[apm-remove-patch]] -==== `apm.removePatch(name, handler)` +==== `apm.removePatch(modules, handler)` [small]#Added in: v2.7.0# @@ -744,6 +759,10 @@ apm.removePatch('timers', './timers-patch') // or ... +apm.removePatch(['timers'], './timers-patch') + +// or ... + apm.removePatch('timers', timerPatchFunction) ----