-
Notifications
You must be signed in to change notification settings - Fork 42
/
express-middleware.js
128 lines (107 loc) · 4.83 KB
/
express-middleware.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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 = {
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('&')}`;
}
}
// nest.js - build request url pattern if exists
if (typeof req.params === 'object') {
Object.keys(req.params).forEach((paramName) => {
route = route.replace(req.params[paramName], ':' + paramName);
});
}
// this condition will evaluate to true only in
// express framework and no route was found for the request. if we log this metrics
// we'll risk in a memory leak since the route is not a pattern but a hardcoded string.
if (!route || route === '') {
// if (!req.route && res && res.statusCode === 404) {
route = 'N/A';
}
return route;
}
async middleware(req, res, next) {
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(await Prometheus.register.metrics());
}
if (routeUrl === `${this.setupOptions.metricsRoute}.json`) {
debug('Request to /metrics endpoint');
return res.json(await 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;