@adalo/metrics 0.1.140 → 0.1.141
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +1 -1
- package/lib/metrics/baseMetricsClient.d.ts +15 -26
- package/lib/metrics/baseMetricsClient.d.ts.map +1 -1
- package/lib/metrics/baseMetricsClient.js +91 -96
- package/lib/metrics/baseMetricsClient.js.map +1 -1
- package/package.json +1 -1
- package/scripts/README.md +22 -0
- package/src/metrics/baseMetricsClient.js +87 -127
package/.env.example
CHANGED
|
@@ -5,7 +5,7 @@ METRICS_ENABLED=true
|
|
|
5
5
|
# Set to true to collect app_requests_total and app_requests_total_duration (default: false)
|
|
6
6
|
# METRICS_HTTP_ENABLED=false
|
|
7
7
|
METRICS_LOG_VALUES=true
|
|
8
|
-
METRICS_PUSHGATEWAY_URL=https://
|
|
8
|
+
METRICS_PUSHGATEWAY_URL=https://vm-agent.infradalogs.adalo.com
|
|
9
9
|
METRICS_PUSHGATEWAY_SECRET="METRICS_PUSHGATEWAY_SECRET"
|
|
10
10
|
METRICS_INTERVAL_SEC=60
|
|
11
11
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* BaseMetricsClient provides common functionality for all metrics clients.
|
|
3
|
-
* Handles registry setup,
|
|
3
|
+
* Handles registry setup, push to remote (VM-agent), default labels, and common operations.
|
|
4
|
+
* Always pushes registry to the configured URL (POST Prometheus text format + Basic auth). No Pushgateway.
|
|
4
5
|
*/
|
|
5
6
|
export class BaseMetricsClient {
|
|
6
7
|
/**
|
|
@@ -10,8 +11,8 @@ export class BaseMetricsClient {
|
|
|
10
11
|
* @param {string} [config.processType] Process type (web, worker, etc.)
|
|
11
12
|
* @param {boolean} [config.enabled] Enable metrics collection
|
|
12
13
|
* @param {boolean} [config.logValues] Log metrics values to console
|
|
13
|
-
* @param {string} [config.pushgatewayUrl]
|
|
14
|
-
* @param {string} [config.pushgatewaySecret]
|
|
14
|
+
* @param {string} [config.pushgatewayUrl] Push URL (VM-agent import endpoint, e.g. .../api/v1/import/prometheus). /metrics is for GET (scrape), not POST (push).
|
|
15
|
+
* @param {string} [config.pushgatewaySecret] Basic auth secret (Base64 of user:password)
|
|
15
16
|
* @param {number} [config.intervalSec] Interval in seconds for pushing metrics
|
|
16
17
|
* @param {boolean} [config.removeOldMetrics] Enable to clear metrics by service name
|
|
17
18
|
* @param {function} [config.startupValidation] Add to validate on start push.
|
|
@@ -47,7 +48,7 @@ export class BaseMetricsClient {
|
|
|
47
48
|
dyno_id: string;
|
|
48
49
|
process_type: string;
|
|
49
50
|
};
|
|
50
|
-
gateway:
|
|
51
|
+
gateway: any;
|
|
51
52
|
gauges: {};
|
|
52
53
|
counters: {};
|
|
53
54
|
countersFunctions: {};
|
|
@@ -125,34 +126,22 @@ export class BaseMetricsClient {
|
|
|
125
126
|
*/
|
|
126
127
|
private _clearOldWorkers;
|
|
127
128
|
/**
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* @param {Object} [params]
|
|
131
|
-
* @param {string} [params.jobName] Job name (defaults to appName)
|
|
132
|
-
* @param {Object} [params.groupings] Grouping labels
|
|
133
|
-
* @param {string} [params.groupings.process_type] Process type label
|
|
134
|
-
* @param {string} [params.groupings.instance] Instance/dyno ID
|
|
129
|
+
* No-op (no Pushgateway). Kept for API compatibility.
|
|
135
130
|
* @returns {Promise<void>}
|
|
136
131
|
*/
|
|
137
|
-
gatewayDelete: (
|
|
138
|
-
jobName?: string | undefined;
|
|
139
|
-
groupings?: {
|
|
140
|
-
process_type?: string | undefined;
|
|
141
|
-
instance?: string | undefined;
|
|
142
|
-
} | undefined;
|
|
143
|
-
} | undefined) => Promise<void>;
|
|
132
|
+
gatewayDelete: () => Promise<void>;
|
|
144
133
|
/**
|
|
145
|
-
* Push
|
|
134
|
+
* Push registry to configured URL (VM-agent). POST Prometheus text format + Basic auth.
|
|
146
135
|
*
|
|
147
|
-
* @param {object} [params]
|
|
148
|
-
* @param {string} [params.jobName]
|
|
149
|
-
* @param {object} [params.groupings]
|
|
136
|
+
* @param {object} [params] Unused; kept for API compatibility.
|
|
150
137
|
* @returns {Promise<void>}
|
|
151
138
|
*/
|
|
152
|
-
gatewayPush: (params?:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
139
|
+
gatewayPush: (params?: object | undefined) => Promise<void>;
|
|
140
|
+
/**
|
|
141
|
+
* POST registry (Prometheus text format) to VM-agent. VM-agent accepts push at /api/v1/import/prometheus; /metrics is GET (scrape) only.
|
|
142
|
+
* @private
|
|
143
|
+
*/
|
|
144
|
+
private _pushToVMAgent;
|
|
156
145
|
/**
|
|
157
146
|
* Merge the default metric labels (`app`, `dyno_id`, `process_type`)
|
|
158
147
|
* with custom label names.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"baseMetricsClient.d.ts","sourceRoot":"","sources":["../../src/metrics/baseMetricsClient.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"baseMetricsClient.d.ts","sourceRoot":"","sources":["../../src/metrics/baseMetricsClient.js"],"names":[],"mappings":"AAKA;;;;GAIG;AACH;IACE;;;;;;;;;;;;;OAaG;IACH;QAZ2B,OAAO;QACP,MAAM;QACN,WAAW;QACV,OAAO;QACP,SAAS;QACV,cAAc;QACd,iBAAiB;QACjB,WAAW;QACV,gBAAgB;QACf,iBAAiB;QAClB,kBAAkB;OA+C7C;IA5CC,gBAA4E;IAC5E,eAAqE;IACrE,oBAG6B;IAC7B,iBAAuE;IACvE,mBAC+D;IAC/D,uBACoE;IACpE,kBAC0E;IAC1E,oBAGI;IACJ,wCAAiD;IACjD,4BAEoD;IAEpD,mBAAyF;IAEzF,uEAAsC;IAGtC;;;;MAIC;IAGD,aAAmB;IACnB,WAAgB;IAChB,aAAkB;IAClB,sBAA2B;IAE3B,mEAAmE;IACnE;YADkB,MAAM,SAAc,MAAM,GAAG,QAAQ,MAAM,CAAC;MACvC;IAMzB;;;;;;;;OAQG;IACH;QAN2B,IAAI,EAApB,MAAM;QACU,IAAI,EAApB,MAAM;QACuC,QAAQ,UAAzC,MAAM,GAAC,QAAQ,MAAM,CAAC;QACf,UAAU;UAC3B,OAAO,aAAa,EAAE,KAAK,CAuBvC;IAED;;;;;;;;;;;OAWG;IACH;QAR0B,IAAI,EAAnB,MAAM;QACS,IAAI,EAAnB,MAAM;QACY,UAAU;kBAEhB,MAAM,mBAAmB,MAAM,KAAK,IAAI,CAuB9D;IAED;;OAEG;IACH,6BAKC;IAED;;OAEG;IACH,kCAiCC;IAED,sEA2CC;IAED,iCAEC;IAED;;;;;;;;;OASG;IACH,iFAEC;IAED;;;OAGG;IACH,eAFa,QAAQ,IAAI,CAAC,CAOzB;IAED;;;;;;;;;OASG;IACH,yBAGC;IAED;;;OAGG;IACH,qBAFa,QAAQ,IAAI,CAAC,CAIzB;IAED;;;;;OAKG;IACH,8CAFa,QAAQ,IAAI,CAAC,CAYzB;IAED;;;OAGG;IACH,uBAyCC;IAED;;;;;;OAMG;IACH,6BAHW,MAAM,EAAE,KACN,MAAM,EAAE,CAIpB;IAED;;;;MAEC;IAED,gCAGC;IAID,8BAEC;IAED,gCAEC;IAED,4EAEC;IAED,sCAEC;IAED,2DAWC;CACF"}
|
|
@@ -2,10 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
const client = require('prom-client');
|
|
4
4
|
const https = require('https');
|
|
5
|
+
const http = require('http');
|
|
6
|
+
const {
|
|
7
|
+
URL
|
|
8
|
+
} = require('url');
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
11
|
* BaseMetricsClient provides common functionality for all metrics clients.
|
|
8
|
-
* Handles registry setup,
|
|
12
|
+
* Handles registry setup, push to remote (VM-agent), default labels, and common operations.
|
|
13
|
+
* Always pushes registry to the configured URL (POST Prometheus text format + Basic auth). No Pushgateway.
|
|
9
14
|
*/
|
|
10
15
|
class BaseMetricsClient {
|
|
11
16
|
/**
|
|
@@ -15,8 +20,8 @@ class BaseMetricsClient {
|
|
|
15
20
|
* @param {string} [config.processType] Process type (web, worker, etc.)
|
|
16
21
|
* @param {boolean} [config.enabled] Enable metrics collection
|
|
17
22
|
* @param {boolean} [config.logValues] Log metrics values to console
|
|
18
|
-
* @param {string} [config.pushgatewayUrl]
|
|
19
|
-
* @param {string} [config.pushgatewaySecret]
|
|
23
|
+
* @param {string} [config.pushgatewayUrl] Push URL (VM-agent import endpoint, e.g. .../api/v1/import/prometheus). /metrics is for GET (scrape), not POST (push).
|
|
24
|
+
* @param {string} [config.pushgatewaySecret] Basic auth secret (Base64 of user:password)
|
|
20
25
|
* @param {number} [config.intervalSec] Interval in seconds for pushing metrics
|
|
21
26
|
* @param {boolean} [config.removeOldMetrics] Enable to clear metrics by service name
|
|
22
27
|
* @param {function} [config.startupValidation] Add to validate on start push.
|
|
@@ -43,14 +48,9 @@ class BaseMetricsClient {
|
|
|
43
48
|
dyno_id: this.dynoId,
|
|
44
49
|
process_type: this.processType
|
|
45
50
|
};
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
},
|
|
50
|
-
agent: new https.Agent({
|
|
51
|
-
keepAlive: true
|
|
52
|
-
})
|
|
53
|
-
}, this._registry);
|
|
51
|
+
|
|
52
|
+
// Always push to configured URL (VM-agent). No Pushgateway.
|
|
53
|
+
this.gateway = null;
|
|
54
54
|
this.gauges = {};
|
|
55
55
|
this.counters = {};
|
|
56
56
|
this.countersFunctions = {};
|
|
@@ -175,16 +175,35 @@ class BaseMetricsClient {
|
|
|
175
175
|
if (this.startupValidation && !this.startupValidation()) {
|
|
176
176
|
return;
|
|
177
177
|
}
|
|
178
|
+
const runPush = () => {
|
|
179
|
+
if (customPushMetics && typeof customPushMetics === 'function') {
|
|
180
|
+
return Promise.resolve(customPushMetics());
|
|
181
|
+
}
|
|
182
|
+
return this.pushMetrics();
|
|
183
|
+
};
|
|
178
184
|
if (customPushMetics && typeof customPushMetics === 'function') {
|
|
179
185
|
setInterval(() => customPushMetics(), interval * 1000);
|
|
180
186
|
} else {
|
|
181
187
|
setInterval(() => {
|
|
182
|
-
|
|
188
|
+
runPush().catch(err => {
|
|
183
189
|
console.error(`${this.prefixLogs} Failed to push metrics:`, err);
|
|
184
190
|
});
|
|
185
191
|
}, interval * 1000);
|
|
186
192
|
}
|
|
187
|
-
|
|
193
|
+
|
|
194
|
+
// First push immediately so metrics appear without waiting for the first interval
|
|
195
|
+
runPush().catch(err => {
|
|
196
|
+
console.error(`${this.prefixLogs} Failed to push metrics (initial):`, err);
|
|
197
|
+
});
|
|
198
|
+
let pushOrigin = 'none';
|
|
199
|
+
try {
|
|
200
|
+
if (this.pushgatewayUrl && this.pushgatewayUrl.trim()) {
|
|
201
|
+
pushOrigin = new URL(this.pushgatewayUrl.trim()).origin;
|
|
202
|
+
}
|
|
203
|
+
} catch {
|
|
204
|
+
pushOrigin = 'invalid URL';
|
|
205
|
+
}
|
|
206
|
+
console.warn(`${this.prefixLogs} Metrics collection started. (interval: ${this.intervalSec}s, push: ${pushOrigin})`);
|
|
188
207
|
};
|
|
189
208
|
pushMetrics = async () => {
|
|
190
209
|
return this._pushMetrics();
|
|
@@ -227,104 +246,80 @@ class BaseMetricsClient {
|
|
|
227
246
|
*/
|
|
228
247
|
_clearOldWorkers = async removeOldMetrics => {
|
|
229
248
|
if (!removeOldMetrics) return;
|
|
230
|
-
|
|
231
|
-
const url = `${this.pushgatewayUrl}/metrics`;
|
|
232
|
-
const res = await fetch(url, {
|
|
233
|
-
headers: {
|
|
234
|
-
Authorization: `Basic ${this.authToken}`,
|
|
235
|
-
Accept: 'text/plain'
|
|
236
|
-
}
|
|
237
|
-
});
|
|
238
|
-
if (!res.ok) {
|
|
239
|
-
console.error(`${this.prefixLogs} Failed to fetch metrics: ${res.status}`);
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
const text = await res.text();
|
|
243
|
-
const metricRegex = /([a-zA-Z_:][a-zA-Z0-9_:]*)\{([^}]*)\}/gm;
|
|
244
|
-
const labelRegex = /(\w+)="([^"]*)"/g;
|
|
245
|
-
const uniqueLabelSets = new Set();
|
|
246
|
-
let match;
|
|
247
|
-
// eslint-disable-next-line no-cond-assign
|
|
248
|
-
while ((match = metricRegex.exec(text)) !== null) {
|
|
249
|
-
const rawLabels = match[2];
|
|
250
|
-
let lr;
|
|
251
|
-
const labels = {};
|
|
252
|
-
|
|
253
|
-
// eslint-disable-next-line no-cond-assign
|
|
254
|
-
while ((lr = labelRegex.exec(rawLabels)) !== null) {
|
|
255
|
-
// eslint-disable-next-line prefer-destructuring
|
|
256
|
-
labels[lr[1]] = lr[2];
|
|
257
|
-
}
|
|
258
|
-
if (labels.job === this.appName && labels.process_type === this.processType) {
|
|
259
|
-
uniqueLabelSets.add(JSON.stringify(labels));
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
if (uniqueLabelSets.size === 0) {
|
|
263
|
-
console.log(`${this.prefixLogs} No metrics found for job ${this.appName}`);
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
const oldLabelSets = [...uniqueLabelSets].map(s => JSON.parse(s)).filter(labels => labels.instance && labels.instance !== this.dynoId && labels.process_type === this.processType);
|
|
267
|
-
if (oldLabelSets.length === 0) {
|
|
268
|
-
console.log(`${this.prefixLogs} No old dynos to delete.`);
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
for (const labels of oldLabelSets) {
|
|
272
|
-
try {
|
|
273
|
-
await this.gatewayDelete({
|
|
274
|
-
jobName: this.appName,
|
|
275
|
-
groupings: labels
|
|
276
|
-
});
|
|
277
|
-
console.log(`${this.prefixLogs} Deleted metrics for dyno: ${labels.instance}, labels: ${Object.keys(labels)} `);
|
|
278
|
-
} catch (err) {
|
|
279
|
-
console.error(`${this.prefixLogs} Failed to delete metrics for ${labels.instance}:`, err);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
console.log(`${this.prefixLogs} Cleared all old instances for job ${this.appName}`);
|
|
283
|
-
} catch (err) {
|
|
284
|
-
console.error(`${this.prefixLogs} Error deleting old metrics:`, err);
|
|
285
|
-
}
|
|
249
|
+
// No Pushgateway; VM-agent does not support per-instance delete. Skip.
|
|
286
250
|
};
|
|
287
251
|
|
|
288
252
|
/**
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
* @param {Object} [params]
|
|
292
|
-
* @param {string} [params.jobName] Job name (defaults to appName)
|
|
293
|
-
* @param {Object} [params.groupings] Grouping labels
|
|
294
|
-
* @param {string} [params.groupings.process_type] Process type label
|
|
295
|
-
* @param {string} [params.groupings.instance] Instance/dyno ID
|
|
253
|
+
* No-op (no Pushgateway). Kept for API compatibility.
|
|
296
254
|
* @returns {Promise<void>}
|
|
297
255
|
*/
|
|
298
|
-
gatewayDelete = async (
|
|
299
|
-
return
|
|
300
|
-
jobName: params.jobName || this.appName,
|
|
301
|
-
groupings: params.groupings || {
|
|
302
|
-
process_type: this.processType,
|
|
303
|
-
instance: this.dynoId
|
|
304
|
-
}
|
|
305
|
-
});
|
|
256
|
+
gatewayDelete = async () => {
|
|
257
|
+
return Promise.resolve();
|
|
306
258
|
};
|
|
307
259
|
|
|
308
260
|
/**
|
|
309
|
-
* Push
|
|
261
|
+
* Push registry to configured URL (VM-agent). POST Prometheus text format + Basic auth.
|
|
310
262
|
*
|
|
311
|
-
* @param {object} [params]
|
|
312
|
-
* @param {string} [params.jobName]
|
|
313
|
-
* @param {object} [params.groupings]
|
|
263
|
+
* @param {object} [params] Unused; kept for API compatibility.
|
|
314
264
|
* @returns {Promise<void>}
|
|
315
265
|
*/
|
|
316
266
|
gatewayPush = async (params = {}) => {
|
|
317
267
|
if (this.disablePushgateway) {
|
|
268
|
+
console.warn(`${this.prefixLogs} Metrics push skipped: METRICS_DISABLE_PUSHGATEWAY is set`);
|
|
318
269
|
return Promise.resolve();
|
|
319
270
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
271
|
+
if (!this.pushgatewayUrl || !this.pushgatewayUrl.trim()) {
|
|
272
|
+
console.warn(`${this.prefixLogs} Metrics push skipped: METRICS_PUSHGATEWAY_URL is not set`);
|
|
273
|
+
return Promise.resolve();
|
|
274
|
+
}
|
|
275
|
+
return this._pushToVMAgent();
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* POST registry (Prometheus text format) to VM-agent. VM-agent accepts push at /api/v1/import/prometheus; /metrics is GET (scrape) only.
|
|
280
|
+
* @private
|
|
281
|
+
*/
|
|
282
|
+
_pushToVMAgent = () => {
|
|
283
|
+
let pushUrl = (this.pushgatewayUrl || '').trim();
|
|
284
|
+
try {
|
|
285
|
+
const u = new URL(pushUrl);
|
|
286
|
+
if (!u.pathname || u.pathname === '/' || u.pathname === '/metrics') {
|
|
287
|
+
pushUrl = `${u.origin}/api/v1/import/prometheus${u.search}`;
|
|
288
|
+
}
|
|
289
|
+
} catch {
|
|
290
|
+
// leave pushUrl as-is
|
|
291
|
+
}
|
|
292
|
+
return new Promise((resolve, reject) => {
|
|
293
|
+
const u = new URL(pushUrl);
|
|
294
|
+
const req = (u.protocol === 'https:' ? https : http).request({
|
|
295
|
+
hostname: u.hostname,
|
|
296
|
+
port: u.port || (u.protocol === 'https:' ? 443 : 80),
|
|
297
|
+
path: u.pathname + u.search,
|
|
298
|
+
method: 'POST',
|
|
299
|
+
headers: {
|
|
300
|
+
'Content-Type': client.register.contentType,
|
|
301
|
+
Authorization: this.authToken ? `Basic ${this.authToken}` : undefined
|
|
302
|
+
},
|
|
303
|
+
agent: u.protocol === 'https:' ? new https.Agent({
|
|
304
|
+
keepAlive: true
|
|
305
|
+
}) : undefined
|
|
306
|
+
}, res => {
|
|
307
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
308
|
+
console.log(`${this.prefixLogs} Metrics pushed to ${u.origin} OK (${res.statusCode})`);
|
|
309
|
+
resolve();
|
|
310
|
+
} else {
|
|
311
|
+
let data = '';
|
|
312
|
+
res.on('data', chunk => {
|
|
313
|
+
data += chunk;
|
|
314
|
+
});
|
|
315
|
+
res.on('end', () => reject(new Error(`Push failed: ${res.statusCode} ${data}`)));
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
req.on('error', reject);
|
|
319
|
+
this._registry.metrics().then(metrics => {
|
|
320
|
+
req.setHeader('Content-Length', Buffer.byteLength(metrics, 'utf8'));
|
|
321
|
+
req.end(metrics, 'utf8');
|
|
322
|
+
}).catch(reject);
|
|
328
323
|
});
|
|
329
324
|
};
|
|
330
325
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"baseMetricsClient.js","names":["client","require","https","BaseMetricsClient","constructor","config","appName","process","env","BUILD_APP_NAME","dynoId","HOSTNAME","processType","BUILD_DYNO_PROCESS_TYPE","enabled","METRICS_ENABLED","logValues","METRICS_LOG_VALUES","pushgatewayUrl","METRICS_PUSHGATEWAY_URL","authToken","pushgatewaySecret","METRICS_PUSHGATEWAY_SECRET","intervalSec","parseInt","METRICS_INTERVAL_SEC","startupValidation","disablePushgateway","METRICS_DISABLE_PUSHGATEWAY","prefixLogs","_registry","Registry","collectDefaultMetrics","register","defaultLabels","app","dyno_id","process_type","gateway","Pushgateway","headers","Authorization","agent","Agent","keepAlive","gauges","counters","countersFunctions","gaugeUpdaters","_clearOldWorkers","removeOldMetrics","_setCleanupHandlers","createGauge","name","help","updateFn","labelNames","Object","keys","g","Gauge","registers","createCounter","c","Counter","data","value","inc","clearAllCounters","metricsLogValues","console","log","values","forEach","counter","reset","_pushMetrics","entries","result","val","Promise","undefined","set","err","error","gatewayPush","metrics","getMetricsAsJSON","JSON","stringify","_startPush","interval","customPushMetics","warn","setInterval","pushMetrics","catch","startPush","cleanup","gatewayDelete","exit","url","res","fetch","Accept","ok","status","text","metricRegex","labelRegex","uniqueLabelSets","Set","match","exec","rawLabels","lr","labels","job","add","size","oldLabelSets","map","s","parse","filter","instance","length","jobName","groupings","params","delete","resolve","push","withDefaultLabels","getDefaultLabels","on","metricsEnabled","registry","getMetricsAsString","metricsMiddleware","req","contentType","end","module","exports"],"sources":["../../src/metrics/baseMetricsClient.js"],"sourcesContent":["const client = require('prom-client')\nconst https = require('https')\n\n/**\n * BaseMetricsClient provides common functionality for all metrics clients.\n * Handles registry setup, pushgateway, default labels, and common operations.\n */\nclass BaseMetricsClient {\n /**\n * @param {Object} config\n * @param {string} [config.appName] Name of the application\n * @param {string} [config.dynoId] Dyno/instance ID\n * @param {string} [config.processType] Process type (web, worker, etc.)\n * @param {boolean} [config.enabled] Enable metrics collection\n * @param {boolean} [config.logValues] Log metrics values to console\n * @param {string} [config.pushgatewayUrl] PushGateway URL\n * @param {string} [config.pushgatewaySecret] PushGateway secret token\n * @param {number} [config.intervalSec] Interval in seconds for pushing metrics\n * @param {boolean} [config.removeOldMetrics] Enable to clear metrics by service name\n * @param {function} [config.startupValidation] Add to validate on start push.\n * @param {boolean} [config.disablePushgateway] Disable pushing to Pushgateway (use HTTP scraping instead)\n */\n constructor(config = {}) {\n this.appName = config.appName || process.env.BUILD_APP_NAME || 'unknown-app'\n this.dynoId = config.dynoId || process.env.HOSTNAME || 'unknown-dyno'\n this.processType =\n config.processType ||\n process.env.BUILD_DYNO_PROCESS_TYPE ||\n 'undefined_build_dyno_type'\n this.enabled = config.enabled ?? process.env.METRICS_ENABLED === 'true'\n this.logValues =\n config.logValues ?? process.env.METRICS_LOG_VALUES === 'true'\n this.pushgatewayUrl =\n config.pushgatewayUrl || process.env.METRICS_PUSHGATEWAY_URL || ''\n this.authToken =\n config.pushgatewaySecret || process.env.METRICS_PUSHGATEWAY_SECRET || ''\n this.intervalSec =\n config.intervalSec ||\n parseInt(process.env.METRICS_INTERVAL_SEC || '', 10) ||\n 15\n this.startupValidation = config.startupValidation\n this.disablePushgateway =\n config.disablePushgateway ??\n process.env.METRICS_DISABLE_PUSHGATEWAY === 'true'\n\n this.prefixLogs = `[${this.processType}] [${this.appName}] [${this.dynoId}] [Monitoring]`\n\n this._registry = new client.Registry()\n client.collectDefaultMetrics({ register: this._registry })\n\n this.defaultLabels = {\n app: this.appName,\n dyno_id: this.dynoId,\n process_type: this.processType,\n }\n\n this.gateway = new client.Pushgateway(\n this.pushgatewayUrl,\n {\n headers: { Authorization: `Basic ${this.authToken}` },\n agent: new https.Agent({ keepAlive: true }),\n },\n this._registry\n )\n this.gauges = {}\n this.counters = {}\n this.countersFunctions = {}\n\n /** @type {Object<string, function(): number | Promise<number>>} */\n this.gaugeUpdaters = {}\n\n this._clearOldWorkers(config.removeOldMetrics)\n this._setCleanupHandlers()\n }\n\n /**\n * Create a gauge metric.\n * @param {Object} options - Gauge configuration\n * @param {string} options.name - Name of the gauge\n * @param {string} options.help - Help text describing the gauge\n * @param {function(): number|Promise<number>} [options.updateFn] - Optional function returning the gauge value\n * @param {string[]} [options.labelNames] - Optional custom label names\n * @returns {import('prom-client').Gauge} The created Prometheus gauge\n */\n createGauge = ({\n name,\n help,\n updateFn,\n labelNames = Object.keys(this.defaultLabels),\n }) => {\n if (this.gauges[name]) return this.gauges[name]\n\n const g = new client.Gauge({\n name,\n help,\n labelNames,\n registers: [this._registry],\n })\n this.gauges[name] = g\n\n if (updateFn && typeof updateFn === 'function') {\n this.gaugeUpdaters[name] = updateFn\n }\n\n return g\n }\n\n /**\n * Create a Prometheus Counter metric.\n *\n * @param {Object} params - Counter configuration\n * @param {string} params.name - Metric name\n * @param {string} params.help - Metric description\n * @param {string[]} [params.labelNames] - Optional list of label names. Defaults to this.defaultLabels keys.\n *\n * @returns {(labels?: Object, incrementValue?: number) => void}\n * A function to increment the counter.\n * Usage: (labels?, incrementValue?)\n */\n createCounter({ name, help, labelNames = Object.keys(this.defaultLabels) }) {\n if (this.counters[name]) return this.countersFunctions[name]\n\n const c = new client.Counter({\n name,\n help,\n labelNames,\n registers: [this._registry],\n })\n this.counters[name] = c\n\n this.countersFunctions = {\n ...this.countersFunctions,\n [name]: (data = {}, value = 1) => {\n c.inc({ ...this.defaultLabels, ...data }, value)\n },\n }\n\n return this.countersFunctions[name]\n }\n\n /**\n * Clear all collected counters\n */\n clearAllCounters = () => {\n if (this.metricsLogValues) {\n console.log('Counters to clear: ', Object.keys(this.counters))\n }\n Object.values(this.counters).forEach(counter => counter.reset())\n }\n\n /**\n * Push all gauges and counters to PushGateway and optionally log.\n */\n _pushMetrics = async () => {\n try {\n for (const [name, updateFn] of Object.entries(this.gaugeUpdaters)) {\n try {\n if (!updateFn) {\n continue\n }\n const result = updateFn()\n const val = result instanceof Promise ? await result : result\n if (val !== undefined) this.gauges[name].set(this.defaultLabels, val)\n } catch (err) {\n console.error(\n `${this.prefixLogs} Failed to update gauge ${name}:`,\n err\n )\n }\n }\n\n if (!this.disablePushgateway) {\n await this.gatewayPush()\n }\n // this.clearAllCounters() //TODO: or uncommit or delete (based on grafana expectation)\n\n if (this.logValues) {\n const metrics = await this._registry.getMetricsAsJSON()\n console.log(\n `${this.prefixLogs} Metrics:\\n`,\n JSON.stringify(metrics, null, 2)\n )\n }\n } catch (err) {\n console.error(`${this.prefixLogs} Failed to push metrics:`, err)\n }\n }\n\n _startPush = (interval = this.intervalSec, customPushMetics = undefined) => {\n if (!this.enabled) {\n console.warn(`${this.prefixLogs} Metrics disabled`)\n return\n }\n\n if (this.startupValidation && !this.startupValidation()) {\n return\n }\n\n if (customPushMetics && typeof customPushMetics === 'function') {\n setInterval(() => customPushMetics(), interval * 1000)\n } else {\n setInterval(() => {\n this.pushMetrics().catch(err => {\n console.error(`${this.prefixLogs} Failed to push metrics:`, err)\n })\n }, interval * 1000)\n }\n\n console.warn(\n `${this.prefixLogs} Metrics collection started. (interval: ${this.intervalSec}s)`\n )\n }\n\n pushMetrics = async () => {\n return this._pushMetrics()\n }\n\n /**\n * Start periodic metrics collection and push.\n *\n * This method wraps the internal `_startPush` method.\n * If a `customPushMetrics` function is provided, it will be executed\n * at the given interval instead of the default `pushMetrics` behavior.\n *\n * @param {number} [interval=this.intervalSec] - Interval in seconds between pushes.\n * @param {() => void | Promise<void>} [customPushMetrics] - Optional custom push function. If provided, Prometheus push is skipped.\n */\n startPush = (interval, customPushMetics = undefined) => {\n this._startPush(interval, customPushMetics)\n }\n\n /**\n * Cleanup metrics and exit process.\n * @returns {Promise<void>}\n */\n cleanup = async () => {\n if (this.enabled) {\n await this.gatewayDelete()\n }\n process.exit(0)\n }\n\n /**\n * Remove old/stale dyno/instance metrics from PushGateway.\n *\n * Compares existing PushGateway metrics for this job and deletes any instances\n * that do not match the current dynoId.\n *\n * @param {boolean} removeOldMetrics If true, performs cleanup; otherwise does nothing\n * @returns {Promise<void>}\n * @private\n */\n _clearOldWorkers = async removeOldMetrics => {\n if (!removeOldMetrics) return\n\n try {\n const url = `${this.pushgatewayUrl}/metrics`\n const res = await fetch(url, {\n headers: {\n Authorization: `Basic ${this.authToken}`,\n Accept: 'text/plain',\n },\n })\n\n if (!res.ok) {\n console.error(\n `${this.prefixLogs} Failed to fetch metrics: ${res.status}`\n )\n return\n }\n\n const text = await res.text()\n\n const metricRegex = /([a-zA-Z_:][a-zA-Z0-9_:]*)\\{([^}]*)\\}/gm\n const labelRegex = /(\\w+)=\"([^\"]*)\"/g\n\n const uniqueLabelSets = new Set()\n\n let match\n // eslint-disable-next-line no-cond-assign\n while ((match = metricRegex.exec(text)) !== null) {\n const rawLabels = match[2]\n let lr\n const labels = {}\n\n // eslint-disable-next-line no-cond-assign\n while ((lr = labelRegex.exec(rawLabels)) !== null) {\n // eslint-disable-next-line prefer-destructuring\n labels[lr[1]] = lr[2]\n }\n\n if (\n labels.job === this.appName &&\n labels.process_type === this.processType\n ) {\n uniqueLabelSets.add(JSON.stringify(labels))\n }\n }\n\n if (uniqueLabelSets.size === 0) {\n console.log(\n `${this.prefixLogs} No metrics found for job ${this.appName}`\n )\n return\n }\n\n const oldLabelSets = [...uniqueLabelSets]\n .map(s => JSON.parse(s))\n .filter(\n labels =>\n labels.instance &&\n labels.instance !== this.dynoId &&\n labels.process_type === this.processType\n )\n\n if (oldLabelSets.length === 0) {\n console.log(`${this.prefixLogs} No old dynos to delete.`)\n return\n }\n\n for (const labels of oldLabelSets) {\n try {\n await this.gatewayDelete({ jobName: this.appName, groupings: labels })\n console.log(\n `${this.prefixLogs} Deleted metrics for dyno: ${\n labels.instance\n }, labels: ${Object.keys(labels)} `\n )\n } catch (err) {\n console.error(\n `${this.prefixLogs} Failed to delete metrics for ${labels.instance}:`,\n err\n )\n }\n }\n\n console.log(\n `${this.prefixLogs} Cleared all old instances for job ${this.appName}`\n )\n } catch (err) {\n console.error(`${this.prefixLogs} Error deleting old metrics:`, err)\n }\n }\n\n /**\n * Delete metrics for this job/instance from PushGateway.\n *\n * @param {Object} [params]\n * @param {string} [params.jobName] Job name (defaults to appName)\n * @param {Object} [params.groupings] Grouping labels\n * @param {string} [params.groupings.process_type] Process type label\n * @param {string} [params.groupings.instance] Instance/dyno ID\n * @returns {Promise<void>}\n */\n gatewayDelete = async (params = {}) => {\n return this.gateway.delete({\n jobName: params.jobName || this.appName,\n groupings: params.groupings || {\n process_type: this.processType,\n instance: this.dynoId,\n },\n })\n }\n\n /**\n * Push metrics to PushGateway.\n *\n * @param {object} [params]\n * @param {string} [params.jobName]\n * @param {object} [params.groupings]\n * @returns {Promise<void>}\n */\n gatewayPush = async (params = {}) => {\n if (this.disablePushgateway) {\n return Promise.resolve()\n }\n const groupings = {\n process_type: this.processType,\n instance: this.dynoId,\n ...(params.groupings || {}),\n }\n return this.gateway.push({\n jobName: params.jobName || this.appName,\n groupings,\n })\n }\n\n /**\n * Merge the default metric labels (`app`, `dyno_id`, `process_type`)\n * with custom label names.\n *\n * @param {string[]} labels Additional label names\n * @returns {string[]} Combined label names\n */\n withDefaultLabels = (labels = []) => {\n return [...Object.keys(this.defaultLabels), ...labels]\n }\n\n getDefaultLabels = (labels = []) => {\n return this.defaultLabels\n }\n\n _setCleanupHandlers = () => {\n process.on('SIGINT', this.cleanup)\n process.on('SIGTERM', this.cleanup)\n }\n\n // GETTERS\n\n get metricsEnabled() {\n return this.enabled\n }\n\n get metricsLogValues() {\n return this.logValues\n }\n\n get registry() {\n return this._registry\n }\n\n async getMetricsAsString() {\n return this._registry.metrics()\n }\n\n metricsMiddleware() {\n return async (req, res) => {\n try {\n const metrics = await this.getMetricsAsString()\n res.set('Content-Type', client.register.contentType)\n res.end(metrics)\n } catch (err) {\n console.error(`${this.prefixLogs} Failed to get metrics:`, err)\n res.status(500).end('Failed to collect metrics')\n }\n }\n }\n}\n\nmodule.exports = { BaseMetricsClient }\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"baseMetricsClient.js","names":["client","require","https","http","URL","BaseMetricsClient","constructor","config","appName","process","env","BUILD_APP_NAME","dynoId","HOSTNAME","processType","BUILD_DYNO_PROCESS_TYPE","enabled","METRICS_ENABLED","logValues","METRICS_LOG_VALUES","pushgatewayUrl","METRICS_PUSHGATEWAY_URL","authToken","pushgatewaySecret","METRICS_PUSHGATEWAY_SECRET","intervalSec","parseInt","METRICS_INTERVAL_SEC","startupValidation","disablePushgateway","METRICS_DISABLE_PUSHGATEWAY","prefixLogs","_registry","Registry","collectDefaultMetrics","register","defaultLabels","app","dyno_id","process_type","gateway","gauges","counters","countersFunctions","gaugeUpdaters","_clearOldWorkers","removeOldMetrics","_setCleanupHandlers","createGauge","name","help","updateFn","labelNames","Object","keys","g","Gauge","registers","createCounter","c","Counter","data","value","inc","clearAllCounters","metricsLogValues","console","log","values","forEach","counter","reset","_pushMetrics","entries","result","val","Promise","undefined","set","err","error","gatewayPush","metrics","getMetricsAsJSON","JSON","stringify","_startPush","interval","customPushMetics","warn","runPush","resolve","pushMetrics","setInterval","catch","pushOrigin","trim","origin","startPush","cleanup","gatewayDelete","exit","params","_pushToVMAgent","pushUrl","u","pathname","search","reject","req","protocol","request","hostname","port","path","method","headers","contentType","Authorization","agent","Agent","keepAlive","res","statusCode","on","chunk","Error","then","setHeader","Buffer","byteLength","end","withDefaultLabels","labels","getDefaultLabels","metricsEnabled","registry","getMetricsAsString","metricsMiddleware","status","module","exports"],"sources":["../../src/metrics/baseMetricsClient.js"],"sourcesContent":["const client = require('prom-client')\nconst https = require('https')\nconst http = require('http')\nconst { URL } = require('url')\n\n/**\n * BaseMetricsClient provides common functionality for all metrics clients.\n * Handles registry setup, push to remote (VM-agent), default labels, and common operations.\n * Always pushes registry to the configured URL (POST Prometheus text format + Basic auth). No Pushgateway.\n */\nclass BaseMetricsClient {\n /**\n * @param {Object} config\n * @param {string} [config.appName] Name of the application\n * @param {string} [config.dynoId] Dyno/instance ID\n * @param {string} [config.processType] Process type (web, worker, etc.)\n * @param {boolean} [config.enabled] Enable metrics collection\n * @param {boolean} [config.logValues] Log metrics values to console\n * @param {string} [config.pushgatewayUrl] Push URL (VM-agent import endpoint, e.g. .../api/v1/import/prometheus). /metrics is for GET (scrape), not POST (push).\n * @param {string} [config.pushgatewaySecret] Basic auth secret (Base64 of user:password)\n * @param {number} [config.intervalSec] Interval in seconds for pushing metrics\n * @param {boolean} [config.removeOldMetrics] Enable to clear metrics by service name\n * @param {function} [config.startupValidation] Add to validate on start push.\n * @param {boolean} [config.disablePushgateway] Disable pushing to Pushgateway (use HTTP scraping instead)\n */\n constructor(config = {}) {\n this.appName = config.appName || process.env.BUILD_APP_NAME || 'unknown-app'\n this.dynoId = config.dynoId || process.env.HOSTNAME || 'unknown-dyno'\n this.processType =\n config.processType ||\n process.env.BUILD_DYNO_PROCESS_TYPE ||\n 'undefined_build_dyno_type'\n this.enabled = config.enabled ?? process.env.METRICS_ENABLED === 'true'\n this.logValues =\n config.logValues ?? process.env.METRICS_LOG_VALUES === 'true'\n this.pushgatewayUrl =\n config.pushgatewayUrl || process.env.METRICS_PUSHGATEWAY_URL || ''\n this.authToken =\n config.pushgatewaySecret || process.env.METRICS_PUSHGATEWAY_SECRET || ''\n this.intervalSec =\n config.intervalSec ||\n parseInt(process.env.METRICS_INTERVAL_SEC || '', 10) ||\n 15\n this.startupValidation = config.startupValidation\n this.disablePushgateway =\n config.disablePushgateway ??\n process.env.METRICS_DISABLE_PUSHGATEWAY === 'true'\n\n this.prefixLogs = `[${this.processType}] [${this.appName}] [${this.dynoId}] [Monitoring]`\n\n this._registry = new client.Registry()\n client.collectDefaultMetrics({ register: this._registry })\n\n this.defaultLabels = {\n app: this.appName,\n dyno_id: this.dynoId,\n process_type: this.processType,\n }\n\n // Always push to configured URL (VM-agent). No Pushgateway.\n this.gateway = null\n this.gauges = {}\n this.counters = {}\n this.countersFunctions = {}\n\n /** @type {Object<string, function(): number | Promise<number>>} */\n this.gaugeUpdaters = {}\n\n this._clearOldWorkers(config.removeOldMetrics)\n this._setCleanupHandlers()\n }\n\n /**\n * Create a gauge metric.\n * @param {Object} options - Gauge configuration\n * @param {string} options.name - Name of the gauge\n * @param {string} options.help - Help text describing the gauge\n * @param {function(): number|Promise<number>} [options.updateFn] - Optional function returning the gauge value\n * @param {string[]} [options.labelNames] - Optional custom label names\n * @returns {import('prom-client').Gauge} The created Prometheus gauge\n */\n createGauge = ({\n name,\n help,\n updateFn,\n labelNames = Object.keys(this.defaultLabels),\n }) => {\n if (this.gauges[name]) return this.gauges[name]\n\n const g = new client.Gauge({\n name,\n help,\n labelNames,\n registers: [this._registry],\n })\n this.gauges[name] = g\n\n if (updateFn && typeof updateFn === 'function') {\n this.gaugeUpdaters[name] = updateFn\n }\n\n return g\n }\n\n /**\n * Create a Prometheus Counter metric.\n *\n * @param {Object} params - Counter configuration\n * @param {string} params.name - Metric name\n * @param {string} params.help - Metric description\n * @param {string[]} [params.labelNames] - Optional list of label names. Defaults to this.defaultLabels keys.\n *\n * @returns {(labels?: Object, incrementValue?: number) => void}\n * A function to increment the counter.\n * Usage: (labels?, incrementValue?)\n */\n createCounter({ name, help, labelNames = Object.keys(this.defaultLabels) }) {\n if (this.counters[name]) return this.countersFunctions[name]\n\n const c = new client.Counter({\n name,\n help,\n labelNames,\n registers: [this._registry],\n })\n this.counters[name] = c\n\n this.countersFunctions = {\n ...this.countersFunctions,\n [name]: (data = {}, value = 1) => {\n c.inc({ ...this.defaultLabels, ...data }, value)\n },\n }\n\n return this.countersFunctions[name]\n }\n\n /**\n * Clear all collected counters\n */\n clearAllCounters = () => {\n if (this.metricsLogValues) {\n console.log('Counters to clear: ', Object.keys(this.counters))\n }\n Object.values(this.counters).forEach(counter => counter.reset())\n }\n\n /**\n * Push all gauges and counters to PushGateway and optionally log.\n */\n _pushMetrics = async () => {\n try {\n for (const [name, updateFn] of Object.entries(this.gaugeUpdaters)) {\n try {\n if (!updateFn) {\n continue\n }\n const result = updateFn()\n const val = result instanceof Promise ? await result : result\n if (val !== undefined) this.gauges[name].set(this.defaultLabels, val)\n } catch (err) {\n console.error(\n `${this.prefixLogs} Failed to update gauge ${name}:`,\n err\n )\n }\n }\n\n if (!this.disablePushgateway) {\n await this.gatewayPush()\n }\n // this.clearAllCounters() //TODO: or uncommit or delete (based on grafana expectation)\n\n if (this.logValues) {\n const metrics = await this._registry.getMetricsAsJSON()\n console.log(\n `${this.prefixLogs} Metrics:\\n`,\n JSON.stringify(metrics, null, 2)\n )\n }\n } catch (err) {\n console.error(`${this.prefixLogs} Failed to push metrics:`, err)\n }\n }\n\n _startPush = (interval = this.intervalSec, customPushMetics = undefined) => {\n if (!this.enabled) {\n console.warn(`${this.prefixLogs} Metrics disabled`)\n return\n }\n\n if (this.startupValidation && !this.startupValidation()) {\n return\n }\n\n const runPush = () => {\n if (customPushMetics && typeof customPushMetics === 'function') {\n return Promise.resolve(customPushMetics())\n }\n return this.pushMetrics()\n }\n\n if (customPushMetics && typeof customPushMetics === 'function') {\n setInterval(() => customPushMetics(), interval * 1000)\n } else {\n setInterval(() => {\n runPush().catch(err => {\n console.error(`${this.prefixLogs} Failed to push metrics:`, err)\n })\n }, interval * 1000)\n }\n\n // First push immediately so metrics appear without waiting for the first interval\n runPush().catch(err => {\n console.error(`${this.prefixLogs} Failed to push metrics (initial):`, err)\n })\n\n let pushOrigin = 'none'\n try {\n if (this.pushgatewayUrl && this.pushgatewayUrl.trim()) {\n pushOrigin = new URL(this.pushgatewayUrl.trim()).origin\n }\n } catch {\n pushOrigin = 'invalid URL'\n }\n console.warn(\n `${this.prefixLogs} Metrics collection started. (interval: ${this.intervalSec}s, push: ${pushOrigin})`\n )\n }\n\n pushMetrics = async () => {\n return this._pushMetrics()\n }\n\n /**\n * Start periodic metrics collection and push.\n *\n * This method wraps the internal `_startPush` method.\n * If a `customPushMetrics` function is provided, it will be executed\n * at the given interval instead of the default `pushMetrics` behavior.\n *\n * @param {number} [interval=this.intervalSec] - Interval in seconds between pushes.\n * @param {() => void | Promise<void>} [customPushMetrics] - Optional custom push function. If provided, Prometheus push is skipped.\n */\n startPush = (interval, customPushMetics = undefined) => {\n this._startPush(interval, customPushMetics)\n }\n\n /**\n * Cleanup metrics and exit process.\n * @returns {Promise<void>}\n */\n cleanup = async () => {\n if (this.enabled) {\n await this.gatewayDelete()\n }\n process.exit(0)\n }\n\n /**\n * Remove old/stale dyno/instance metrics from PushGateway.\n *\n * Compares existing PushGateway metrics for this job and deletes any instances\n * that do not match the current dynoId.\n *\n * @param {boolean} removeOldMetrics If true, performs cleanup; otherwise does nothing\n * @returns {Promise<void>}\n * @private\n */\n _clearOldWorkers = async removeOldMetrics => {\n if (!removeOldMetrics) return\n // No Pushgateway; VM-agent does not support per-instance delete. Skip.\n }\n\n /**\n * No-op (no Pushgateway). Kept for API compatibility.\n * @returns {Promise<void>}\n */\n gatewayDelete = async () => {\n return Promise.resolve()\n }\n\n /**\n * Push registry to configured URL (VM-agent). POST Prometheus text format + Basic auth.\n *\n * @param {object} [params] Unused; kept for API compatibility.\n * @returns {Promise<void>}\n */\n gatewayPush = async (params = {}) => {\n if (this.disablePushgateway) {\n console.warn(`${this.prefixLogs} Metrics push skipped: METRICS_DISABLE_PUSHGATEWAY is set`)\n return Promise.resolve()\n }\n if (!this.pushgatewayUrl || !this.pushgatewayUrl.trim()) {\n console.warn(`${this.prefixLogs} Metrics push skipped: METRICS_PUSHGATEWAY_URL is not set`)\n return Promise.resolve()\n }\n return this._pushToVMAgent()\n }\n\n /**\n * POST registry (Prometheus text format) to VM-agent. VM-agent accepts push at /api/v1/import/prometheus; /metrics is GET (scrape) only.\n * @private\n */\n _pushToVMAgent = () => {\n let pushUrl = (this.pushgatewayUrl || '').trim()\n try {\n const u = new URL(pushUrl)\n if (!u.pathname || u.pathname === '/' || u.pathname === '/metrics') {\n pushUrl = `${u.origin}/api/v1/import/prometheus${u.search}`\n }\n } catch {\n // leave pushUrl as-is\n }\n return new Promise((resolve, reject) => {\n const u = new URL(pushUrl)\n const req = (u.protocol === 'https:' ? https : http).request(\n {\n hostname: u.hostname,\n port: u.port || (u.protocol === 'https:' ? 443 : 80),\n path: u.pathname + u.search,\n method: 'POST',\n headers: {\n 'Content-Type': client.register.contentType,\n Authorization: this.authToken ? `Basic ${this.authToken}` : undefined,\n },\n agent: u.protocol === 'https:' ? new https.Agent({ keepAlive: true }) : undefined,\n },\n res => {\n if (res.statusCode >= 200 && res.statusCode < 300) {\n console.log(`${this.prefixLogs} Metrics pushed to ${u.origin} OK (${res.statusCode})`)\n resolve()\n } else {\n let data = ''\n res.on('data', chunk => { data += chunk })\n res.on('end', () => reject(new Error(`Push failed: ${res.statusCode} ${data}`)))\n }\n }\n )\n req.on('error', reject)\n this._registry.metrics().then(metrics => {\n req.setHeader('Content-Length', Buffer.byteLength(metrics, 'utf8'))\n req.end(metrics, 'utf8')\n }).catch(reject)\n })\n }\n\n /**\n * Merge the default metric labels (`app`, `dyno_id`, `process_type`)\n * with custom label names.\n *\n * @param {string[]} labels Additional label names\n * @returns {string[]} Combined label names\n */\n withDefaultLabels = (labels = []) => {\n return [...Object.keys(this.defaultLabels), ...labels]\n }\n\n getDefaultLabels = (labels = []) => {\n return this.defaultLabels\n }\n\n _setCleanupHandlers = () => {\n process.on('SIGINT', this.cleanup)\n process.on('SIGTERM', this.cleanup)\n }\n\n // GETTERS\n\n get metricsEnabled() {\n return this.enabled\n }\n\n get metricsLogValues() {\n return this.logValues\n }\n\n get registry() {\n return this._registry\n }\n\n async getMetricsAsString() {\n return this._registry.metrics()\n }\n\n metricsMiddleware() {\n return async (req, res) => {\n try {\n const metrics = await this.getMetricsAsString()\n res.set('Content-Type', client.register.contentType)\n res.end(metrics)\n } catch (err) {\n console.error(`${this.prefixLogs} Failed to get metrics:`, err)\n res.status(500).end('Failed to collect metrics')\n }\n }\n }\n}\n\nmodule.exports = { BaseMetricsClient }\n"],"mappings":"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","ignoreList":[]}
|
package/package.json
CHANGED
package/scripts/README.md
CHANGED
|
@@ -19,3 +19,25 @@ METRICS_INTERVAL_SEC=
|
|
|
19
19
|
```
|
|
20
20
|
node scripts/clearMetrics.js
|
|
21
21
|
```
|
|
22
|
+
|
|
23
|
+
## Publish metrics to VM-agent (example)
|
|
24
|
+
|
|
25
|
+
Publishes built-in gauges (CPU, memory, uptime, etc.) and an example custom gauge to VM-agent.
|
|
26
|
+
|
|
27
|
+
1. Set env (or `.env`):
|
|
28
|
+
- `METRICS_PUSHGATEWAY_URL` – VM-agent import URL, e.g. `https://vm-agent.staging.infradalogs.adalo.com/api/v1/import/prometheus`
|
|
29
|
+
- `METRICS_PUSHGATEWAY_SECRET` – Base64 of `user:password` (same as VM-agent BASIC_USER / BASIC_PASSWORD)
|
|
30
|
+
- `METRICS_ENABLED=true`
|
|
31
|
+
- `METRICS_INTERVAL_SEC=15` (optional, default 15)
|
|
32
|
+
|
|
33
|
+
2. Generate secret:
|
|
34
|
+
```bash
|
|
35
|
+
echo -n 'user:password' | base64
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
3. Run:
|
|
39
|
+
```bash
|
|
40
|
+
node scripts/publish-metrics-example.js
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Metrics appear in VM-agent (and VictoriaMetrics) under names like `app_process_cpu_usage_percent`, `app_uptime_seconds`, `app_example_custom_total`, etc.
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
const client = require('prom-client')
|
|
2
2
|
const https = require('https')
|
|
3
|
+
const http = require('http')
|
|
4
|
+
const { URL } = require('url')
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* BaseMetricsClient provides common functionality for all metrics clients.
|
|
6
|
-
* Handles registry setup,
|
|
8
|
+
* Handles registry setup, push to remote (VM-agent), default labels, and common operations.
|
|
9
|
+
* Always pushes registry to the configured URL (POST Prometheus text format + Basic auth). No Pushgateway.
|
|
7
10
|
*/
|
|
8
11
|
class BaseMetricsClient {
|
|
9
12
|
/**
|
|
@@ -13,8 +16,8 @@ class BaseMetricsClient {
|
|
|
13
16
|
* @param {string} [config.processType] Process type (web, worker, etc.)
|
|
14
17
|
* @param {boolean} [config.enabled] Enable metrics collection
|
|
15
18
|
* @param {boolean} [config.logValues] Log metrics values to console
|
|
16
|
-
* @param {string} [config.pushgatewayUrl]
|
|
17
|
-
* @param {string} [config.pushgatewaySecret]
|
|
19
|
+
* @param {string} [config.pushgatewayUrl] Push URL (VM-agent import endpoint, e.g. .../api/v1/import/prometheus). /metrics is for GET (scrape), not POST (push).
|
|
20
|
+
* @param {string} [config.pushgatewaySecret] Basic auth secret (Base64 of user:password)
|
|
18
21
|
* @param {number} [config.intervalSec] Interval in seconds for pushing metrics
|
|
19
22
|
* @param {boolean} [config.removeOldMetrics] Enable to clear metrics by service name
|
|
20
23
|
* @param {function} [config.startupValidation] Add to validate on start push.
|
|
@@ -54,14 +57,8 @@ class BaseMetricsClient {
|
|
|
54
57
|
process_type: this.processType,
|
|
55
58
|
}
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
{
|
|
60
|
-
headers: { Authorization: `Basic ${this.authToken}` },
|
|
61
|
-
agent: new https.Agent({ keepAlive: true }),
|
|
62
|
-
},
|
|
63
|
-
this._registry
|
|
64
|
-
)
|
|
60
|
+
// Always push to configured URL (VM-agent). No Pushgateway.
|
|
61
|
+
this.gateway = null
|
|
65
62
|
this.gauges = {}
|
|
66
63
|
this.counters = {}
|
|
67
64
|
this.countersFunctions = {}
|
|
@@ -196,18 +193,38 @@ class BaseMetricsClient {
|
|
|
196
193
|
return
|
|
197
194
|
}
|
|
198
195
|
|
|
196
|
+
const runPush = () => {
|
|
197
|
+
if (customPushMetics && typeof customPushMetics === 'function') {
|
|
198
|
+
return Promise.resolve(customPushMetics())
|
|
199
|
+
}
|
|
200
|
+
return this.pushMetrics()
|
|
201
|
+
}
|
|
202
|
+
|
|
199
203
|
if (customPushMetics && typeof customPushMetics === 'function') {
|
|
200
204
|
setInterval(() => customPushMetics(), interval * 1000)
|
|
201
205
|
} else {
|
|
202
206
|
setInterval(() => {
|
|
203
|
-
|
|
207
|
+
runPush().catch(err => {
|
|
204
208
|
console.error(`${this.prefixLogs} Failed to push metrics:`, err)
|
|
205
209
|
})
|
|
206
210
|
}, interval * 1000)
|
|
207
211
|
}
|
|
208
212
|
|
|
213
|
+
// First push immediately so metrics appear without waiting for the first interval
|
|
214
|
+
runPush().catch(err => {
|
|
215
|
+
console.error(`${this.prefixLogs} Failed to push metrics (initial):`, err)
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
let pushOrigin = 'none'
|
|
219
|
+
try {
|
|
220
|
+
if (this.pushgatewayUrl && this.pushgatewayUrl.trim()) {
|
|
221
|
+
pushOrigin = new URL(this.pushgatewayUrl.trim()).origin
|
|
222
|
+
}
|
|
223
|
+
} catch {
|
|
224
|
+
pushOrigin = 'invalid URL'
|
|
225
|
+
}
|
|
209
226
|
console.warn(
|
|
210
|
-
`${this.prefixLogs} Metrics collection started. (interval: ${this.intervalSec}s)`
|
|
227
|
+
`${this.prefixLogs} Metrics collection started. (interval: ${this.intervalSec}s, push: ${pushOrigin})`
|
|
211
228
|
)
|
|
212
229
|
}
|
|
213
230
|
|
|
@@ -252,136 +269,79 @@ class BaseMetricsClient {
|
|
|
252
269
|
*/
|
|
253
270
|
_clearOldWorkers = async removeOldMetrics => {
|
|
254
271
|
if (!removeOldMetrics) return
|
|
255
|
-
|
|
256
|
-
try {
|
|
257
|
-
const url = `${this.pushgatewayUrl}/metrics`
|
|
258
|
-
const res = await fetch(url, {
|
|
259
|
-
headers: {
|
|
260
|
-
Authorization: `Basic ${this.authToken}`,
|
|
261
|
-
Accept: 'text/plain',
|
|
262
|
-
},
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
if (!res.ok) {
|
|
266
|
-
console.error(
|
|
267
|
-
`${this.prefixLogs} Failed to fetch metrics: ${res.status}`
|
|
268
|
-
)
|
|
269
|
-
return
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const text = await res.text()
|
|
273
|
-
|
|
274
|
-
const metricRegex = /([a-zA-Z_:][a-zA-Z0-9_:]*)\{([^}]*)\}/gm
|
|
275
|
-
const labelRegex = /(\w+)="([^"]*)"/g
|
|
276
|
-
|
|
277
|
-
const uniqueLabelSets = new Set()
|
|
278
|
-
|
|
279
|
-
let match
|
|
280
|
-
// eslint-disable-next-line no-cond-assign
|
|
281
|
-
while ((match = metricRegex.exec(text)) !== null) {
|
|
282
|
-
const rawLabels = match[2]
|
|
283
|
-
let lr
|
|
284
|
-
const labels = {}
|
|
285
|
-
|
|
286
|
-
// eslint-disable-next-line no-cond-assign
|
|
287
|
-
while ((lr = labelRegex.exec(rawLabels)) !== null) {
|
|
288
|
-
// eslint-disable-next-line prefer-destructuring
|
|
289
|
-
labels[lr[1]] = lr[2]
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (
|
|
293
|
-
labels.job === this.appName &&
|
|
294
|
-
labels.process_type === this.processType
|
|
295
|
-
) {
|
|
296
|
-
uniqueLabelSets.add(JSON.stringify(labels))
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
if (uniqueLabelSets.size === 0) {
|
|
301
|
-
console.log(
|
|
302
|
-
`${this.prefixLogs} No metrics found for job ${this.appName}`
|
|
303
|
-
)
|
|
304
|
-
return
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
const oldLabelSets = [...uniqueLabelSets]
|
|
308
|
-
.map(s => JSON.parse(s))
|
|
309
|
-
.filter(
|
|
310
|
-
labels =>
|
|
311
|
-
labels.instance &&
|
|
312
|
-
labels.instance !== this.dynoId &&
|
|
313
|
-
labels.process_type === this.processType
|
|
314
|
-
)
|
|
315
|
-
|
|
316
|
-
if (oldLabelSets.length === 0) {
|
|
317
|
-
console.log(`${this.prefixLogs} No old dynos to delete.`)
|
|
318
|
-
return
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
for (const labels of oldLabelSets) {
|
|
322
|
-
try {
|
|
323
|
-
await this.gatewayDelete({ jobName: this.appName, groupings: labels })
|
|
324
|
-
console.log(
|
|
325
|
-
`${this.prefixLogs} Deleted metrics for dyno: ${
|
|
326
|
-
labels.instance
|
|
327
|
-
}, labels: ${Object.keys(labels)} `
|
|
328
|
-
)
|
|
329
|
-
} catch (err) {
|
|
330
|
-
console.error(
|
|
331
|
-
`${this.prefixLogs} Failed to delete metrics for ${labels.instance}:`,
|
|
332
|
-
err
|
|
333
|
-
)
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
console.log(
|
|
338
|
-
`${this.prefixLogs} Cleared all old instances for job ${this.appName}`
|
|
339
|
-
)
|
|
340
|
-
} catch (err) {
|
|
341
|
-
console.error(`${this.prefixLogs} Error deleting old metrics:`, err)
|
|
342
|
-
}
|
|
272
|
+
// No Pushgateway; VM-agent does not support per-instance delete. Skip.
|
|
343
273
|
}
|
|
344
274
|
|
|
345
275
|
/**
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
* @param {Object} [params]
|
|
349
|
-
* @param {string} [params.jobName] Job name (defaults to appName)
|
|
350
|
-
* @param {Object} [params.groupings] Grouping labels
|
|
351
|
-
* @param {string} [params.groupings.process_type] Process type label
|
|
352
|
-
* @param {string} [params.groupings.instance] Instance/dyno ID
|
|
276
|
+
* No-op (no Pushgateway). Kept for API compatibility.
|
|
353
277
|
* @returns {Promise<void>}
|
|
354
278
|
*/
|
|
355
|
-
gatewayDelete = async (
|
|
356
|
-
return
|
|
357
|
-
jobName: params.jobName || this.appName,
|
|
358
|
-
groupings: params.groupings || {
|
|
359
|
-
process_type: this.processType,
|
|
360
|
-
instance: this.dynoId,
|
|
361
|
-
},
|
|
362
|
-
})
|
|
279
|
+
gatewayDelete = async () => {
|
|
280
|
+
return Promise.resolve()
|
|
363
281
|
}
|
|
364
282
|
|
|
365
283
|
/**
|
|
366
|
-
* Push
|
|
284
|
+
* Push registry to configured URL (VM-agent). POST Prometheus text format + Basic auth.
|
|
367
285
|
*
|
|
368
|
-
* @param {object} [params]
|
|
369
|
-
* @param {string} [params.jobName]
|
|
370
|
-
* @param {object} [params.groupings]
|
|
286
|
+
* @param {object} [params] Unused; kept for API compatibility.
|
|
371
287
|
* @returns {Promise<void>}
|
|
372
288
|
*/
|
|
373
289
|
gatewayPush = async (params = {}) => {
|
|
374
290
|
if (this.disablePushgateway) {
|
|
291
|
+
console.warn(`${this.prefixLogs} Metrics push skipped: METRICS_DISABLE_PUSHGATEWAY is set`)
|
|
375
292
|
return Promise.resolve()
|
|
376
293
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
...(params.groupings || {}),
|
|
294
|
+
if (!this.pushgatewayUrl || !this.pushgatewayUrl.trim()) {
|
|
295
|
+
console.warn(`${this.prefixLogs} Metrics push skipped: METRICS_PUSHGATEWAY_URL is not set`)
|
|
296
|
+
return Promise.resolve()
|
|
381
297
|
}
|
|
382
|
-
return this.
|
|
383
|
-
|
|
384
|
-
|
|
298
|
+
return this._pushToVMAgent()
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* POST registry (Prometheus text format) to VM-agent. VM-agent accepts push at /api/v1/import/prometheus; /metrics is GET (scrape) only.
|
|
303
|
+
* @private
|
|
304
|
+
*/
|
|
305
|
+
_pushToVMAgent = () => {
|
|
306
|
+
let pushUrl = (this.pushgatewayUrl || '').trim()
|
|
307
|
+
try {
|
|
308
|
+
const u = new URL(pushUrl)
|
|
309
|
+
if (!u.pathname || u.pathname === '/' || u.pathname === '/metrics') {
|
|
310
|
+
pushUrl = `${u.origin}/api/v1/import/prometheus${u.search}`
|
|
311
|
+
}
|
|
312
|
+
} catch {
|
|
313
|
+
// leave pushUrl as-is
|
|
314
|
+
}
|
|
315
|
+
return new Promise((resolve, reject) => {
|
|
316
|
+
const u = new URL(pushUrl)
|
|
317
|
+
const req = (u.protocol === 'https:' ? https : http).request(
|
|
318
|
+
{
|
|
319
|
+
hostname: u.hostname,
|
|
320
|
+
port: u.port || (u.protocol === 'https:' ? 443 : 80),
|
|
321
|
+
path: u.pathname + u.search,
|
|
322
|
+
method: 'POST',
|
|
323
|
+
headers: {
|
|
324
|
+
'Content-Type': client.register.contentType,
|
|
325
|
+
Authorization: this.authToken ? `Basic ${this.authToken}` : undefined,
|
|
326
|
+
},
|
|
327
|
+
agent: u.protocol === 'https:' ? new https.Agent({ keepAlive: true }) : undefined,
|
|
328
|
+
},
|
|
329
|
+
res => {
|
|
330
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
331
|
+
console.log(`${this.prefixLogs} Metrics pushed to ${u.origin} OK (${res.statusCode})`)
|
|
332
|
+
resolve()
|
|
333
|
+
} else {
|
|
334
|
+
let data = ''
|
|
335
|
+
res.on('data', chunk => { data += chunk })
|
|
336
|
+
res.on('end', () => reject(new Error(`Push failed: ${res.statusCode} ${data}`)))
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
)
|
|
340
|
+
req.on('error', reject)
|
|
341
|
+
this._registry.metrics().then(metrics => {
|
|
342
|
+
req.setHeader('Content-Length', Buffer.byteLength(metrics, 'utf8'))
|
|
343
|
+
req.end(metrics, 'utf8')
|
|
344
|
+
}).catch(reject)
|
|
385
345
|
})
|
|
386
346
|
}
|
|
387
347
|
|