-
-
Notifications
You must be signed in to change notification settings - Fork 278
Expand file tree
/
Copy pathprometheus.js
More file actions
140 lines (119 loc) · 4.08 KB
/
Copy pathprometheus.js
File metadata and controls
140 lines (119 loc) · 4.08 KB
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
'use strict';
const http = require('http');
const https = require('https');
const log = require('npmlog');
const certs = require('./lib/certs');
const metrics = require('./lib/metrics');
const { metricsConfig } = require('./lib/metrics-config');
const METRICS_PATH = '/metrics';
const ALLOWED_METHODS = ['GET', 'HEAD'];
/**
* Sends a plain text response.
*
* @param {http.ServerResponse} res HTTP response.
* @param {Number} statusCode HTTP status code.
* @param {String} body Response body.
* @param {Object} [headers] Additional response headers.
* @returns {void}
*/
function sendText(res, statusCode, body, headers) {
res.statusCode = statusCode;
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
Object.keys(headers || {}).forEach(key => res.setHeader(key, headers[key]));
res.end(body);
}
/**
* Handles a request to the standalone Prometheus endpoint.
*
* @param {http.IncomingMessage} req HTTP request.
* @param {http.ServerResponse} res HTTP response.
* @returns {Promise<void>}
*/
async function handleRequest(req, res) {
let path = (req.url || '').split('?').shift();
// scrape configs and path rewriting proxies often add a trailing slash
if (path.length > 1 && path.at(-1) === '/') {
path = path.slice(0, -1);
}
if (path !== METRICS_PATH) {
return sendText(res, 404, 'Not Found\n');
}
if (!ALLOWED_METHODS.includes(req.method)) {
return sendText(res, 405, 'Method Not Allowed\n', { Allow: ALLOWED_METHODS.join(', ') });
}
if (req.method === 'HEAD') {
// node discards the body of a HEAD response, so there is nothing to collect it for
res.statusCode = 200;
res.setHeader('Content-Type', metrics.contentType);
return res.end();
}
try {
let output = await metrics.getMetrics();
res.statusCode = 200;
res.setHeader('Content-Type', metrics.contentType);
res.end(output);
} catch (err) {
log.error('Metrics', 'Failed to collect metrics: %s', err.message);
sendText(res, 500, 'error: metrics collection failed\n');
}
}
/**
* Creates an HTTP or HTTPS server for Prometheus metrics.
*
* @param {Object} options Metrics listener configuration.
* @returns {http.Server|https.Server} Metrics server.
*/
function createServer(options) {
let listener = (req, res) => {
handleRequest(req, res).catch(err => {
log.error('Metrics', 'Failed to handle request: %s', err.message);
if (res.headersSent) {
return res.end();
}
sendText(res, 500, 'error: metrics request failed\n');
});
};
if (!options.secure) {
return http.createServer(listener);
}
let serverOptions = {};
certs.loadTLSOptions(serverOptions, 'metrics');
let server = https.createServer(serverOptions, listener);
// pick up renewed certificates on config reload, like the other TLS listeners do
certs.registerReload(server, 'metrics', serverOptions);
return server;
}
/**
* Starts the standalone Prometheus listener.
*
* @param {Object} options Metrics listener configuration.
* @param {Function} done Completion callback.
* @returns {void}
*/
function start(options, done) {
options = options || {};
// must match how lib/metrics.js decides whether metrics are collected at all
if (options.enabled !== true) {
return setImmediate(() => done(null, false));
}
let started = false;
let server = createServer(options);
server.on('error', err => {
if (!started) {
started = true;
return done(err);
}
log.error('Metrics', err);
});
server.listen(options.port, options.host, () => {
if (started) {
return server.close();
}
started = true;
let address = server.address();
log.info('Metrics', '%s server listening on %s:%s', options.secure ? 'HTTPS' : 'HTTP', options.host || '0.0.0.0', address && address.port);
done(null, server);
});
}
module.exports = done => start(metricsConfig, done);
module.exports.start = start;