@adalo/metrics 0.1.143 → 0.1.144

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 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://vm-agent.infradalogs.adalo.com
8
+ METRICS_PUSHGATEWAY_URL=https://pushgateway.infradalogs.adalo.com
9
9
  METRICS_PUSHGATEWAY_SECRET="METRICS_PUSHGATEWAY_SECRET"
10
10
  METRICS_INTERVAL_SEC=60
11
11
 
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * BaseMetricsClient provides common functionality for all metrics clients.
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.
3
+ * Handles registry setup, pushgateway, default labels, and common operations.
5
4
  */
6
5
  export class BaseMetricsClient {
7
6
  /**
@@ -11,8 +10,8 @@ export class BaseMetricsClient {
11
10
  * @param {string} [config.processType] Process type (web, worker, etc.)
12
11
  * @param {boolean} [config.enabled] Enable metrics collection
13
12
  * @param {boolean} [config.logValues] Log metrics values to console
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)
13
+ * @param {string} [config.pushgatewayUrl] PushGateway URL
14
+ * @param {string} [config.pushgatewaySecret] PushGateway secret token
16
15
  * @param {number} [config.intervalSec] Interval in seconds for pushing metrics
17
16
  * @param {boolean} [config.removeOldMetrics] Enable to clear metrics by service name
18
17
  * @param {function} [config.startupValidation] Add to validate on start push.
@@ -48,7 +47,7 @@ export class BaseMetricsClient {
48
47
  dyno_id: string;
49
48
  process_type: string;
50
49
  };
51
- gateway: any;
50
+ gateway: client.Pushgateway<"text/plain; version=0.0.4; charset=utf-8">;
52
51
  gauges: {};
53
52
  counters: {};
54
53
  countersFunctions: {};
@@ -126,22 +125,34 @@ export class BaseMetricsClient {
126
125
  */
127
126
  private _clearOldWorkers;
128
127
  /**
129
- * No-op (no Pushgateway). Kept for API compatibility.
128
+ * Delete metrics for this job/instance from PushGateway.
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
130
135
  * @returns {Promise<void>}
131
136
  */
132
- gatewayDelete: () => Promise<void>;
137
+ gatewayDelete: (params?: {
138
+ jobName?: string | undefined;
139
+ groupings?: {
140
+ process_type?: string | undefined;
141
+ instance?: string | undefined;
142
+ } | undefined;
143
+ } | undefined) => Promise<void>;
133
144
  /**
134
- * Push registry to configured URL (VM-agent). POST Prometheus text format + Basic auth.
145
+ * Push metrics to PushGateway.
135
146
  *
136
- * @param {object} [params] Unused; kept for API compatibility.
147
+ * @param {object} [params]
148
+ * @param {string} [params.jobName]
149
+ * @param {object} [params.groupings]
137
150
  * @returns {Promise<void>}
138
151
  */
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;
152
+ gatewayPush: (params?: {
153
+ jobName?: string | undefined;
154
+ groupings?: object | undefined;
155
+ } | undefined) => Promise<void>;
145
156
  /**
146
157
  * Merge the default metric labels (`app`, `dyno_id`, `process_type`)
147
158
  * with custom label names.
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"baseMetricsClient.d.ts","sourceRoot":"","sources":["../../src/metrics/baseMetricsClient.js"],"names":[],"mappings":"AAGA;;;GAGG;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;OAqD7C;IAlDC,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;IAED,wEAOC;IACD,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,sEAuBC;IAED,iCAEC;IAED;;;;;;;;;OASG;IACH,iFAEC;IAED;;;OAGG;IACH,eAFa,QAAQ,IAAI,CAAC,CAOzB;IAED;;;;;;;;;OASG;IACH,yBA0FC;IAED;;;;;;;;;OASG;IACH;;;;;;sBAFa,QAAQ,IAAI,CAAC,CAUzB;IAED;;;;;;;OAOG;IACH;;;sBAFa,QAAQ,IAAI,CAAC,CAezB;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,15 +2,10 @@
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');
9
5
 
10
6
  /**
11
7
  * BaseMetricsClient provides common functionality for all metrics clients.
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.
8
+ * Handles registry setup, pushgateway, default labels, and common operations.
14
9
  */
15
10
  class BaseMetricsClient {
16
11
  /**
@@ -20,8 +15,8 @@ class BaseMetricsClient {
20
15
  * @param {string} [config.processType] Process type (web, worker, etc.)
21
16
  * @param {boolean} [config.enabled] Enable metrics collection
22
17
  * @param {boolean} [config.logValues] Log metrics values to console
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)
18
+ * @param {string} [config.pushgatewayUrl] PushGateway URL
19
+ * @param {string} [config.pushgatewaySecret] PushGateway secret token
25
20
  * @param {number} [config.intervalSec] Interval in seconds for pushing metrics
26
21
  * @param {boolean} [config.removeOldMetrics] Enable to clear metrics by service name
27
22
  * @param {function} [config.startupValidation] Add to validate on start push.
@@ -48,9 +43,14 @@ class BaseMetricsClient {
48
43
  dyno_id: this.dynoId,
49
44
  process_type: this.processType
50
45
  };
51
-
52
- // Always push to configured URL (VM-agent). No Pushgateway.
53
- this.gateway = null;
46
+ this.gateway = new client.Pushgateway(this.pushgatewayUrl, {
47
+ headers: {
48
+ Authorization: `Basic ${this.authToken}`
49
+ },
50
+ agent: new https.Agent({
51
+ keepAlive: true
52
+ })
53
+ }, this._registry);
54
54
  this.gauges = {};
55
55
  this.counters = {};
56
56
  this.countersFunctions = {};
@@ -175,35 +175,16 @@ 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
- };
184
178
  if (customPushMetics && typeof customPushMetics === 'function') {
185
179
  setInterval(() => customPushMetics(), interval * 1000);
186
180
  } else {
187
181
  setInterval(() => {
188
- runPush().catch(err => {
182
+ this.pushMetrics().catch(err => {
189
183
  console.error(`${this.prefixLogs} Failed to push metrics:`, err);
190
184
  });
191
185
  }, interval * 1000);
192
186
  }
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})`);
187
+ console.warn(`${this.prefixLogs} Metrics collection started. (interval: ${this.intervalSec}s)`);
207
188
  };
208
189
  pushMetrics = async () => {
209
190
  return this._pushMetrics();
@@ -246,80 +227,104 @@ class BaseMetricsClient {
246
227
  */
247
228
  _clearOldWorkers = async removeOldMetrics => {
248
229
  if (!removeOldMetrics) return;
249
- // No Pushgateway; VM-agent does not support per-instance delete. Skip.
230
+ try {
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
+ }
250
286
  };
251
287
 
252
288
  /**
253
- * No-op (no Pushgateway). Kept for API compatibility.
289
+ * Delete metrics for this job/instance from PushGateway.
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
254
296
  * @returns {Promise<void>}
255
297
  */
256
- gatewayDelete = async () => {
257
- return Promise.resolve();
298
+ gatewayDelete = async (params = {}) => {
299
+ return this.gateway.delete({
300
+ jobName: params.jobName || this.appName,
301
+ groupings: params.groupings || {
302
+ process_type: this.processType,
303
+ instance: this.dynoId
304
+ }
305
+ });
258
306
  };
259
307
 
260
308
  /**
261
- * Push registry to configured URL (VM-agent). POST Prometheus text format + Basic auth.
309
+ * Push metrics to PushGateway.
262
310
  *
263
- * @param {object} [params] Unused; kept for API compatibility.
311
+ * @param {object} [params]
312
+ * @param {string} [params.jobName]
313
+ * @param {object} [params.groupings]
264
314
  * @returns {Promise<void>}
265
315
  */
266
316
  gatewayPush = async (params = {}) => {
267
317
  if (this.disablePushgateway) {
268
- console.warn(`${this.prefixLogs} Metrics push skipped: METRICS_DISABLE_PUSHGATEWAY is set`);
269
- return Promise.resolve();
270
- }
271
- if (!this.pushgatewayUrl || !this.pushgatewayUrl.trim()) {
272
- console.warn(`${this.prefixLogs} Metrics push skipped: METRICS_PUSHGATEWAY_URL is not set`);
273
318
  return Promise.resolve();
274
319
  }
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);
320
+ const groupings = {
321
+ process_type: this.processType,
322
+ instance: this.dynoId,
323
+ ...(params.groupings || {})
324
+ };
325
+ return this.gateway.push({
326
+ jobName: params.jobName || this.appName,
327
+ groupings
323
328
  });
324
329
  };
325
330
 
@@ -1 +1 @@
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":[]}
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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adalo/metrics",
3
- "version": "0.1.143",
3
+ "version": "0.1.144",
4
4
  "description": "Reusable metrics utilities for Node.js and Laravel apps",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -0,0 +1,21 @@
1
+ # This is utils for manage metrics
2
+
3
+
4
+ ## Type envs to handling require metrics
5
+ ```js
6
+ BUILD_APP_NAME=
7
+ HOSTNAME=
8
+ BUILD_DYNO_PROCESS_TYPE=
9
+ METRICS_ENABLED=
10
+ METRICS_LOG_VALUES=
11
+ METRICS_PUSHGATEWAY_URL=
12
+ METRICS_PUSHGATEWAY_SECRET=
13
+ METRICS_INTERVAL_SEC=
14
+
15
+ ```
16
+
17
+
18
+ ## Clear metriks for service
19
+ ```
20
+ node scripts/clearMetrics.js
21
+ ```
@@ -0,0 +1,6 @@
1
+ const { BaseMetricsClient } = require('../src')
2
+ require('dotenv').config()
3
+
4
+ new BaseMetricsClient({
5
+ removeOldMetrics: true,
6
+ })
@@ -1,12 +1,9 @@
1
1
  const client = require('prom-client')
2
2
  const https = require('https')
3
- const http = require('http')
4
- const { URL } = require('url')
5
3
 
6
4
  /**
7
5
  * BaseMetricsClient provides common functionality for all metrics clients.
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.
6
+ * Handles registry setup, pushgateway, default labels, and common operations.
10
7
  */
11
8
  class BaseMetricsClient {
12
9
  /**
@@ -16,8 +13,8 @@ class BaseMetricsClient {
16
13
  * @param {string} [config.processType] Process type (web, worker, etc.)
17
14
  * @param {boolean} [config.enabled] Enable metrics collection
18
15
  * @param {boolean} [config.logValues] Log metrics values to console
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)
16
+ * @param {string} [config.pushgatewayUrl] PushGateway URL
17
+ * @param {string} [config.pushgatewaySecret] PushGateway secret token
21
18
  * @param {number} [config.intervalSec] Interval in seconds for pushing metrics
22
19
  * @param {boolean} [config.removeOldMetrics] Enable to clear metrics by service name
23
20
  * @param {function} [config.startupValidation] Add to validate on start push.
@@ -57,8 +54,14 @@ class BaseMetricsClient {
57
54
  process_type: this.processType,
58
55
  }
59
56
 
60
- // Always push to configured URL (VM-agent). No Pushgateway.
61
- this.gateway = null
57
+ this.gateway = new client.Pushgateway(
58
+ this.pushgatewayUrl,
59
+ {
60
+ headers: { Authorization: `Basic ${this.authToken}` },
61
+ agent: new https.Agent({ keepAlive: true }),
62
+ },
63
+ this._registry
64
+ )
62
65
  this.gauges = {}
63
66
  this.counters = {}
64
67
  this.countersFunctions = {}
@@ -193,38 +196,18 @@ class BaseMetricsClient {
193
196
  return
194
197
  }
195
198
 
196
- const runPush = () => {
197
- if (customPushMetics && typeof customPushMetics === 'function') {
198
- return Promise.resolve(customPushMetics())
199
- }
200
- return this.pushMetrics()
201
- }
202
-
203
199
  if (customPushMetics && typeof customPushMetics === 'function') {
204
200
  setInterval(() => customPushMetics(), interval * 1000)
205
201
  } else {
206
202
  setInterval(() => {
207
- runPush().catch(err => {
203
+ this.pushMetrics().catch(err => {
208
204
  console.error(`${this.prefixLogs} Failed to push metrics:`, err)
209
205
  })
210
206
  }, interval * 1000)
211
207
  }
212
208
 
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
- }
226
209
  console.warn(
227
- `${this.prefixLogs} Metrics collection started. (interval: ${this.intervalSec}s, push: ${pushOrigin})`
210
+ `${this.prefixLogs} Metrics collection started. (interval: ${this.intervalSec}s)`
228
211
  )
229
212
  }
230
213
 
@@ -269,79 +252,136 @@ class BaseMetricsClient {
269
252
  */
270
253
  _clearOldWorkers = async removeOldMetrics => {
271
254
  if (!removeOldMetrics) return
272
- // No Pushgateway; VM-agent does not support per-instance delete. Skip.
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
+ }
273
343
  }
274
344
 
275
345
  /**
276
- * No-op (no Pushgateway). Kept for API compatibility.
346
+ * Delete metrics for this job/instance from PushGateway.
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
277
353
  * @returns {Promise<void>}
278
354
  */
279
- gatewayDelete = async () => {
280
- return Promise.resolve()
355
+ gatewayDelete = async (params = {}) => {
356
+ return this.gateway.delete({
357
+ jobName: params.jobName || this.appName,
358
+ groupings: params.groupings || {
359
+ process_type: this.processType,
360
+ instance: this.dynoId,
361
+ },
362
+ })
281
363
  }
282
364
 
283
365
  /**
284
- * Push registry to configured URL (VM-agent). POST Prometheus text format + Basic auth.
366
+ * Push metrics to PushGateway.
285
367
  *
286
- * @param {object} [params] Unused; kept for API compatibility.
368
+ * @param {object} [params]
369
+ * @param {string} [params.jobName]
370
+ * @param {object} [params.groupings]
287
371
  * @returns {Promise<void>}
288
372
  */
289
373
  gatewayPush = async (params = {}) => {
290
374
  if (this.disablePushgateway) {
291
- console.warn(`${this.prefixLogs} Metrics push skipped: METRICS_DISABLE_PUSHGATEWAY is set`)
292
- return Promise.resolve()
293
- }
294
- if (!this.pushgatewayUrl || !this.pushgatewayUrl.trim()) {
295
- console.warn(`${this.prefixLogs} Metrics push skipped: METRICS_PUSHGATEWAY_URL is not set`)
296
375
  return Promise.resolve()
297
376
  }
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
377
+ const groupings = {
378
+ process_type: this.processType,
379
+ instance: this.dynoId,
380
+ ...(params.groupings || {}),
314
381
  }
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)
382
+ return this.gateway.push({
383
+ jobName: params.jobName || this.appName,
384
+ groupings,
345
385
  })
346
386
  }
347
387