UNPKG

@openreply/emw-health-check

Version:

express middleware enables health checks for your service

114 lines (99 loc) 4.35 kB
/** * @file express middleware to handle health checks * @author Dominik Riedel <d.riedel@reply.de> * @copyright Dominik Riedel 2018, MIT License */ 'use strict'; const responseWrapper = require('./responseWrapper'); let errorCalls = 0; /** * @typedef HealthCheckConfiguration * @type {Object} * @property {string} serviceName - name of the service that will be appended to the trace id string * @property {string} checkInternalErrors - whether or not to check for internal server errors on all endpoints. (default: true) * @property {string} errorThreshold - the amount of internal server errors that may happen before health of service is considered unhealthy (default: 10) * @property {string} routesToCheck - the actual routes that are supposed to be observed for ongoing error behavior (default: *) * @property {string} responseCodesToCheck - the HTTP response codes that indicate an error behavior (default: 5XX) */ /** * @callback RequestMiddleware * @param {Object} req the express request object (see http://expressjs.com/en/4x/api.html#req) * @param {Object} res the express response object (see http://expressjs.com/en/4x/api.html#res) * @param {Object} next the next middleware object to be executed from the express middleware list */ /** * Enables GET /health endpoint to retrieve the health of the service. * * This middleware also intercepts the response of every request made to the service to check if there is an ongoing problem * with a steady response status of HTTP 500 (internal server error). If so the service health will switch to `unhealthy`. * * By default all API endpints (except the GET /health) endpoint will be monitored. * By default the service will switch to and `unhealthy` state if the service will responde with HTTP status 500 for 10 sequential requests. * * @param {RequestIdConfiguration} options - configuration options for this middleware * @returns {RequestMiddleware} * @throws TypeError in case that the middleware has been passed incorrectly to express * @throws TypeError if not passed an options object * @throws TypeError if not given a serviceName with the options */ function healthCheck(options) { // ensure that the middleware has not been passed as a function reference if (arguments.length === 3) { throw new TypeError('the requestId middleware has to be passed via app.use(healthCheck()); and not as a reference e.g. app.use(healthCheck);'); } if (typeof options !== 'object') { throw new TypeError('the healthCheck middleware expects to be passed an options object'); } if (typeof options.serviceName !== 'string') { throw new TypeError('you need to pass a serviceName to the healthCheck middleware'); } if (typeof options.serviceVersion !== 'string') { throw new TypeError('you need to pass a serviceVersion to the healthCheck middleware'); } // overwrite default values (if any) const { serviceName, serviceVersion, checkInternalErrors, errorThreshold } = Object.assign({ checkInternalErrors: true, errorThreshold: 10, responseCodesToCheck: [500] }, options); const checkNameAndVersion = (query = {}) => { const { name, version } = query; // check service name if (name && name !== serviceName) { return false; } // check service version if (version && version !== serviceVersion) { return false; } return true; }; return async (req, res, next) => { // intercept on /health endpoint and return health result if (req.path.toLowerCase() === '/health' && ['get', 'head'].indexOf(req.method.toLowerCase()) >= 0) { if (!checkNameAndVersion(req.query) || (checkInternalErrors && errorCalls > errorThreshold)) { return res.status(500).json({ service: serviceName, status: 'unhealthy' }); } return res.status(200).json({ service: serviceName, status: 'healthy' }); } // Circuit Beaker to check API reponses and set errorCalls responseWrapper.wrapResponse(req, res, (wrappedRes) => { const { statusCode } = wrappedRes; if (req.path !== '/health' && statusCode === 500) errorCalls += 1; if (req.path !== '/health' && statusCode !== 500) errorCalls = 0; }); return next(); }; } module.exports = healthCheck;