Skip to content

Commit

Permalink
Merge pull request #83 from samarthmaniar/feature/add-koa-middleware-…
Browse files Browse the repository at this point in the history
…support

#84 - Added Koa middleware support
  • Loading branch information
fieldju committed Jul 9, 2020
2 parents c47e39c + 00f019e commit 8dc6ead
Show file tree
Hide file tree
Showing 9 changed files with 600 additions and 29 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ node_js:
- 11
- 10
- 8
- 6
before_install:
- sudo apt-get -qq update
- sudo apt-get install -y gawk jq
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
"gh-pages": "^1.1.0",
"ink-docstrap": "^1.3.2",
"jsdoc": "^3.5.5",
"koa": "^2.13.0",
"koa-bodyparser": "^4.3.0",
"koa-router": "^9.1.0",
"lerna": "^2.11.0",
"mocha": "^5.1.1",
"mocha-lcov-reporter": "^1.3.0",
Expand Down
14 changes: 13 additions & 1 deletion packages/measured-node-metrics/lib/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const { nodeProcessMetrics, createProcessMetrics } = require('./nodeProcessMetrics');
const { nodeOsMetrics, createOSMetrics } = require('./nodeOsMetrics');
const { createExpressMiddleware, onRequestStart, onRequestEnd } = require('./nodeHttpRequestMetrics');
const { createExpressMiddleware, createKoaMiddleware, onRequestStart, onRequestEnd } = require('./nodeHttpRequestMetrics');

/**
* The main module for the measured-node-metrics lib.
Expand Down Expand Up @@ -69,6 +69,18 @@ module.exports = {
*/
createExpressMiddleware,

/**
* Creates a Koa middleware that reports a timer on request data.
* With this middleware you will get requests counts and latency percentiles all filterable by status codes, http method, and uri paths.
*
* @function
* @name createExpressMiddleware
* @param {SelfReportingMetricsRegistry} metricsRegistry
* @param {number} [reportingIntervalInSeconds]
* @return {Function}
*/
createKoaMiddleware,

/**
* At the start of the request, create a stopwatch, that starts tracking how long the request is taking.
* @function
Expand Down
24 changes: 23 additions & 1 deletion packages/measured-node-metrics/lib/nodeHttpRequestMetrics.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const { Stopwatch } = require('measured-core');
const DEFAULT_REQUEST_METRICS_REPORTING_INTERVAL_IN_SECONDS = 10;

/**
* This module has functions needed to create middlewares for frameworks such as express.
* This module has functions needed to create middlewares for frameworks such as express and koa.
* It also exports the 2 functions needed to implement your own middleware.
* If you implement a middleware for a framework not implemented here, please contribute it back.
*
Expand Down Expand Up @@ -37,6 +37,28 @@ module.exports = {
};
},

/**
* Creates a Koa middleware that reports a timer on request data.
* With this middleware you will get requests counts and latency percentiles all filterable by status codes, http method, and uri paths.
*
* @param {SelfReportingMetricsRegistry} metricsRegistry
* @param {number} [reportingIntervalInSeconds]
* @return {Function}
*/
createKoaMiddleware: (metricsRegistry, reportingIntervalInSeconds) => async (ctx, next) => {
const stopwatch = module.exports.onRequestStart();
const { req, res } = ctx;

res.once('finish', () => {
const { method } = req;
const { statusCode } = res;
const uri = ctx._matchedRoute || '_unknown';
module.exports.onRequestEnd(metricsRegistry, stopwatch, method, statusCode, uri, reportingIntervalInSeconds);
});

await next();
},

