@adalo/metrics 0.1.113 → 0.1.115

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.
@@ -1,13 +1,13 @@
1
- const client = require('prom-client')
2
1
  const fs = require('fs')
3
2
  const os = require('os')
4
- const https = require('https')
3
+ const { BaseMetricsClient } = require('./baseMetricsClient')
5
4
 
6
5
  /**
7
6
  * MetricsClient handles Prometheus metrics collection and push.
8
7
  * Supports gauges, counters, default metrics, and custom metrics.
8
+ * Extends BaseMetricsClient for common functionality.
9
9
  */
10
- class MetricsClient {
10
+ class MetricsClient extends BaseMetricsClient {
11
11
  /**
12
12
  * @param {Object} config
13
13
  * @param {string} [config.appName] Name of the application
@@ -19,62 +19,17 @@ class MetricsClient {
19
19
  * @param {string} [config.pushgatewaySecret] PushGateway secret token
20
20
  * @param {number} [config.intervalSec] Interval in seconds for pushing metrics
21
21
  * @param {boolean} [config.removeOldMetrics] Enable to clear metrics by service name
22
- * @param {boolean} [config.scripDefaultMetrics] Enable to scip default metrics creation
23
22
  * @param {function} [config.startupValidation] Add to validate on start push.
24
23
  */
25
24
  constructor(config = {}) {
26
- this.appName = config.appName || process.env.BUILD_APP_NAME || 'unknown-app'
27
- this.dynoId = config.dynoId || process.env.HOSTNAME || 'unknown-dyno'
28
- this.processType =
29
- config.processType ||
30
- process.env.BUILD_DYNO_PROCESS_TYPE ||
31
- 'undefined_build_dyno_type'
32
- this.enabled = config.enabled ?? process.env.METRICS_ENABLED === 'true'
33
- this.logValues =
34
- config.logValues ?? process.env.METRICS_LOG_VALUES === 'true'
35
- this.pushgatewayUrl =
36
- config.pushgatewayUrl || process.env.METRICS_PUSHGATEWAY_URL || ''
37
- this.authToken =
38
- config.pushgatewaySecret || process.env.METRICS_PUSHGATEWAY_SECRET || ''
39
- this.intervalSec =
40
- config.intervalSec ||
41
- parseInt(process.env.METRICS_INTERVAL_SEC || '', 10) ||
42
- 15
43
- this.startupValidation = config.startupValidation
44
-
45
- this.prefixLogs = `[${this.processType}] [${this.appName}] [${this.dynoId}] [Monitoring]`
46
-
47
- this._registry = new client.Registry()
48
- client.collectDefaultMetrics({ register: this._registry })
49
-
50
- this.defaultLabels = {
51
- app: this.appName,
52
- dyno_id: this.dynoId,
53
- process_type: this.processType,
54
- }
25
+ super(config)
55
26
 
56
- this.gateway = new client.Pushgateway(
57
- this.pushgatewayUrl,
58
- {
59
- headers: { Authorization: `Basic ${this.authToken}` },
60
- agent: new https.Agent({ keepAlive: true }),
61
- },
62
- this._registry
63
- )
64
- this.gauges = {}
65
- this.counters = {}
66
- this.countersFunctions = {}
67
-
68
- /** @type {Object<string, function(): number | Promise<number>>} */
69
- this.gaugeUpdaters = {}
70
27
  this._lastUsageMicros = 0
71
28
  this._lastCheckTime = Date.now()
72
29
 
73
- this._clearOldWorkers(config.removeOldMetrics)
74
- if (!config.scripDefaultMetrics) {
75
- this._initDefaultMetrics()
76
- }
77
- this._setCleanupHandlers()
30
+ this._httpRequestBuffer = []
31
+
32
+ this._initDefaultMetrics()
78
33
  }
79
34
 
80
35
  /**
@@ -118,9 +73,9 @@ class MetricsClient {
118
73
  updateFn: process.uptime,
119
74
  })
120
75
 
121
- this.createCounter({
122
- name: 'app_requests_total',
123
- help: 'How long the process has been running',
76
+ this.createGauge({
77
+ name: 'http_app_requests_total',
78
+ help: 'Total number of HTTP requests (collected over interval)',
124
79
  labelNames: this.withDefaultLabels([
125
80
  'method',
126
81
  'route',
@@ -130,9 +85,9 @@ class MetricsClient {
130
85
  ]),
131
86
  })
132
87
 
133
- this.createCounter({
134
- name: 'app_requests_total_duration',
135
- help: 'How long the process has been running',
88
+ this.createGauge({
89
+ name: 'http_app_requests_total_duration',
90
+ help: 'Total duration of HTTP requests in milliseconds (collected over interval)',
136
91
  labelNames: this.withDefaultLabels([
137
92
  'method',
138
93
  'route',
@@ -143,71 +98,6 @@ class MetricsClient {
143
98
  })
144
99
  }
145
100
 
146
- /**
147
- * Create a gauge metric.
148
- * @param {Object} options - Gauge configuration
149
- * @param {string} options.name - Name of the gauge
150
- * @param {string} options.help - Help text describing the gauge
151
- * @param {function(): number|Promise<number>} [options.updateFn] - Optional function returning the gauge value
152
- * @param {string[]} [options.labelNames] - Optional custom label names
153
- * @returns {import('prom-client').Gauge} The created Prometheus gauge
154
- */
155
- createGauge = ({
156
- name,
157
- help,
158
- updateFn,
159
- labelNames = Object.keys(this.defaultLabels),
160
- }) => {
161
- if (this.gauges[name]) return this.gauges[name]
162
-
163
- const g = new client.Gauge({
164
- name,
165
- help,
166
- labelNames,
167
- registers: [this._registry],
168
- })
169
- this.gauges[name] = g
170
-
171
- if (updateFn && typeof updateFn === 'function') {
172
- this.gaugeUpdaters[name] = updateFn
173
- }
174
-
175
- return g
176
- }
177
-
178
- /**
179
- * Create a Prometheus Counter metric.
180
- *
181
- * @param {Object} params - Counter configuration
182
- * @param {string} params.name - Metric name
183
- * @param {string} params.help - Metric description
184
- * @param {string[]} [params.labelNames] - Optional list of label names. Defaults to this.defaultLabels keys.
185
- *
186
- * @returns {(labels?: Object, incrementValue?: number) => void}
187
- * A function to increment the counter.
188
- * Usage: (labels?, incrementValue?)
189
- */
190
- createCounter({ name, help, labelNames = Object.keys(this.defaultLabels) }) {
191
- if (this.counters[name]) return this.countersFunctions[name]
192
-
193
- const c = new client.Counter({
194
- name,
195
- help,
196
- labelNames,
197
- registers: [this._registry],
198
- })
199
- this.counters[name] = c
200
-
201
- this.countersFunctions = {
202
- ...this.countersFunctions,
203
- [name]: (data = {}, value = 1) => {
204
- c.inc({ ...this.defaultLabels, ...data }, value)
205
- },
206
- }
207
-
208
- return this.countersFunctions[name]
209
- }
210
-
211
101
  /**
212
102
  * Get CPU usage percent (cgroup-aware)
213
103
  * @returns {number}
@@ -307,9 +197,9 @@ class MetricsClient {
307
197
  }
308
198
 
309
199
  /**
310
- * Increment the HTTP requests counter with detailed request information.
200
+ * Track an HTTP request (adds to buffer for interval collection).
311
201
  *
312
- * @param {Object} params - The parameters for the request counter.
202
+ * @param {Object} params - The parameters for the request.
313
203
  * @param {string} params.method - HTTP method (GET, POST, etc.).
314
204
  * @param {string} params.route - The full requested URL or route.
315
205
  * @param {number} params.status_code - HTTP response status code.
@@ -325,28 +215,80 @@ class MetricsClient {
325
215
  databaseId = '',
326
216
  duration,
327
217
  }) {
328
- this.countersFunctions?.app_requests_total({
218
+ if (!this.enabled) return
219
+
220
+ this._httpRequestBuffer.push({
329
221
  method,
330
222
  route,
331
- status_code,
332
- appId,
333
- databaseId,
223
+ status_code: String(status_code),
224
+ appId: appId || '',
225
+ databaseId: databaseId || '',
226
+ duration,
334
227
  })
335
- this.countersFunctions?.app_requests_total_duration(
336
- {
337
- method,
338
- route,
339
- status_code,
340
- appId,
341
- databaseId,
342
- },
343
- duration
344
- )
228
+ }
229
+
230
+ /**
231
+ * Collect HTTP metrics from buffer, group by labels, and set gauges.
232
+ * @private
233
+ */
234
+ _collectHttpMetrics = () => {
235
+ if (this._httpRequestBuffer.length === 0) {
236
+ return
237
+ }
238
+
239
+ const grouped = {}
240
+ const defaultLabels = this.getDefaultLabels()
241
+
242
+ this._httpRequestBuffer.forEach(req => {
243
+ const key = JSON.stringify({
244
+ method: req.method,
245
+ route: req.route,
246
+ appId: req.appId,
247
+ databaseId: req.databaseId,
248
+ status_code: req.status_code,
249
+ })
250
+
251
+ if (!grouped[key]) {
252
+ grouped[key] = {
253
+ labels: {
254
+ method: req.method,
255
+ route: req.route,
256
+ appId: req.appId,
257
+ databaseId: req.databaseId,
258
+ status_code: req.status_code,
259
+ },
260
+ count: 0,
261
+ totalDuration: 0,
262
+ }
263
+ }
264
+
265
+ grouped[key].count += 1
266
+ grouped[key].totalDuration += req.duration || 0
267
+ })
268
+
269
+ Object.values(grouped).forEach(({ labels, count, totalDuration }) => {
270
+ const allLabels = { ...defaultLabels, ...labels }
271
+ this.gauges.http_app_requests_total.set(allLabels, count)
272
+ this.gauges.http_app_requests_total_duration.set(
273
+ allLabels,
274
+ totalDuration
275
+ )
276
+ })
277
+
278
+ this._httpRequestBuffer = []
279
+ }
280
+
281
+ /**
282
+ * Override pushMetrics to collect HTTP metrics before pushing.
283
+ */
284
+ pushMetrics = async () => {
285
+ this._collectHttpMetrics()
286
+ await super.pushMetrics()
345
287
  }
346
288
 
347
289
  /**
348
290
  * Express middleware to track HTTP requests.
349
- * Track the `app_requests_total` and `app_requests_total_duration` metric.
291
+ * Track the `http_app_requests_total` and `http_app_requests_total_duration` metric.
350
292
  */
351
293
  trackHttpRequestMiddleware = (req, res, next) => {
352
294
  if (!this.enabled || req.method === 'OPTIONS') {
@@ -380,278 +322,6 @@ class MetricsClient {
380
322
 
381
323
  next()
382
324
  }
383
-
384
- /**
385
- * Clear all collected counters
386
- */
387
- clearAllCounters = () => {
388
- if (this.metricsLogValues) {
389
- console.log('Counters to clear: ', Object.keys(this.counters))
390
- }
391
- Object.values(this.counters).forEach(counter => counter.reset())
392
- }
393
-
394
- /**
395
- * Push all gauges and counters to PushGateway and optionally log.
396
- */
397
- pushMetrics = async () => {
398
- try {
399
- for (const [name, updateFn] of Object.entries(this.gaugeUpdaters)) {
400
- try {
401
- if (!updateFn) {
402
- return
403
- }
404
- const result = updateFn()
405
- const val = result instanceof Promise ? await result : result
406
- if (val !== undefined) this.gauges[name].set(this.defaultLabels, val)
407
- } catch (err) {
408
- console.error(
409
- `${this.prefixLogs} Failed to update gauge ${name}:`,
410
- err
411
- )
412
- }
413
- }
414
-
415
- await this.gatewayPush()
416
- this.clearAllCounters()
417
-
418
- if (this.logValues) {
419
- const metrics = await this._registry.getMetricsAsJSON()
420
- console.log(
421
- `${this.prefixLogs} Metrics:\n`,
422
- JSON.stringify(metrics, null, 2)
423
- )
424
- }
425
- } catch (err) {
426
- console.error(`${this.prefixLogs} Failed to push metrics:`, err)
427
- }
428
- }
429
-
430
- _startPush = (interval = this.intervalSec, customPushMetics = undefined) => {
431
- if (!this.enabled) {
432
- console.warn(`${this.prefixLogs} Metrics disabled`)
433
- return
434
- }
435
-
436
- if (this.startupValidation && !this.startupValidation()) {
437
- return
438
- }
439
-
440
- if (customPushMetics && typeof customPushMetics === 'function') {
441
- setInterval(() => customPushMetics(), interval * 1000)
442
- } else {
443
- setInterval(() => {
444
- this.pushMetrics().catch(err => {
445
- console.error(`${this.prefixLogs} Failed to push metrics:`, err)
446
- })
447
- }, interval * 1000)
448
- }
449
-
450
- console.warn(
451
- `${this.prefixLogs} Metrics collection started. (interval: ${this.intervalSec}s)`
452
- )
453
- }
454
-
455
- /**
456
- * Start periodic metrics collection and push.
457
- *
458
- * This method wraps the internal `_startPush` method.
459
- * If a `customPushMetrics` function is provided, it will be executed
460
- * at the given interval instead of the default `pushMetrics` behavior.
461
- *
462
- * @param {number} [interval=this.intervalSec] - Interval in seconds between pushes.
463
- * @param {() => void | Promise<void>} [customPushMetrics] - Optional custom push function. If provided, Prometheus push is skipped.
464
- */
465
- startPush = (interval, customPushMetics = undefined) => {
466
- this._startPush(interval, customPushMetics)
467
- }
468
-
469
- /**
470
- * Cleanup metrics and exit process.
471
- * @returns {Promise<void>}
472
- */
473
- cleanup = async () => {
474
- if (this.enabled) {
475
- await this.gatewayDelete()
476
- }
477
- process.exit(0)
478
- }
479
-
480
- /**
481
- * Remove old/stale dyno/instance metrics from PushGateway.
482
- *
483
- * Compares existing PushGateway metrics for this job and deletes any instances
484
- * that do not match the current dynoId.
485
- *
486
- * @param {boolean} removeOldMetrics If true, performs cleanup; otherwise does nothing
487
- * @returns {Promise<void>}
488
- * @private
489
- */
490
- _clearOldWorkers = async removeOldMetrics => {
491
- if (!removeOldMetrics) return
492
-
493
- try {
494
- const url = `${this.pushgatewayUrl}/metrics`
495
- const res = await fetch(url, {
496
- headers: {
497
- Authorization: `Basic ${this.authToken}`,
498
- Accept: 'text/plain',
499
- },
500
- })
501
-
502
- if (!res.ok) {
503
- console.error(
504
- `${this.prefixLogs} Failed to fetch metrics: ${res.status}`
505
- )
506
- return
507
- }
508
-
509
- const text = await res.text()
510
-
511
- const metricRegex = /([a-zA-Z_:][a-zA-Z0-9_:]*)\{([^}]*)\}/gm
512
- const labelRegex = /(\w+)="([^"]*)"/g
513
-
514
- const uniqueLabelSets = new Set()
515
-
516
- let match
517
- // eslint-disable-next-line no-cond-assign
518
- while ((match = metricRegex.exec(text)) !== null) {
519
- const rawLabels = match[2]
520
- let lr
521
- const labels = {}
522
-
523
- // eslint-disable-next-line no-cond-assign
524
- while ((lr = labelRegex.exec(rawLabels)) !== null) {
525
- // eslint-disable-next-line prefer-destructuring
526
- labels[lr[1]] = lr[2]
527
- }
528
-
529
- if (
530
- labels.job === this.appName &&
531
- labels.process_type === this.processType
532
- ) {
533
- uniqueLabelSets.add(JSON.stringify(labels))
534
- }
535
- }
536
-
537
- if (uniqueLabelSets.size === 0) {
538
- console.log(
539
- `${this.prefixLogs} No metrics found for job ${this.appName}`
540
- )
541
- return
542
- }
543
-
544
- const oldLabelSets = [...uniqueLabelSets]
545
- .map(s => JSON.parse(s))
546
- .filter(
547
- labels =>
548
- labels.instance &&
549
- labels.instance !== this.dynoId &&
550
- labels.process_type === this.processType
551
- )
552
-
553
- if (oldLabelSets.length === 0) {
554
- console.log(`${this.prefixLogs} No old dynos to delete.`)
555
- return
556
- }
557
-
558
- for (const labels of oldLabelSets) {
559
- try {
560
- await this.gatewayDelete({ jobName: this.appName, groupings: labels })
561
- console.log(
562
- `${this.prefixLogs} Deleted metrics for dyno: ${
563
- labels.instance
564
- }, labels: ${Object.keys(labels)} `
565
- )
566
- } catch (err) {
567
- console.error(
568
- `${this.prefixLogs} Failed to delete metrics for ${labels.instance}:`,
569
- err
570
- )
571
- }
572
- }
573
-
574
- console.log(
575
- `${this.prefixLogs} Cleared all old instances for job ${this.appName}`
576
- )
577
- } catch (err) {
578
- console.error(`${this.prefixLogs} Error deleting old metrics:`, err)
579
- }
580
- }
581
-
582
- /**
583
- * Delete metrics for this job/instance from PushGateway.
584
- *
585
- * @param {Object} [params]
586
- * @param {string} [params.jobName] Job name (defaults to appName)
587
- * @param {Object} [params.groupings] Grouping labels
588
- * @param {string} [params.groupings.process_type] Process type label
589
- * @param {string} [params.groupings.instance] Instance/dyno ID
590
- * @returns {Promise<void>}
591
- */
592
- gatewayDelete = async (params = {}) => {
593
- return this.gateway.delete({
594
- jobName: params.jobName || this.appName,
595
- groupings: params.groupings || {
596
- process_type: this.processType,
597
- instance: this.dynoId,
598
- },
599
- })
600
- }
601
-
602
- /**
603
- * Push metrics to PushGateway.
604
- *
605
- * @param {object} [params]
606
- * @param {string} [params.jobName]
607
- * @param {object} [params.groupings]
608
- * @returns {Promise<void>}
609
- */
610
- gatewayPush = async (params = {}) => {
611
- const groupings = {
612
- process_type: this.processType,
613
- instance: this.dynoId,
614
- ...(params.groupings || {}),
615
- }
616
- return this.gateway.push({
617
- jobName: params.jobName || this.appName,
618
- groupings,
619
- })
620
- }
621
-
622
- /**
623
- * Merge the default metric labels (`app`, `dyno_id`, `process_type`)
624
- * with custom label names.
625
- *
626
- * @param {string[]} labels Additional label names
627
- * @returns {string[]} Combined label names
628
- */
629
- withDefaultLabels = (labels = []) => {
630
- return [...Object.keys(this.defaultLabels), ...labels]
631
- }
632
-
633
- getDefaultLabels = (labels = []) => {
634
- return this.defaultLabels
635
- }
636
-
637
- _setCleanupHandlers = () => {
638
- process.on('SIGINT', this.cleanup)
639
- process.on('SIGTERM', this.cleanup)
640
- }
641
-
642
- // GETTERS
643
-
644
- get metricsEnabled() {
645
- return this.enabled
646
- }
647
-
648
- get metricsLogValues() {
649
- return this.logValues
650
- }
651
-
652
- get registry() {
653
- return this._registry
654
- }
655
325
  }
