koa-actuator
Advanced tools
Comparing version 0.1.0 to 0.2.0
49
index.js
@@ -5,3 +5,4 @@ const compose = require('koa-compose'); | ||
const appRootPath = require('app-root-path'); | ||
const package_json = require(appRootPath + path.sep +'package.json'); | ||
const package_json = require(appRootPath + path.sep + 'package.json'); | ||
const assert = require('assert'); | ||
@@ -15,12 +16,38 @@ const HEALTH_PATH = '/health'; | ||
/** | ||
* Writes {status: 'UP'} to response body if request path is /health | ||
* Writes health checks results and aggregated status to response body if request path is /health | ||
*/ | ||
//TODO: add a callback function | ||
async function health(ctx, next) { | ||
if (HEALTH_PATH == ctx.path) | ||
ctx.body = {status: 'UP'}; | ||
else | ||
await next(); | ||
function health(checks, options) { | ||
Object.keys(checks).forEach(name => assert(typeof(checks[name]) === 'function', `'${name}' check must be a function`)); | ||
const timeout = options.checkTimeout || 5000; | ||
return async function healthMiddleware(ctx, next) { | ||
if (HEALTH_PATH === ctx.path) { | ||
const health = {status: 'UP'}; | ||
for (const checkName of Object.keys(checks)) { | ||
const checkResult = await runCheck(checks[checkName], timeout); | ||
health[checkName] = checkResult; | ||
if (checkResult && checkResult.status === 'DOWN') { | ||
health.status = checkResult.status; | ||
} | ||
} | ||
ctx.status = health.status === 'UP' ? 200 : 503; | ||
ctx.body = health; | ||
} else { | ||
await next(); | ||
} | ||
} | ||
} | ||
async function runCheck(check, timeout) { | ||
try { | ||
return await Promise.race([ | ||
check(), | ||
new Promise((resolve, reject) => setTimeout(() => reject(new Error('Check timed out')), timeout)) | ||
]); | ||
} catch (e) { | ||
return {status: 'DOWN', error: e && (e.message || e.toString())}; | ||
} | ||
} | ||
/** | ||
@@ -73,3 +100,3 @@ * Exposes application and resources information. E.g. name, version. | ||
heap: memory.heapTotal, | ||
"heap.used": memory.heapUsed, | ||
'heap.used': memory.heapUsed, | ||
resources: { | ||
@@ -88,2 +115,4 @@ memory: memory, | ||
module.exports = compose([health, env, info, metrics]); | ||
module.exports = (healthChecks = {}, options = {}) => { | ||
return compose([health(healthChecks, options), env, info, metrics]); | ||
}; |
{ | ||
"name": "koa-actuator", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"description": "Healthcheck and monitoring middleware for koa inspired by java spring's actuators", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -16,5 +16,5 @@ # koa-actuator | ||
const app = new Koa(); | ||
... | ||
app.use(actuator); | ||
... | ||
//... | ||
app.use(actuator()); | ||
//... | ||
app.listen(3000); | ||
@@ -29,5 +29,31 @@ ``` | ||
### /health | ||
Performs health checks and returns the results: | ||
``` | ||
{"status":"UP"} | ||
{ | ||
"status": "UP", | ||
"db": { | ||
"status": "UP", | ||
"freeConnections": 10 | ||
}, | ||
"redis": { | ||
"status": "UP", | ||
"usedMemory": "52m", | ||
"uptimeDays": 16 | ||
} | ||
} | ||
``` | ||
The statuses of the health checks are aggregated in a root-level `status` field. If at least one check yields `DOWN` status, the aggregated status will become `DOWN`. Health checks can be defined on actuator construction: | ||
```js | ||
const healthChecks = { | ||
db: async () => { | ||
return { | ||
status: (await isDbAlive()) ? 'UP' : 'DOWN', | ||
freeConnections: await freeDbConnectionsLeft() | ||
} | ||
}, | ||
//... | ||
}; | ||
app.use(actuator(healthChecks)); | ||
``` | ||
A check can return an object of an arbitrary structure. If the returned structure contains `status` field, it will be counted in the aggregated status. | ||
@@ -34,0 +60,0 @@ ### /env |
142
test.js
const actuator = require('.'); | ||
const Koa = require('koa'); | ||
const supertest = require('supertest'); | ||
const request = require('supertest'); | ||
const assert = require('chai').assert; | ||
const app = new Koa(); | ||
describe('Koa actuator', () => { | ||
describe('koa-actuator', () => { | ||
let request; | ||
describe('/health', () => { | ||
it('should return 200 and status UP if no checks defined', (done) => { | ||
//arrange | ||
const app = new Koa(); | ||
app.use(actuator()); | ||
before(() => { | ||
app.use(actuator); | ||
request = supertest.agent(app.listen()); | ||
}); | ||
//act & assert | ||
request(app.callback()) | ||
.get('/health') | ||
.expect(200) | ||
.expect({status: 'UP'}, done); | ||
}); | ||
describe('/health', () => { | ||
it('GET should return 200 and status UP', (done) => { | ||
request | ||
it('should return 200 and status UP if sync and async checks pass', (done) => { | ||
//arrange | ||
const app = new Koa(); | ||
app.use(actuator({ | ||
db: () => Promise.resolve({status: 'UP', freeConnections: 10}), | ||
redis: () => ({status: 'UP', usedMemory: '52m', uptimeDays: 16}) | ||
})); | ||
//act & assert | ||
request(app.callback()) | ||
.get('/health') | ||
.expect(200) | ||
.expect(/UP/, done); | ||
.expect({ | ||
status: 'UP', | ||
db: {status: 'UP', freeConnections: 10}, | ||
redis: {status: 'UP', usedMemory: '52m', uptimeDays: 16} | ||
}, done); | ||
}); | ||
it('should return 503 and status DOWN if any check fails', (done) => { | ||
//arrange | ||
const app = new Koa(); | ||
app.use(actuator({ | ||
db: () => Promise.resolve({status: 'UP', freeConnections: 10}), | ||
redis: () => Promise.resolve({status: 'DOWN', usedMemory: '52m', uptimeDays: 16}) | ||
})); | ||
//act & assert | ||
request(app.callback()) | ||
.get('/health') | ||
.expect(503) | ||
.expect({ | ||
status: 'DOWN', | ||
db: {status: 'UP', freeConnections: 10}, | ||
redis: {status: 'DOWN', usedMemory: '52m', uptimeDays: 16} | ||
}, done); | ||
}); | ||
it('should return 503 and status DOWN if a sync check throws exception', (done) => { | ||
//arrange | ||
const app = new Koa(); | ||
app.use(actuator({ | ||
db: () => { throw new Error('unexpected error'); }, | ||
redis: () => Promise.resolve({status: 'UP', usedMemory: '52m', uptimeDays: 16}) | ||
})); | ||
//act & assert | ||
request(app.callback()) | ||
.get('/health') | ||
.expect(503) | ||
.expect({ | ||
status: 'DOWN', | ||
db: {status: 'DOWN', error: 'unexpected error'}, | ||
redis: {status: 'UP', usedMemory: '52m', uptimeDays: 16} | ||
}, done); | ||
}); | ||
it('should return 503 and status DOWN if an async check rejects promise', (done) => { | ||
//arrange | ||
const app = new Koa(); | ||
app.use(actuator({ | ||
db: () => Promise.resolve({status: 'UP', freeConnections: 10}), | ||
redis: () => Promise.reject('unexpected async error') | ||
})); | ||
//act & assert | ||
request(app.callback()) | ||
.get('/health') | ||
.expect(503) | ||
.expect({ | ||
status: 'DOWN', | ||
db: {status: 'UP', freeConnections: 10}, | ||
redis: {status: 'DOWN', error: 'unexpected async error'} | ||
}, done); | ||
}); | ||
it('should return 503 and status DOWN if an async check times out', (done) => { | ||
//arrange | ||
const app = new Koa(); | ||
const options = {checkTimeout: 100}; | ||
app.use(actuator({ | ||
db: () => new Promise((resolve, reject) => setTimeout(() => resolve({status: 'UP', freeConnections: 10}), 3000)) | ||
}, options)); | ||
//act & assert | ||
request(app.callback()) | ||
.get('/health') | ||
.expect(503) | ||
.expect({ | ||
status: 'DOWN', | ||
db: {status: 'DOWN', error: 'Check timed out'}, | ||
}, done); | ||
}); | ||
}); | ||
describe('/info', () => { | ||
it('GET should return 200 and version', (done) => { | ||
request | ||
it('should return 200 and version', (done) => { | ||
//arrange | ||
const app = new Koa(); | ||
app.use(actuator()); | ||
//act & assert | ||
request(app.callback()) | ||
.get('/info') | ||
@@ -39,8 +135,10 @@ .expect(200) | ||
describe('/env', () => { | ||
it('GET should return 200 and the list of environment variables', (done) => { | ||
it('should return 200 and the list of environment variables', (done) => { | ||
//arrange | ||
process.env.TEST_VAR = 'test'; | ||
const app = new Koa(); | ||
app.use(actuator()); | ||
//act & assert | ||
request | ||
request(app.callback()) | ||
.get('/env') | ||
@@ -60,5 +158,7 @@ .expect(200) | ||
process.env.PASSWORD = 'test-password'; | ||
const app = new Koa(); | ||
app.use(actuator()); | ||
//act & assert | ||
request | ||
request(app.callback()) | ||
.get('/env') | ||
@@ -78,4 +178,8 @@ .expect(200) | ||
it('should return 200 and show some service info (like uptime, heap usage etc)', (done) => { | ||
//arrange | ||
const app = new Koa(); | ||
app.use(actuator()); | ||
//act & assert | ||
request | ||
request(app.callback()) | ||
.get('/metrics') | ||
@@ -94,2 +198,2 @@ .expect(200) | ||
}); | ||
}); | ||
}); |
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
14628
278
114