UNPKG

prometheus-api-metrics

Version:

API and process monitoring with Prometheus for Node.js micro-service

132 lines 6.16 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; const Prometheus = require('prom-client'); require('pkginfo')(module, ['name']); const debug = require('debug')(module.exports.name); const utils = require('./utils'); const WILDCARD_ROUTE_ENDING = '(.*)'; class KoaMiddleware { constructor(setupOptions) { this.setupOptions = setupOptions; } _collectDefaultServerMetrics(timeout) { const NUMBER_OF_CONNECTIONS_METRICS_NAME = 'koajs_number_of_open_connections'; this.setupOptions.numberOfConnectionsGauge = Prometheus.register.getSingleMetric(NUMBER_OF_CONNECTIONS_METRICS_NAME) || new Prometheus.Gauge({ name: NUMBER_OF_CONNECTIONS_METRICS_NAME, help: 'Number of open connections to the Koa.js server' }); if (this.setupOptions.server) { setInterval(this._getConnections.bind(this), timeout).unref(); } } _getConnections() { if (this.setupOptions.server) { this.setupOptions.server.getConnections((error, count) => { if (error) { debug('Error while collection number of open connections', error); } else { this.setupOptions.numberOfConnectionsGauge.set(count); } }); } } _handleResponse(ctx) { const responseLength = parseInt(ctx.response.get('Content-Length')) || 0; const route = this._getRoute(ctx) || 'N/A'; if (route && utils.shouldLogMetrics(this.setupOptions.excludeRoutes, route)) { const labels = Object.assign({ method: ctx.req.method, route, code: ctx.res.statusCode }, this.setupOptions.extractAdditionalLabelValuesFn(ctx)); this.setupOptions.requestSizeHistogram.observe(labels, ctx.req.metrics.contentLength); ctx.req.metrics.timer(labels); this.setupOptions.responseSizeHistogram.observe(labels, responseLength); debug(`metrics updated, request length: ${ctx.req.metrics.contentLength}, response length: ${responseLength}`); } } _getRoute(ctx) { let route; if (ctx._matchedRoute && !ctx._matchedRoute.endsWith(WILDCARD_ROUTE_ENDING)) { route = ctx._matchedRoute; route = route.endsWith('/') ? route.substring(0, route.length - 1) : route; } else if (ctx._matchedRoute) { route = this._handleSubRoutes(ctx._matchedRoute, ctx.originalUrl, ctx.request.method, ctx.router); } if (this.setupOptions.includeQueryParams === true && Object.keys(ctx.query).length > 0) { route = `${route || '/'}?${Object.keys(ctx.query).sort().map((queryParam) => `${queryParam}=<?>`).join('&')}`; } return route; } _handleSubRoutes(matchedRoute, originalUrl, method, router) { let route; const routeStart = matchedRoute.substring(0, matchedRoute.length - WILDCARD_ROUTE_ENDING.length); let url = this._removeQueryFromUrl(originalUrl).substring(routeStart.length); let matchedRoutes = router.match(url, method); if (matchedRoutes.path.length > 0) { route = this._findFirstProperRoute(matchedRoutes.path); return routeStart + route; } else { url = this._removeQueryFromUrl(originalUrl); matchedRoutes = router.match(url, method); if (matchedRoutes.path.length > 0) { route = this._findFirstProperRoute(matchedRoutes.path); return route; } } } _findFirstProperRoute(routes) { const properRoute = routes.find(route => { if (!route.path.endsWith('(.*)')) { return route; } }); if (!properRoute) { return undefined; } let route = properRoute.path; route = route.endsWith('/') ? route.substring(0, route.length - 1) : route; return route; } _removeQueryFromUrl(url) { return url.split('?')[0]; } middleware(ctx, next) { return __awaiter(this, void 0, void 0, function* () { if (!this.setupOptions.server && ctx.req.socket) { this.setupOptions.server = ctx.req.socket.server; this._collectDefaultServerMetrics(this.setupOptions.defaultMetricsInterval); } if (ctx.req.url === this.setupOptions.metricsRoute) { debug('Request to /metrics endpoint'); ctx.set('Content-Type', Prometheus.register.contentType); ctx.body = yield Prometheus.register.metrics(); return next(); } if (ctx.req.url === `${this.setupOptions.metricsRoute}.json`) { debug('Request to /metrics endpoint'); ctx.body = yield Prometheus.register.getMetricsAsJSON(); return next(); } ctx.req.metrics = { timer: this.setupOptions.responseTimeHistogram.startTimer(), contentLength: parseInt(ctx.request.get('content-length')) || 0 }; debug(`Set start time and content length for request. url: ${ctx.req.url}, method: ${ctx.req.method}`); ctx.res.once('finish', () => { debug('on finish.'); this._handleResponse(ctx); }); return next(); }); } ; } module.exports = KoaMiddleware; //# sourceMappingURL=koa-middleware.js.map