656
326
 
657
327
  module.exports = { MetricsClient }
@@ -1,28 +1,27 @@
1
1
  const { Pool } = require('pg')
2
- const { MetricsClient } = require('.')
2
+ const { BaseMetricsClient } = require('./baseMetricsClient')
3
3
 
4
4
  /**
5
5
  * DatabaseMetricsClient collects Postgres connection metrics
6
6
  * and pushes them to Prometheus Pushgateway.
7
7
  *
8
- * @extends MetricsClient
8
+ * @extends BaseMetricsClient
9
9
  */
10
- class DatabaseMetricsClient extends MetricsClient {
10
+ class DatabaseMetricsClient extends BaseMetricsClient {
11
11
  /**
12
12
  * @param {Object} options
13
13
  * @param {string} options.databaseUrl - Required main database URL
14
14
  * @param {string} options.databaseName - Main database name for metrics
15
15
  * @param {Object<string, string>} [options.additional_database_urls] - Optional additional DBs, keyed by custom name with URL as value
16
- * @param {string} [options.appName] - Application name (from MetricsClient)
17
- * @param {string} [options.dynoId] - Dyno/instance ID (from MetricsClient)
18
- * @param {string} [options.processType] - Process type (from MetricsClient)
19
- * @param {boolean} [options.enabled] - Enable metrics collection (from MetricsClient)
20
- * @param {boolean} [options.logValues] - Log metrics values (from MetricsClient)
21
- * @param {string} [options.pushgatewayUrl] - PushGateway URL (from MetricsClient)
22
- * @param {string} [options.pushgatewaySecret] - PushGateway secret token (from MetricsClient)
16
+ * @param {string} [options.appName] - Application name (from BaseMetricsClient)
17
+ * @param {string} [options.dynoId] - Dyno/instance ID (from BaseMetricsClient)
18
+ * @param {string} [options.processType] - Process type (from BaseMetricsClient)
19
+ * @param {boolean} [options.enabled] - Enable metrics collection (from BaseMetricsClient)
20
+ * @param {boolean} [options.logValues] - Log metrics values (from BaseMetricsClient)
21
+ * @param {string} [options.pushgatewayUrl] - PushGateway URL (from BaseMetricsClient)
22
+ * @param {string} [options.pushgatewaySecret] - PushGateway secret token (from BaseMetricsClient)
23
23
  * @param {number} [options.intervalSec] - Interval in seconds for pushing metrics
24
24
  * @param {boolean} [options.removeOldMetrics] - Remove old metrics by service
25
- * @param {boolean} [options.scripDefaultMetrics] - Skip default metrics creation
26
25
  * @param {function} [options.startupValidation] - Function to validate startup
27
26
  */
28
27
  constructor({
@@ -78,7 +77,6 @@ class DatabaseMetricsClient extends MetricsClient {
78
77
 
79
78
  super({
80
79
  ...metricsConfig,
81
- scripDefaultMetrics: true,
82
80
  processType: metricsConfig.processType || 'database-metrics',
83
81
  intervalSec,
84
82
  startupValidation,
@@ -3,7 +3,7 @@ const { RedisMetricsClient } = require('.')
3
3
  const { IOREDIS } = require('./redisUtils')
4
4
 
5
5
  /**
6
- * QueueRedisMetricsClient extends MetricsClient to collect
6
+ * QueueRedisMetricsClient extends RedisMetricsClient to collect
7
7
  * Redis and Bee Queue metrics periodically and push them to Prometheus Pushgateway.
8
8
  *
9
9
  * @extends RedisMetricsClient
@@ -12,17 +12,16 @@ class QueueRedisMetricsClient extends RedisMetricsClient {
12
12
  /**
13
13
  * @param {Object} options
14
14
  * @param {any} options.redisClient - Redis client instance (required)
15
- * @param {string} [options.appName] - Application name (from MetricsClient)
16
- * @param {string} [options.dynoId] - Dyno/instance ID (from MetricsClient)
17
- * @param {string} [options.processType] - Process type (from MetricsClient)
18
- * @param {boolean} [options.enabled] - Enable metrics collection (from MetricsClient)
19
- * @param {boolean} [options.logValues] - Log metrics values (from MetricsClient)
20
- * @param {string} [options.pushgatewayUrl] - PushGateway URL (from MetricsClient)
21
- * @param {string} [options.pushgatewaySecret] - PushGateway secret token (from MetricsClient)
22
- * @param {number} [options.intervalSec] - Interval in seconds for pushing metrics (from MetricsClient)
23
- * @param {boolean} [options.removeOldMetrics] - Remove old metrics by service (from MetricsClient)
24
- * @param {boolean} [options.scripDefaultMetrics] - Skip default metrics creation (from MetricsClient)
25
- * @param {function} [options.startupValidation] - Function to validate startup (from MetricsClient)
15
+ * @param {string} [options.appName] - Application name (from BaseMetricsClient)
16
+ * @param {string} [options.dynoId] - Dyno/instance ID (from BaseMetricsClient)
17
+ * @param {string} [options.processType] - Process type (from BaseMetricsClient)
18
+ * @param {boolean} [options.enabled] - Enable metrics collection (from BaseMetricsClient)
19
+ * @param {boolean} [options.logValues] - Log metrics values (from BaseMetricsClient)
20
+ * @param {string} [options.pushgatewayUrl] - PushGateway URL (from BaseMetricsClient)
21
+ * @param {string} [options.pushgatewaySecret] - PushGateway secret token (from BaseMetricsClient)
22
+ * @param {number} [options.intervalSec] - Interval in seconds for pushing metrics (from BaseMetricsClient)
23
+ * @param {boolean} [options.removeOldMetrics] - Remove old metrics by service (from BaseMetricsClient)
24
+ * @param {function} [options.startupValidation] - Function to validate startup (from BaseMetricsClient)
26
25
  */
27
26
  constructor({ redisClient, metricsConfig = {} } = {}) {
28
27
  const getConfiguredQueueNames = () => {