1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.MetricsGatherer = exports.MetricsGathererError = void 0;
|
4 | const express = require("express");
|
5 | const prometheus = require("prom-client");
|
6 | const typed_error_1 = require("typed-error");
|
7 | const Debug = require("debug");
|
8 | const debug = Debug('node-metrics-gatherer');
|
9 | const collect_1 = require("./collectors/api/collect");
|
10 | const types_1 = require("./types");
|
11 | class MetricsGathererError extends typed_error_1.TypedError {
|
12 | }
|
13 | exports.MetricsGathererError = MetricsGathererError;
|
14 | const constructors = {
|
15 | gauge: new types_1.MetricConstructor(prometheus.Gauge),
|
16 | counter: new types_1.MetricConstructor(prometheus.Counter),
|
17 | summary: new types_1.MetricConstructor(prometheus.Summary),
|
18 | histogram: new types_1.MetricConstructor(prometheus.Histogram),
|
19 | };
|
20 | class MetricsGatherer {
|
21 | constructor() {
|
22 | this.initState();
|
23 | this.setupDescribe();
|
24 | this.client = prometheus;
|
25 | }
|
26 | initState() {
|
27 | try {
|
28 | this.metrics = {
|
29 | gauge: {},
|
30 | counter: {},
|
31 | histogram: {},
|
32 | summary: {},
|
33 | };
|
34 | this.meta = {};
|
35 | this.internalErrorCount = 0;
|
36 | }
|
37 | catch (e) {
|
38 | this.err(e);
|
39 | }
|
40 | }
|
41 | setupDescribe() {
|
42 | this.describe = {};
|
43 | for (const kind of ['gauge', 'counter', 'histogram', 'summary']) {
|
44 | this.describe[kind] = (name, help, customParams = {}) => {
|
45 | if (this.meta[name]) {
|
46 | throw new MetricsGathererError(`tried to describe metric "${name}" twice`);
|
47 | }
|
48 | else {
|
49 | this.meta[name] = {
|
50 | kind,
|
51 | help,
|
52 | customParams,
|
53 | };
|
54 | }
|
55 | };
|
56 | }
|
57 | }
|
58 | gauge(name, val, labels = {}) {
|
59 | try {
|
60 | this.ensureExists('gauge', name, labels);
|
61 | this.metrics.gauge[name].set(labels, val);
|
62 | }
|
63 | catch (e) {
|
64 | this.err(e);
|
65 | }
|
66 | }
|
67 | inc(name, val = 1, labels = {}) {
|
68 | try {
|
69 | const kind = /.+_total$/.test(name) ? 'counter' : 'gauge';
|
70 | this.ensureExists(kind, name, labels);
|
71 | if (!this.checkMetricType(name, ['gauge', 'counter'])) {
|
72 | throw new MetricsGathererError(`Tried to increment non-gauge, non-counter metric ${name}`);
|
73 | }
|
74 | if (this.meta[name].kind === 'gauge') {
|
75 | this.metrics.gauge[name].inc(labels, val);
|
76 | }
|
77 | else {
|
78 | this.metrics.counter[name].inc(labels, val);
|
79 | }
|
80 | }
|
81 | catch (e) {
|
82 | this.err(e);
|
83 | }
|
84 | }
|
85 | dec(name, val = 1, labels = {}) {
|
86 | try {
|
87 | this.ensureExists('gauge', name, labels);
|
88 | if (!this.checkMetricType(name, ['gauge'])) {
|
89 | throw new MetricsGathererError(`Tried to decrement non-gauge metric ${name}`);
|
90 | }
|
91 | this.metrics.gauge[name].dec(labels, val);
|
92 | }
|
93 | catch (e) {
|
94 | this.err(e);
|
95 | }
|
96 | }
|
97 | counter(name, val = 1, labels = {}) {
|
98 | try {
|
99 | this.ensureExists('counter', name, labels);
|
100 | this.metrics.counter[name].inc(labels, val);
|
101 | }
|
102 | catch (e) {
|
103 | this.err(e);
|
104 | }
|
105 | }
|
106 | summary(name, val, labels = {}, customParams = {}) {
|
107 | try {
|
108 | this.ensureExists('summary', name, labels, customParams);
|
109 | this.metrics.summary[name].observe(labels, val);
|
110 | }
|
111 | catch (e) {
|
112 | this.err(e);
|
113 | }
|
114 | }
|
115 | histogram(name, val, labels = {}, customParams = {}) {
|
116 | try {
|
117 | this.ensureExists('histogram', name, labels, customParams);
|
118 | this.metrics.histogram[name].observe(labels, val);
|
119 | }
|
120 | catch (e) {
|
121 | this.err(e);
|
122 | }
|
123 | }
|
124 | histogramSummary(name, val, labels = {}) {
|
125 | try {
|
126 | this.histogram(`${name}_hist`, val, labels);
|
127 | this.summary(`${name}_summary`, val, labels);
|
128 | }
|
129 | catch (e) {
|
130 | this.err(e);
|
131 | }
|
132 | }
|
133 | checkMetricType(name, kinds) {
|
134 | try {
|
135 | return kinds.includes(this.meta[name].kind);
|
136 | }
|
137 | catch (e) {
|
138 | this.err(e);
|
139 | }
|
140 | }
|
141 | getMetric(name) {
|
142 | if (this.meta[name]) {
|
143 | return this.metrics[this.meta[name].kind][name];
|
144 | }
|
145 | }
|
146 | exists(name) {
|
147 | return this.getMetric(name) != null;
|
148 | }
|
149 | ensureExists(kind, name, labels = {}, customParams = {}) {
|
150 | try {
|
151 | if (this.exists(name)) {
|
152 | return;
|
153 | }
|
154 | if (!this.meta[name]) {
|
155 | this.describe[kind](name, `undescribed ${kind} metric`, Object.assign({ labelNames: Object.keys(labels) }, customParams));
|
156 | }
|
157 | else if (this.meta[name].kind !== kind) {
|
158 | throw new MetricsGathererError(`tried to use ${name} twice - first as ` +
|
159 | `${this.meta[name].kind}, then as ${kind}`);
|
160 | }
|
161 | this.metrics[kind][name] = constructors[kind].create(Object.assign(Object.assign({ name, help: this.meta[name].help, labelNames: Object.keys(labels) }, customParams), this.meta[name].customParams));
|
162 | }
|
163 | catch (e) {
|
164 | this.err(e);
|
165 | }
|
166 | }
|
167 | reset(name) {
|
168 | try {
|
169 | if (!name) {
|
170 | prometheus.register.resetMetrics();
|
171 | }
|
172 | else {
|
173 | const metric = this.getMetric(name);
|
174 | if (metric) {
|
175 | metric.reset();
|
176 | }
|
177 | }
|
178 | }
|
179 | catch (e) {
|
180 | this.err(e);
|
181 | }
|
182 | }
|
183 | exportOn(port, path = '/metrics', requestHandler) {
|
184 | const app = express();
|
185 | app.use(path, requestHandler !== null && requestHandler !== void 0 ? requestHandler : this.requestHandler());
|
186 | app.listen(port);
|
187 | }
|
188 | requestHandler(authTest) {
|
189 | return (req, res) => {
|
190 | if (authTest && !authTest(req)) {
|
191 | return res.status(403).send();
|
192 | }
|
193 | res.writeHead(200, { 'Content-Type': 'text/plain' });
|
194 | res.end(prometheus.register.metrics());
|
195 | };
|
196 | }
|
197 | aggregateRequestHandler(authTest) {
|
198 | const aggregatorRegistry = new prometheus.AggregatorRegistry();
|
199 | return (req, res) => {
|
200 | if (authTest && !authTest(req)) {
|
201 | return res.status(403).send();
|
202 | }
|
203 | aggregatorRegistry
|
204 | .clusterMetrics()
|
205 | .then((metrics) => {
|
206 | res.set('Content-Type', aggregatorRegistry.contentType);
|
207 | res.send(metrics);
|
208 | })
|
209 | .catch((err) => {
|
210 | this.err(err);
|
211 | res.status(500).send();
|
212 | });
|
213 | };
|
214 | }
|
215 | collectDefaultMetrics() {
|
216 | prometheus.collectDefaultMetrics();
|
217 | }
|
218 | collectAPIMetrics(app) {
|
219 | app.use(collect_1.collectAPIMetrics(this));
|
220 | return app;
|
221 | }
|
222 | output() {
|
223 | try {
|
224 | return prometheus.register.metrics();
|
225 | }
|
226 | catch (e) {
|
227 | this.err(e);
|
228 | return '';
|
229 | }
|
230 | }
|
231 | clear() {
|
232 | try {
|
233 | prometheus.register.clear();
|
234 | this.initState();
|
235 | }
|
236 | catch (e) {
|
237 | this.err(e);
|
238 | }
|
239 | }
|
240 | err(e) {
|
241 | debug(e);
|
242 | this.internalErrorCount++;
|
243 | }
|
244 | }
|
245 | exports.MetricsGatherer = MetricsGatherer;
|
246 |
|
\ | No newline at end of file |