This repository has been archived by the owner on Jan 8, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 60
/
index.js
162 lines (142 loc) · 4.81 KB
/
index.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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
const express = require('express');
const Prometheus = require('prom-client');
const ResponseTime = require('response-time');
const {
requestCountGenerator,
requestDurationGenerator,
requestLengthGenerator,
responseLengthGenerator,
} = require('./metrics');
const {
normalizeStatusCode,
normalizePath,
} = require('./normalizers');
const defaultOptions = {
metricsPath: '/metrics',
metricsApp: null,
authenticate: null,
collectDefaultMetrics: true,
collectGCMetrics: false,
// buckets for response time from 0.05s to 2.5s
// these are arbitrary values since i dont know any better ¯\_(ツ)_/¯
requestDurationBuckets: Prometheus.exponentialBuckets(0.05, 1.75, 8),
requestLengthBuckets: [],
responseLengthBuckets: [],
extraMasks: [],
customLabels: [],
transformLabels: null,
normalizeStatus: true,
};
module.exports = (userOptions = {}) => {
const options = { ...defaultOptions, ...userOptions };
const originalLabels = ['route', 'method', 'status'];
options.customLabels = new Set([...originalLabels, ...options.customLabels]);
options.customLabels = [...options.customLabels];
const { metricsPath, metricsApp, normalizeStatus } = options;
const app = express();
app.disable('x-powered-by');
const requestDuration = requestDurationGenerator(
options.customLabels,
options.requestDurationBuckets,
options.prefix,
);
const requestCount = requestCountGenerator(
options.customLabels,
options.prefix,
);
const requestLength = requestLengthGenerator(
options.customLabels,
options.requestLengthBuckets,
options.prefix,
);
const responseLength = responseLengthGenerator(
options.customLabels,
options.responseLengthBuckets,
options.prefix,
);
/**
* Corresponds to the R(equest rate), E(error rate), and D(uration of requests),
* of the RED metrics.
*/
const redMiddleware = ResponseTime((req, res, time) => {
const { originalUrl, method } = req;
// will replace ids from the route with `#val` placeholder this serves to
// measure the same routes, e.g., /image/id1, and /image/id2, will be
// treated as the same route
const route = normalizePath(originalUrl, options.extraMasks);
if (route !== metricsPath) {
const status = normalizeStatus
? normalizeStatusCode(res.statusCode) : res.statusCode.toString();
const labels = { route, method, status };
if (typeof options.transformLabels === 'function') {
options.transformLabels(labels, req, res);
}
requestCount.inc(labels);
// observe normalizing to seconds
requestDuration.observe(labels, time / 1000);
// observe request length
if (options.requestLengthBuckets.length) {
const reqLength = req.get('Content-Length');
if (reqLength) {
requestLength.observe(labels, Number(reqLength));
}
}
// observe response length
if (options.responseLengthBuckets.length) {
const resLength = res.get('Content-Length');
if (resLength) {
responseLength.observe(labels, Number(resLength));
}
}
}
});
if (options.collectDefaultMetrics) {
// when this file is required, we will start to collect automatically
// default metrics include common cpu and head usage metrics that can be
// used to calculate saturation of the service
Prometheus.collectDefaultMetrics({
prefix: options.prefix,
});
}
if (options.collectGCMetrics) {
// if the option has been turned on, we start collecting garbage
// collector metrics too. using try/catch because the dependency is
// optional and it could not be installed
try {
/* eslint-disable global-require */
/* eslint-disable import/no-extraneous-dependencies */
const gcStats = require('prometheus-gc-stats');
/* eslint-enable import/no-extraneous-dependencies */
/* eslint-enable global-require */
const startGcStats = gcStats(Prometheus.register, {
prefix: options.prefix,
});
startGcStats();
} catch (err) {
// the dependency has not been installed, skipping
}
}
app.use(redMiddleware);
/**
* Metrics route to be used by prometheus to scrape metrics
*/
const routeApp = metricsApp || app;
routeApp.get(metricsPath, async (req, res, next) => {
if (typeof options.authenticate === 'function') {
let result = null;
try {
result = await options.authenticate(req);
} catch (err) {
// treat errors as failures to authenticate
}
// the requester failed to authenticate, then return next, so we don't
// hint at the existance of this route
if (!result) {
return next();
}
}
res.set('Content-Type', Prometheus.register.contentType);
return res.end(await Prometheus.register.metrics());
});
return app;
};