@adalo/metrics 0.1.173 → 0.1.175

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.
Files changed (53) hide show
  1. package/README.md +7 -0
  2. package/__tests__/httpMetricsRedisCollector.test.js +203 -0
  3. package/__tests__/httpMetricsRedisRecorder.test.js +60 -0
  4. package/__tests__/httpMetricsRedisStore.test.js +431 -0
  5. package/docs/http-metrics-redis.md +19 -0
  6. package/lib/index.d.ts +4 -0
  7. package/lib/index.d.ts.map +1 -1
  8. package/lib/index.js +44 -0
  9. package/lib/index.js.map +1 -1
  10. package/lib/metrics/baseMetricsClient.d.ts +2 -0
  11. package/lib/metrics/baseMetricsClient.d.ts.map +1 -1
  12. package/lib/metrics/baseMetricsClient.js +6 -3
  13. package/lib/metrics/baseMetricsClient.js.map +1 -1
  14. package/lib/metrics/httpMetricsRedisCollector.d.ts +50 -0
  15. package/lib/metrics/httpMetricsRedisCollector.d.ts.map +1 -0
  16. package/lib/metrics/httpMetricsRedisCollector.js +115 -0
  17. package/lib/metrics/httpMetricsRedisCollector.js.map +1 -0
  18. package/lib/metrics/httpMetricsRedisRecorder.d.ts +48 -0
  19. package/lib/metrics/httpMetricsRedisRecorder.d.ts.map +1 -0
  20. package/lib/metrics/httpMetricsRedisRecorder.js +86 -0
  21. package/lib/metrics/httpMetricsRedisRecorder.js.map +1 -0
  22. package/lib/metrics/httpMetricsRedisStore.d.ts +88 -0
  23. package/lib/metrics/httpMetricsRedisStore.d.ts.map +1 -0
  24. package/lib/metrics/httpMetricsRedisStore.js +223 -0
  25. package/lib/metrics/httpMetricsRedisStore.js.map +1 -0
  26. package/lib/metrics/metricsClient.d.ts +34 -27
  27. package/lib/metrics/metricsClient.d.ts.map +1 -1
  28. package/lib/metrics/metricsClient.js +35 -37
  29. package/lib/metrics/metricsClient.js.map +1 -1
  30. package/lib/metrics/metricsDatabaseClient.d.ts.map +1 -1
  31. package/lib/metrics/metricsDatabaseClient.js +6 -1
  32. package/lib/metrics/metricsDatabaseClient.js.map +1 -1
  33. package/lib/metrics/metricsProcessTypeUtils.d.ts +58 -0
  34. package/lib/metrics/metricsProcessTypeUtils.d.ts.map +1 -0
  35. package/lib/metrics/metricsProcessTypeUtils.js +86 -0
  36. package/lib/metrics/metricsProcessTypeUtils.js.map +1 -0
  37. package/lib/metrics/metricsQueueRedisClient.d.ts.map +1 -1
  38. package/lib/metrics/metricsQueueRedisClient.js +5 -0
  39. package/lib/metrics/metricsQueueRedisClient.js.map +1 -1
  40. package/lib/metrics/metricsRedisClient.d.ts.map +1 -1
  41. package/lib/metrics/metricsRedisClient.js +7 -1
  42. package/lib/metrics/metricsRedisClient.js.map +1 -1
  43. package/package.json +5 -5
  44. package/src/index.ts +4 -0
  45. package/src/metrics/baseMetricsClient.js +4 -1
  46. package/src/metrics/httpMetricsRedisCollector.js +121 -0
  47. package/src/metrics/httpMetricsRedisRecorder.js +74 -0
  48. package/src/metrics/httpMetricsRedisStore.js +208 -0
  49. package/src/metrics/metricsClient.js +34 -53
  50. package/src/metrics/metricsDatabaseClient.js +7 -1
  51. package/src/metrics/metricsProcessTypeUtils.js +98 -0
  52. package/src/metrics/metricsQueueRedisClient.js +6 -0
  53. package/src/metrics/metricsRedisClient.js +12 -1
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Record separator for hash fields (avoids collisions when route contains "|").
3
+ * @type {string}
4
+ */
5
+ const FIELD_SEP = '\x1e'
6
+
7
+ /**
8
+ * Default Redis key TTL in seconds (sliding: refreshed on each `record` write).
9
+ * @type {number}
10
+ */
11
+ const DEFAULT_HTTP_METRICS_REDIS_TTL_SEC = 120
12
+
13
+ const DRAIN_LUA = `
14
+ local function drain(key)
15
+ local v = redis.call('HGETALL', key)
16
+ redis.call('DEL', key)
17
+ return v
18
+ end
19
+ return {drain(KEYS[1]), drain(KEYS[2])}
20
+ `
21
+
22
+ /**
23
+ * @param {string} method
24
+ * @param {string} route
25
+ * @param {number} statusCode
26
+ * @returns {string}
27
+ */
28
+ function buildFieldKey(method, route, statusCode) {
29
+ return [method, route, String(statusCode)].join(FIELD_SEP)
30
+ }
31
+
32
+ function hgetallPairsToObject(pairs) {
33
+ const o = {}
34
+ if (!pairs || !pairs.length) {
35
+ return o
36
+ }
37
+ for (let i = 0; i < pairs.length; i += 2) {
38
+ o[pairs[i]] = pairs[i + 1]
39
+ }
40
+ return o
41
+ }
42
+
43
+ /**
44
+ * @param {unknown} raw redis eval result [countPairs, durPairs]
45
+ * @returns {{ labels: { method: string, route: string, status_code: string }, count: number, dur: number }[]}
46
+ */
47
+ function rowsFromDrainRaw(raw) {
48
+ const rows = []
49
+ if (!raw || !Array.isArray(raw) || raw.length < 2) {
50
+ return rows
51
+ }
52
+ const counts = hgetallPairsToObject(raw[0])
53
+ const durs = hgetallPairsToObject(raw[1])
54
+ const fieldKeys = Object.keys(counts)
55
+ for (const field of fieldKeys) {
56
+ const count = parseInt(counts[field], 10)
57
+ if (!count || count < 1) {
58
+ continue
59
+ }
60
+ const dur = parseInt(durs[field] || '0', 10) || 0
61
+ const parts = field.split(FIELD_SEP)
62
+ let labels = null
63
+ if (parts.length === 3) {
64
+ const [m, route, statusStr] = parts
65
+ labels = { method: m, route, status_code: statusStr }
66
+ } else if (parts.length === 5 || parts.length === 6) {
67
+ const [m, route, statusStr] = parts
68
+ labels = { method: m, route, status_code: statusStr }
69
+ }
70
+ if (!labels) {
71
+ continue
72
+ }
73
+ rows.push({ labels, count, dur })
74
+ }
75
+ return rows
76
+ }
77
+
78
+ /**
79
+ * Redis HTTP aggregate store. Uses an **injected** client (same pattern as {@link RedisMetricsClient}).
80
+ * Expects `redis` v3-style API: `multi().hincrby().expire().exec(cb)`, `eval`.
81
+ *
82
+ * **Structure:** `countKey` / `durKey` are each **one Redis hash**. Each **field name** encodes
83
+ * `(method, route, status_code)`. Values: `:count` is HINCRBY 1 per request; `:dur` is summed ms.
84
+ */
85
+ class HttpMetricsRedisStore {
86
+ /**
87
+ * @param {Object} opts
88
+ * @param {import('redis').RedisClient} opts.redisClient
89
+ * @param {string} opts.appName BUILD_APP_NAME (key segment)
90
+ * @param {string} opts.processType logical process for key (e.g. web)
91
+ * @param {number} [opts.ttlSec] Expire keys after this many seconds (default 120)
92
+ */
93
+ constructor({ redisClient, appName, processType, ttlSec }) {
94
+ if (redisClient == null) {
95
+ throw new Error('HttpMetricsRedisStore: redisClient is required')
96
+ }
97
+ this._client = redisClient
98
+ this.ttlSec =
99
+ typeof ttlSec === 'number' && ttlSec > 0
100
+ ? ttlSec
101
+ : DEFAULT_HTTP_METRICS_REDIS_TTL_SEC
102
+ const keySeg = `${encodeURIComponent(appName)}:${encodeURIComponent(
103
+ processType
104
+ )}`
105
+ this.countKey = `metrics:http:v2:${keySeg}:count`
106
+ this.durKey = `metrics:http:v2:${keySeg}:dur`
107
+ }
108
+
109
+ /**
110
+ * @returns {import('redis').RedisClient}
111
+ * @private
112
+ */
113
+ _ensureClient() {
114
+ return this._client
115
+ }
116
+
117
+ /**
118
+ * @param {string} method
119
+ * @param {string} route
120
+ * @param {number} statusCode
121
+ * @param {number} durationMs
122
+ */
123
+ record(method, route, statusCode, durationMs) {
124
+ try {
125
+ const client = this._ensureClient()
126
+ const field = buildFieldKey(method, route, statusCode)
127
+ const dur = Math.max(0, Math.round(Number(durationMs) || 0))
128
+ client
129
+ .multi()
130
+ .hincrby(this.countKey, field, 1)
131
+ .hincrby(this.durKey, field, dur)
132
+ .expire(this.countKey, this.ttlSec)
133
+ .expire(this.durKey, this.ttlSec)
134
+ .exec(err => {
135
+ if (err) {
136
+ console.error('[HttpMetricsRedisStore] record failed:', err.message)
137
+ }
138
+ })
139
+ } catch (e) {
140
+ console.error('[HttpMetricsRedisStore] record:', e.message)
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Atomically drain Redis hashes (same Lua as `flushToCounters`) and return parsed rows.
146
+ *
147
+ * @returns {Promise<{ ok: boolean, rows: { labels: Object, count: number, dur: number }[] }>}
148
+ */
149
+ drainRows() {
150
+ let client
151
+ try {
152
+ client = this._ensureClient()
153
+ } catch (e) {
154
+ console.error('[HttpMetricsRedisStore] drainRows:', e.message)
155
+ return Promise.resolve({ ok: false, rows: [] })
156
+ }
157
+ return new Promise(resolve => {
158
+ client.eval(DRAIN_LUA, 2, this.countKey, this.durKey, (evalErr, raw) => {
159
+ if (evalErr) {
160
+ console.error(
161
+ '[HttpMetricsRedisStore] drain failed:',
162
+ evalErr.message
163
+ )
164
+ resolve({ ok: false, rows: [] })
165
+ return
166
+ }
167
+ try {
168
+ const rows = rowsFromDrainRaw(raw)
169
+ resolve({ ok: true, rows })
170
+ } catch (e) {
171
+ console.error(
172
+ '[HttpMetricsRedisStore] drainRows parse failed:',
173
+ e.message
174
+ )
175
+ resolve({ ok: false, rows: [] })
176
+ }
177
+ })
178
+ })
179
+ }
180
+
181
+ /**
182
+ * @param {(labels: Object, value: number) => void} applyCount
183
+ * @param {(labels: Object, value: number) => void} applyDuration
184
+ * @returns {Promise<boolean>}
185
+ */
186
+ flushToCounters(applyCount, applyDuration) {
187
+ return this.drainRows().then(({ ok, rows }) => {
188
+ if (!ok) {
189
+ return false
190
+ }
191
+ for (const row of rows) {
192
+ applyCount(row.labels, row.count)
193
+ if (row.dur > 0) {
194
+ applyDuration(row.labels, row.dur)
195
+ }
196
+ }
197
+ return true
198
+ })
199
+ }
200
+ }
201
+
202
+ module.exports = {
203
+ HttpMetricsRedisStore,
204
+ buildFieldKey,
205
+ FIELD_SEP,
206
+ DEFAULT_HTTP_METRICS_REDIS_TTL_SEC,
207
+ rowsFromDrainRaw,
208
+ }
@@ -4,32 +4,38 @@ const { BaseMetricsClient } = require('./baseMetricsClient')
4
4
 
5
5
  /**
6
6
  * MetricsClient handles Prometheus metrics collection and push.
7
- * Supports gauges, counters, default metrics, and custom metrics.
8
- * Extends BaseMetricsClient for common functionality.
7
+ * Supports gauges, default process metrics, optional HTTP counters, and custom metrics.
8
+ *
9
+ * **HTTP metrics:** In-process counters only (`app_requests_*`), gated by `httpMetricsEnabled` /
10
+ * `METRICS_HTTP_ENABLED`. For Redis-backed HTTP aggregation (multi-web / cluster), use
11
+ * {@link HttpMetricsRedisRecorder} and {@link HttpMetricsRedisCollector} — not this class.
12
+ *
13
+ * @extends BaseMetricsClient
9
14
  */
10
15
  class MetricsClient extends BaseMetricsClient {
11
16
  /**
12
- * @param {Object} config
17
+ * @param {Object} [config]
13
18
  * @param {string} [config.appName] Name of the application
14
19
  * @param {string} [config.dynoId] Dyno/instance ID
15
20
  * @param {string} [config.processType] Process type (web, worker, etc.)
16
21
  * @param {boolean} [config.enabled] Enable metrics collection
17
- * @param {boolean} [config.httpMetricsEnabled=false] Enable HTTP request metrics (app_requests_total, app_requests_total_duration)
22
+ * @param {boolean} [config.httpMetricsEnabled] Enable HTTP request metrics (`app_requests_total`, `app_requests_total_duration`); defaults from `METRICS_HTTP_ENABLED === 'true'`
18
23
  * @param {boolean} [config.logValues] Log metrics values to console
19
- * @param {string} [config.pushgatewayUrl] PushGateway URL
20
- * @param {string} [config.pushgatewaySecret] PushGateway secret token
24
+ * @param {string} [config.pushgatewayUrl] Push URL (VM-agent import endpoint, e.g. .../api/v1/import/prometheus). /metrics is for GET (scrape), not POST (push).
25
+ * @param {string} [config.pushgatewaySecret] Basic auth secret (Base64 of `user:password`)
21
26
  * @param {number} [config.intervalSec] Interval in seconds for pushing metrics
22
27
  * @param {boolean} [config.removeOldMetrics] Enable to clear metrics by service name
23
- * @param {function} [config.startupValidation] Add to validate on start push.
24
- * @param {boolean} [config.disablePushgateway] Disable pushing to Pushgateway (use HTTP scraping instead)
28
+ * @param {function} [config.startupValidation] Add to validate on start push
29
+ * @param {boolean} [config.disablePushgateway] Disable pushing to VM-agent (use HTTP scraping instead)
30
+ * @param {boolean} [config.blockNodeDefaultMetrics] When true, skip prom-client default process metrics (rare; see {@link BaseMetricsClient})
25
31
  */
26
32
  constructor(config = {}) {
27
33
  super(config)
28
34
 
29
35
  this.httpMetricsEnabled =
30
- config.httpMetricsEnabled ??
31
- process.env.METRICS_HTTP_ENABLED === 'true' ??
32
- false
36
+ config.httpMetricsEnabled !== undefined
37
+ ? config.httpMetricsEnabled
38
+ : process.env.METRICS_HTTP_ENABLED === 'true'
33
39
 
34
40
  this._lastUsageMicros = 0
35
41
  this._lastCheckTime = Date.now()
@@ -85,8 +91,6 @@ class MetricsClient extends BaseMetricsClient {
85
91
  labelNames: this.withDefaultLabelsWithoutDynoId([
86
92
  'method',
87
93
  'route',
88
- 'appId',
89
- 'databaseId',
90
94
  'status_code',
91
95
  ]),
92
96
  useLabelsWithoutDynoId: true,
@@ -98,8 +102,6 @@ class MetricsClient extends BaseMetricsClient {
98
102
  labelNames: this.withDefaultLabelsWithoutDynoId([
99
103
  'method',
100
104
  'route',
101
- 'appId',
102
- 'databaseId',
103
105
  'status_code',
104
106
  ]),
105
107
  useLabelsWithoutDynoId: true,
@@ -139,7 +141,7 @@ class MetricsClient extends BaseMetricsClient {
139
141
  }
140
142
 
141
143
  /**
142
- * Get available CPU cores.
144
+ * Available CPU cores (cgroup quota or `os.cpus().length`).
143
145
  * @returns {number}
144
146
  */
145
147
  getAvailableCPUs() {
@@ -160,7 +162,7 @@ class MetricsClient extends BaseMetricsClient {
160
162
  }
161
163
 
162
164
  /**
163
- * Get container memory usage in bytes.
165
+ * Container memory usage in bytes (`memory.current` or RSS fallback).
164
166
  * @returns {number}
165
167
  */
166
168
  getContainerMemoryUsage() {
@@ -175,7 +177,7 @@ class MetricsClient extends BaseMetricsClient {
175
177
  }
176
178
 
177
179
  /**
178
- * Get container memory limit in bytes.
180
+ * Container memory limit in bytes (`memory.max` or host total).
179
181
  * @returns {number}
180
182
  */
181
183
  getContainerMemoryLimit() {
@@ -195,7 +197,7 @@ class MetricsClient extends BaseMetricsClient {
195
197
  }
196
198
 
197
199
  /**
198
- * Measure event loop lag in ms.
200
+ * Event loop lag sample in milliseconds.
199
201
  * @returns {Promise<number>}
200
202
  */
201
203
  measureLag() {
@@ -206,48 +208,39 @@ class MetricsClient extends BaseMetricsClient {
206
208
  }
207
209
 
208
210
  /**
209
- * Increment the HTTP requests counter with detailed request information.
211
+ * Increment HTTP request counters (in-process). No-op if `httpMetricsEnabled` is false.
210
212
  *
211
- * @param {Object} params - The parameters for the request counter.
212
- * @param {string} params.method - HTTP method (GET, POST, etc.).
213
- * @param {string} params.route - The full requested URL or route.
214
- * @param {number} params.status_code - HTTP response status code.
215
- * @param {string} [params.appId=''] - Optional application identifier.
216
- * @param {string} [params.databaseId=''] - Optional database identifier.
217
- * @param {number} params.duration - Request duration in milliseconds.
213
+ * @param {Object} params
214
+ * @param {string} params.method HTTP method
215
+ * @param {string} params.route Route or path pattern
216
+ * @param {number} params.status_code HTTP status code
217
+ * @param {number} params.duration Duration in milliseconds
218
218
  */
219
- trackHttpRequest({
220
- method,
221
- route,
222
- status_code,
223
- appId = '',
224
- databaseId = '',
225
- duration,
226
- }) {
219
+ trackHttpRequest({ method, route, status_code, duration }) {
227
220
  if (!this.httpMetricsEnabled) return
228
221
 
229
222
  this.countersFunctions?.app_requests_total({
230
223
  method,
231
224
  route,
232
225
  status_code,
233
- appId,
234
- databaseId,
235
226
  })
236
227
  this.countersFunctions?.app_requests_total_duration(
237
228
  {
238
229
  method,
239
230
  route,
240
231
  status_code,
241
- appId,
242
- databaseId,
243
232
  },
244
233
  duration
245
234
  )
246
235
  }
247
236
 
248
237
  /**
249
- * Express middleware to track HTTP requests.
250
- * Track the `app_requests_total` and `app_requests_total_duration` metric.
238
+ * Express middleware: records `app_requests_*` on response finish.
239
+ * Skips when disabled, HTTP metrics off, or `OPTIONS`.
240
+ *
241
+ * @param {import('http').IncomingMessage} req
242
+ * @param {import('http').ServerResponse} res
243
+ * @param {function} next
251
244
  */
252
245
  trackHttpRequestMiddleware = (req, res, next) => {
253
246
  if (!this.enabled || !this.httpMetricsEnabled || req.method === 'OPTIONS') {
@@ -258,23 +251,11 @@ class MetricsClient extends BaseMetricsClient {
258
251
  const start = Date.now()
259
252
  res.on('finish', () => {
260
253
  const route = req.route?.path || req.path || 'unknown'
261
- const appId =
262
- req.params?.appId || req.body?.appId || req.query?.appId || ''
263
- const databaseId =
264
- req.params?.databaseId ||
265
- req.body?.databaseId ||
266
- req.query?.databaseId ||
267
- req.params?.datasourceId ||
268
- req.body?.datasourceId ||
269
- req.query?.datasourceId ||
270
- ''
271
254
 
272
255
  this.trackHttpRequest({
273
256
  method: req.method,
274
257
  route,
275
258
  status_code: res.statusCode,
276
- appId,
277
- databaseId,
278
259
  duration: Date.now() - start,
279
260
  })
280
261
  })
@@ -1,5 +1,9 @@
1
1
  const { Pool } = require('pg')
2
2
  const { BaseMetricsClient } = require('./baseMetricsClient')
3
+ const {
4
+ exitUnlessProcessTypeIs,
5
+ METRICS_PROCESS_TYPE_DATABASE,
6
+ } = require('./metricsProcessTypeUtils')
3
7
 
4
8
  /**
5
9
  * DatabaseMetricsClient collects Postgres connection metrics
@@ -31,6 +35,8 @@ class DatabaseMetricsClient extends BaseMetricsClient {
31
35
  additional_database_urls = {},
32
36
  ...metricsConfig
33
37
  } = {}) {
38
+ exitUnlessProcessTypeIs(metricsConfig, METRICS_PROCESS_TYPE_DATABASE)
39
+
34
40
  const intervalSec =
35
41
  metricsConfig.intervalSec ||
36
42
  parseInt(process.env.METRICS_DATABASE_INTERVAL_SEC || '', 10) ||
@@ -78,7 +84,7 @@ class DatabaseMetricsClient extends BaseMetricsClient {
78
84
 
79
85
  super({
80
86
  ...metricsConfig,
81
- processType: metricsConfig.processType || 'database-metrics',
87
+ processType: metricsConfig.processType || METRICS_PROCESS_TYPE_DATABASE,
82
88
  intervalSec,
83
89
  startupValidation,
84
90
  })
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Helpers for resolving `processType` and silently exiting when a specialized metrics client
3
+ * is constructed on the wrong dyno / process (no log output).
4
+ *
5
+ * **Canonical names** align with typical Procfile processes (e.g. backend: `web`, `worker`,
6
+ * `queue-metrics`, `database-metrics`, `http-metrics`). The compile **worker** dyno serves queues/builds
7
+ * — it does not run HTTP request metrics. HTTP Redis **key segment** for API traffic is fixed **`web`**
8
+ * (`HttpMetricsRedisCollector` / `HttpMetricsRedisRecorder`), not `BUILD_DYNO_PROCESS_TYPE`.
9
+ *
10
+ * @module metrics/metricsProcessTypeUtils
11
+ */
12
+
13
+ /** DB-only metrics dyno (`database-metrics` in Procfile). */
14
+ const METRICS_PROCESS_TYPE_DATABASE = 'database-metrics'
15
+
16
+ /** Queue + Redis metrics dyno (`queue-metrics` in Procfile). */
17
+ const METRICS_PROCESS_TYPE_QUEUE = 'queue-metrics'
18
+
19
+ /** Redis-only metrics dyno (no Bee Queue; optional separate process). */
20
+ const METRICS_PROCESS_TYPE_REDIS = 'redis-metrics'
21
+
22
+ /** Web servers — HTTP traffic, HTTP Redis **writers** typically use this in Redis key segment. */
23
+ const METRICS_PROCESS_TYPE_WEB = 'web'
24
+
25
+ /** Build/compile workers and similar (e.g. backend `worker:`) — no HTTP server metrics here. */
26
+ const METRICS_PROCESS_TYPE_WORKER = 'worker'
27
+
28
+ /**
29
+ * Parent {@link RedisMetricsClient} allows either redis-only or queue stack (`QueueRedisMetricsClient`).
30
+ * @type {readonly string[]}
31
+ */
32
+ const REDIS_METRICS_CLIENT_ALLOWED_PROCESS_TYPES = Object.freeze([
33
+ METRICS_PROCESS_TYPE_REDIS,
34
+ METRICS_PROCESS_TYPE_QUEUE,
35
+ ])
36
+
37
+ /**
38
+ * Resolve logical process type the same way specialized metrics clients do.
39
+ *
40
+ * @param {{ processType?: string }} metricsConfig - Remainder of constructor options (e.g. after destructuring `redisClient`, `databaseUrl`, …)
41
+ * @param {string} defaultProcessType - Fallback when `metricsConfig.processType` and `BUILD_DYNO_PROCESS_TYPE` are unset
42
+ * @returns {string}
43
+ */
44
+ function resolveMetricsProcessType(metricsConfig, defaultProcessType) {
45
+ return (
46
+ metricsConfig.processType ||
47
+ process.env.BUILD_DYNO_PROCESS_TYPE ||
48
+ defaultProcessType
49
+ )
50
+ }
51
+
52
+ /**
53
+ * Exit with no logs if the resolved process type is not exactly `expectedProcessType`.
54
+ *
55
+ * @param {{ processType?: string }} metricsConfig
56
+ * @param {string} expectedProcessType - Single allowed value (use a module constant, e.g. {@link METRICS_PROCESS_TYPE_DATABASE})
57
+ * @returns {void}
58
+ */
59
+ function exitUnlessProcessTypeIs(metricsConfig, expectedProcessType) {
60
+ const resolved = resolveMetricsProcessType(metricsConfig, expectedProcessType)
61
+ if (resolved !== expectedProcessType) {
62
+ process.exit(0)
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Exit with no logs if the resolved process type is not in `allowedProcessTypes`.
68
+ *
69
+ * @param {{ processType?: string }} metricsConfig
70
+ * @param {readonly string[]} allowedProcessTypes
71
+ * @param {string} defaultWhenUnspecified - Used only to resolve when config/env omit `processType` (e.g. {@link METRICS_PROCESS_TYPE_QUEUE} for {@link RedisMetricsClient})
72
+ * @returns {void}
73
+ */
74
+ function exitUnlessProcessTypeIn(
75
+ metricsConfig,
76
+ allowedProcessTypes,
77
+ defaultWhenUnspecified
78
+ ) {
79
+ const resolved = resolveMetricsProcessType(
80
+ metricsConfig,
81
+ defaultWhenUnspecified
82
+ )
83
+ if (!allowedProcessTypes.includes(resolved)) {
84
+ process.exit(0)
85
+ }
86
+ }
87
+
88
+ module.exports = {
89
+ resolveMetricsProcessType,
90
+ exitUnlessProcessTypeIs,
91
+ exitUnlessProcessTypeIn,
92
+ METRICS_PROCESS_TYPE_DATABASE,
93
+ METRICS_PROCESS_TYPE_QUEUE,
94
+ METRICS_PROCESS_TYPE_REDIS,
95
+ METRICS_PROCESS_TYPE_WEB,
96
+ METRICS_PROCESS_TYPE_WORKER,
97
+ REDIS_METRICS_CLIENT_ALLOWED_PROCESS_TYPES,
98
+ }
@@ -1,5 +1,9 @@
1
1
  const { RedisMetricsClient } = require('./metricsRedisClient')
2
2
  const { IOREDIS, REDIS_V3, REDIS_V4 } = require('../redisUtils')
3
+ const {
4
+ exitUnlessProcessTypeIs,
5
+ METRICS_PROCESS_TYPE_QUEUE,
6
+ } = require('./metricsProcessTypeUtils')
3
7
 
4
8
  /**
5
9
  * QueueRedisMetricsClient extends RedisMetricsClient to collect
@@ -24,6 +28,8 @@ class QueueRedisMetricsClient extends RedisMetricsClient {
24
28
  * @param {boolean} [options.disablePushgateway] - Disable pushing to Pushgateway (use HTTP scraping instead)
25
29
  */
26
30
  constructor({ redisClient, ...metricsConfig } = {}) {
31
+ exitUnlessProcessTypeIs(metricsConfig, METRICS_PROCESS_TYPE_QUEUE)
32
+
27
33
  const getConfiguredQueueNames = () => {
28
34
  if (!process.env.METRICS_APP_REDIS_BQ) {
29
35
  throw new Error(
@@ -5,6 +5,11 @@ const {
5
5
  IOREDIS,
6
6
  REDIS_V3,
7
7
  } = require('../redisUtils')
8
+ const {
9
+ exitUnlessProcessTypeIn,
10
+ METRICS_PROCESS_TYPE_QUEUE,
11
+ REDIS_METRICS_CLIENT_ALLOWED_PROCESS_TYPES,
12
+ } = require('./metricsProcessTypeUtils')
8
13
 
9
14
  const redisConnectionStableFields = ['name', 'flags', 'cmd']
10
15
  const redisConnectionFields = ['name', 'flags', 'tot-mem', 'cmd']
@@ -36,6 +41,12 @@ class RedisMetricsClient extends BaseMetricsClient {
36
41
  throw new Error('RedisMetricsClient requires redisClient')
37
42
  }
38
43
 
44
+ exitUnlessProcessTypeIn(
45
+ metricsConfig,
46
+ REDIS_METRICS_CLIENT_ALLOWED_PROCESS_TYPES,
47
+ METRICS_PROCESS_TYPE_QUEUE
48
+ )
49
+
39
50
  const intervalSec =
40
51
  metricsConfig.intervalSec ||
41
52
  parseInt(process.env.METRICS_QUEUE_INTERVAL_SEC || '', 10) ||
@@ -43,7 +54,7 @@ class RedisMetricsClient extends BaseMetricsClient {
43
54
 
44
55
  super({
45
56
  ...metricsConfig,
46
- processType: metricsConfig.processType || 'queue-metrics',
57
+ processType: metricsConfig.processType || METRICS_PROCESS_TYPE_QUEUE,
47
58
  intervalSec,
48
59
  })
49
60