/**
* At the start of the request, create a stopwatch, that starts tracking how long the request is taking.
* @return {Stopwatch}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,20 @@ describe('express-middleware', () => {
assert(registeredKeys.length === 1);
assert.equal(registeredKeys[0], 'requests-GET-200-/hello');
const metricWrapper = registry._registry.getMetricWrapperByKey('requests-GET-200-/hello');
const name = metricWrapper.name;
const dimensions = metricWrapper.dimensions;
const { name, dimensions } = metricWrapper;
assert.equal(name, 'requests');
assert.deepEqual(dimensions, { statusCode: '200', method: 'GET', uri: '/hello' });
});
});

it('creates a single timer that has 1 count for requests, when an http POST call is made once', () => {
const options = {method: 'POST', headers: {'Content-Type': 'application/json'}};
const options = { method: 'POST', headers: { 'Content-Type': 'application/json' } };
return callLocalHost(port, 'world', options).then(() => {
const registeredKeys = registry._registry.allKeys();
assert(registeredKeys.length === 1);
assert.equal(registeredKeys[0], 'requests-POST-201-/world');
const metricWrapper = registry._registry.getMetricWrapperByKey('requests-POST-201-/world');
const name = metricWrapper.name;
const dimensions = metricWrapper.dimensions;
const { name, dimensions } = metricWrapper;
assert.equal(name, 'requests');
assert.deepEqual(dimensions, { statusCode: '201', method: 'POST', uri: '/world' });
});
Expand All @@ -84,12 +82,12 @@ describe('express-middleware', () => {

const callLocalHost = (port, endpoint, options) => {
return new Promise((resolve, reject) => {
const req = Object.assign({protocol: `http:`,
host: `127.0.0.1`,
port: `${port}`,
path: `/${endpoint}`,
method: 'GET'},
options || {});
const req = Object.assign({ protocol: 'http:',
host: '127.0.0.1',
port: `${port}`,
path: `/${endpoint}`,
method: 'GET' },
options || {});
http
.request(req, resp => {
let data = '';
Expand All @@ -106,6 +104,6 @@ const callLocalHost = (port, endpoint, options) => {
console.log('Error: ', JSON.stringify(err));
reject();
})
.end();
.end();
});
};
124 changes: 124 additions & 0 deletions packages/measured-node-metrics/test/integration/test-koa-middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
const Koa = require('koa');
const KoaBodyParser = require('koa-bodyparser');
const Router = require('koa-router');
const Registry = require('measured-reporting').SelfReportingMetricsRegistry;
const TestReporter = require('../unit/TestReporter');
const { createKoaMiddleware } = require('../../lib');
const findFreePort = require('find-free-port');
const assert = require('assert');
const http = require('http');

describe('koa-middleware', () => {
let port;
let reporter;
let registry;
let middleware;
let app;
let httpServer;
let router;
beforeEach(() => {
return new Promise(resolve => {
reporter = new TestReporter();
registry = new Registry(reporter);
middleware = createKoaMiddleware(registry, 1);
app = new Koa();
router = new Router();

router.get('/hello', ({ response }) => {
response.body = 'Hello World!';
return response;
});
router.post('/world', ({ response }) => {
response.body = 'Hello World!';
response.status = 201;
return response;
});
router.get('/users/:userId', ({ params, response }) => {
response.body = `id: ${params.userId}`;
return response;
});

app.use(middleware);
app.use(KoaBodyParser());
app.use(router.routes());
app.use(router.allowedMethods());

app.on('error', (err) => console.error(err));

findFreePort(3000).then(portArr => {
port = portArr.shift();

httpServer = app.listen(port);
resolve();
});
});
});

afterEach(() => {
httpServer.close();
registry.shutdown();
});

it('creates a single timer that has 1 count for requests, when an http call is made once', () => {
return callLocalHost(port, 'hello').then(() => {
const registeredKeys = registry._registry.allKeys();
assert(registeredKeys.length === 1);
assert.equal(registeredKeys[0], 'requests-GET-200-/hello');
const metricWrapper = registry._registry.getMetricWrapperByKey('requests-GET-200-/hello');
const { name, dimensions } = metricWrapper;
assert.equal(name, 'requests');
assert.deepEqual(dimensions, { statusCode: '200', method: 'GET', uri: '/hello' });
});
});

it('creates a single timer that has 1 count for requests, when an http POST call is made once', () => {
const options = { method: 'POST', headers: { 'Content-Type': 'application/json' } };
return callLocalHost(port, 'world', options).then(() => {
const registeredKeys = registry._registry.allKeys();
assert(registeredKeys.length === 1);
assert.equal(registeredKeys[0], 'requests-POST-201-/world');
const metricWrapper = registry._registry.getMetricWrapperByKey('requests-POST-201-/world');
const { name, dimensions } = metricWrapper;
assert.equal(name, 'requests');
assert.deepEqual(dimensions, { statusCode: '201', method: 'POST', uri: '/world' });
});
});

it('does not create runaway n metrics in the registry for n ids in the path', () => {
return Promise.all([
callLocalHost(port, 'users/foo'),
callLocalHost(port, 'users/bar'),
callLocalHost(port, 'users/bop')
]).then(() => {
assert.equal(registry._registry.allKeys().length, 1, 'There should only be one metric for /users and GET');
});
});
});

const callLocalHost = (port, endpoint, options) => {
return new Promise((resolve, reject) => {
const req = Object.assign({ protocol: 'http:',
host: '127.0.0.1',
port: `${port}`,
path: `/${endpoint}`,
method: 'GET' },
options || {});
http
.request(req, resp => {
let data = '';
resp.on('data', chunk => {
data += chunk;
});

resp.on('end', () => {
console.log(JSON.stringify(data));
resolve();
});
})
.on('error', err => {
console.log('Error: ', JSON.stringify(err));
reject();
})
.end();
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
const assert = require('assert');
const EventEmitter = require('events');
const { Stopwatch } = require('measured-core');
const { createExpressMiddleware, onRequestStart, onRequestEnd } = require('../../lib');
const { createExpressMiddleware, createKoaMiddleware, onRequestStart, onRequestEnd } = require('../../lib');
const TestReporter = require('./TestReporter');
const Registry = require('measured-reporting').SelfReportingMetricsRegistry;

Expand Down Expand Up @@ -55,7 +55,7 @@ describe('createExpressMiddleware', () => {
middleware(
{
method: 'GET',
routine: {path: '/v1/rest/some-end-point'}
routine: { path: '/v1/rest/some-end-point' }
},
res,
() => {}
Expand All @@ -68,3 +68,30 @@ describe('createExpressMiddleware', () => {
registry.shutdown();
});
});

describe('createKoaMiddleware', () => {
it('creates and registers a metric called request that is a timer', async () => {
const reporter = new TestReporter();
const registry = new Registry(reporter);

const middleware = createKoaMiddleware(registry);

const res = new MockResponse();
middleware(
{
req: {
method: 'GET',
url: '/v1/rest/some-end-point',
},
res,
},
() => Promise.resolve()
).then(() => {
const registeredKeys = registry._registry.allKeys();
assert(registeredKeys.length === 1);
assert(registeredKeys[0].includes('requests-GET'));
registry.shutdown();
});
res.finish();
});
});
Loading

0 comments on commit 8dc6ead

Please sign in to comment.