prometheus-api-metrics
Version:
API and process monitoring with Prometheus for Node.js micro-service
112 lines • 5.42 kB
JavaScript
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');
class ExpressMiddleware {
constructor(setupOptions) {
this.setupOptions = setupOptions;
}
_collectDefaultServerMetrics(timeout) {
const NUMBER_OF_CONNECTIONS_METRICS_NAME = 'expressjs_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 Express.js server'
});
if (this.setupOptions.server) {
setInterval(this._getConnections.bind(this), timeout).unref();
}
}
_getConnections() {
if (this.setupOptions && 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(req, res) {
const responseLength = parseInt(res.get('Content-Length')) || 0;
const route = this._getRoute(req);
if (route && utils.shouldLogMetrics(this.setupOptions.excludeRoutes, route)) {
const labels = Object.assign({ method: req.method, route, code: res.statusCode }, this.setupOptions.extractAdditionalLabelValuesFn(req, res));
this.setupOptions.requestSizeHistogram.observe(labels, req.metrics.contentLength);
req.metrics.timer(labels);
this.setupOptions.responseSizeHistogram.observe(labels, responseLength);
debug(`metrics updated, request length: ${req.metrics.contentLength}, response length: ${responseLength}`);
}
}
_getRoute(req) {
let route = req.baseUrl;
if (req.route) {
if (req.route.path !== '/') {
route = route ? route + req.route.path : req.route.path;
}
if (!route || route === '' || typeof route !== 'string') {
route = req.originalUrl.split('?')[0];
}
else {
const splittedRoute = route.split('/');
const splittedUrl = req.originalUrl.split('?')[0].split('/');
const routeIndex = splittedUrl.length - splittedRoute.length + 1;
const baseUrl = splittedUrl.slice(0, routeIndex).join('/');
route = baseUrl + route;
}
if (this.setupOptions.includeQueryParams === true && Object.keys(req.query).length > 0) {
route = `${route}?${Object.keys(req.query).sort().map((queryParam) => `${queryParam}=<?>`).join('&')}`;
}
}
if (typeof req.params === 'object') {
Object.keys(req.params).forEach((paramName) => {
route = route.replace(req.params[paramName], ':' + paramName);
});
}
if (!route || route === '') {
route = 'N/A';
}
return route;
}
middleware(req, res, next) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.setupOptions.server && req.socket) {
this.setupOptions.server = req.socket.server;
this._collectDefaultServerMetrics(this.setupOptions.defaultMetricsInterval);
}
const routeUrl = req.originalUrl || req.url;
if (routeUrl === this.setupOptions.metricsRoute) {
debug('Request to /metrics endpoint');
res.set('Content-Type', Prometheus.register.contentType);
return res.end(yield Prometheus.register.metrics());
}
if (routeUrl === `${this.setupOptions.metricsRoute}.json`) {
debug('Request to /metrics endpoint');
return res.json(yield Prometheus.register.getMetricsAsJSON());
}
req.metrics = {
timer: this.setupOptions.responseTimeHistogram.startTimer(),
contentLength: parseInt(req.get('content-length')) || 0
};
debug(`Set start time and content length for request. url: ${routeUrl}, method: ${req.method}`);
res.once('finish', () => {
debug('on finish.');
this._handleResponse(req, res);
});
return next();
});
}
;
}
module.exports = ExpressMiddleware;
//# sourceMappingURL=express-middleware.js.map