@adalo/metrics 0.0.0-staging.28 → 0.0.0-staging.29

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/README.md CHANGED
@@ -31,7 +31,7 @@ new MetricsClient({
31
31
 
32
32
  ### HTTP metrics: `MetricsClient` vs Redis
33
33
 
34
- **`MetricsClient`** only registers **in-process** `app_requests_*` counters when `METRICS_HTTP_ENABLED` / `httpMetricsEnabled` is on. For **Redis-backed** HTTP aggregation (multi-web / cluster), use **`HttpMetricsRedisRecorder`** (writers) and **`HttpMetricsRedisCollector`** (drain + push) with an injected **`redisClient`** — same idea as `RedisMetricsClient`, not mixed into `MetricsClient`. See **`docs/http-metrics-redis.md`** for key segments and what is stored in Redis.
34
+ **`MetricsClient`** only registers **in-process** `app_requests_*` counters when `METRICS_HTTP_ENABLED` / `httpMetricsEnabled` is on. For **Redis-backed** HTTP aggregation (multi-web / cluster), use **`HttpMetricsRedisRecorder`** (writers) and **`HttpMetricsRedisCollector`** (drain + push) with an injected **`redisClient`** — same idea as `RedisMetricsClient`, not mixed into `MetricsClient`. See **[`docs/http-metrics-redis.md`](docs/http-metrics-redis.md)** (quick Redis reference) and the repo guide **[`../docs/METRICS.md`](../docs/METRICS.md)** (full architecture for devs and AI assistants).
35
35
 
36
36
  `DatabaseMetricsClient` / `QueueRedisMetricsClient` / `RedisMetricsClient` call **`process.exit(0)` with no logs** if `BUILD_DYNO_PROCESS_TYPE` (or `processType`) does not match `database-metrics` / `queue-metrics` / (`redis-metrics` or `queue-metrics`) respectively.
37
37
  ## Example Usage
@@ -35,7 +35,7 @@ describe('HttpMetricsRedisCollector', () => {
35
35
 
36
36
  it('pushMetrics drains Redis then completes without network push', async () => {
37
37
  const redis = createRedisV3Mock()
38
- const field = buildFieldKey('GET', '/health', 200, '', '', '99')
38
+ const field = buildFieldKey('GET', '/health', 200)
39
39
  redis.eval.mockImplementation((lua, numKeys, k1, k2, cb) => {
40
40
  cb(null, [
41
41
  [field, '1'],
@@ -44,14 +44,12 @@ describe('HttpMetricsRedisRecorder', () => {
44
44
  })
45
45
  const countKey = rec._store.countKey
46
46
  const durKey = rec._store.durKey
47
- const field = buildFieldKey('GET', '/p', 404, 'x', 'y', String(process.pid))
47
+ const field = buildFieldKey('GET', '/p', 404)
48
48
 
49
49
  rec.trackHttpRequest({
50
50
  method: 'GET',
51
51
  route: '/p',
52
52
  status_code: 404,
53
- appId: 'x',
54
- databaseId: 'y',
55
53
  duration: 5,
56
54
  })
57
55
  await flushMicrotasks()
@@ -87,9 +87,9 @@ function createRedisV3InMemoryMock() {
87
87
 
88
88
  describe('HttpMetricsRedisStore', () => {
89
89
  describe('buildFieldKey', () => {
90
- it('joins parts with FIELD_SEP (includes pid)', () => {
91
- expect(buildFieldKey('GET', '/api', 200, 'app1', 'db1', '94662')).toBe(
92
- ['GET', '/api', '200', 'app1', 'db1', '94662'].join(FIELD_SEP)
90
+ it('joins method, route, status with FIELD_SEP', () => {
91
+ expect(buildFieldKey('GET', '/api', 200)).toBe(
92
+ ['GET', '/api', '200'].join(FIELD_SEP)
93
93
  )
94
94
  })
95
95
  })
@@ -123,9 +123,9 @@ describe('HttpMetricsRedisStore', () => {
123
123
  processType: 'web',
124
124
  ttlSec: 90,
125
125
  })
126
- const field = buildFieldKey('GET', '/x', 200, '', '', String(process.pid))
126
+ const field = buildFieldKey('GET', '/x', 200)
127
127
 
128
- store.record('GET', '/x', 200, '', '', 12)
128
+ store.record('GET', '/x', 200, 12)
129
129
  await flushMicrotasks()
130
130
 
131
131
  expect(redis.multi).toHaveBeenCalled()
@@ -140,7 +140,7 @@ describe('HttpMetricsRedisStore', () => {
140
140
  describe('flushToCounters', () => {
141
141
  it('applies aggregated count and summed duration for one route (many requests → one field)', async () => {
142
142
  const redis = createRedisV3Mock()
143
- const field = buildFieldKey('GET', '/api/items', 200, '', '', '111')
143
+ const field = buildFieldKey('GET', '/api/items', 200)
144
144
  redis.eval.mockImplementation((lua, numKeys, k1, k2, cb) => {
145
145
  cb(null, [
146
146
  [field, '100'],
@@ -164,9 +164,6 @@ describe('HttpMetricsRedisStore', () => {
164
164
  method: 'GET',
165
165
  route: '/api/items',
166
166
  status_code: '200',
167
- appId: '',
168
- databaseId: '',
169
- pid: '111',
170
167
  },
171
168
  100
172
169
  )
@@ -175,9 +172,6 @@ describe('HttpMetricsRedisStore', () => {
175
172
  method: 'GET',
176
173
  route: '/api/items',
177
174
  status_code: '200',
178
- appId: '',
179
- databaseId: '',
180
- pid: '111',
181
175
  },
182
176
  4500
183
177
  )
@@ -185,7 +179,7 @@ describe('HttpMetricsRedisStore', () => {
185
179
 
186
180
  it('drains hashes and applies count and duration', async () => {
187
181
  const redis = createRedisV3Mock()
188
- const field = buildFieldKey('POST', '/r', 201, 'a1', 'd1', '222')
182
+ const field = buildFieldKey('POST', '/r', 201)
189
183
  redis.eval.mockImplementation((lua, numKeys, k1, k2, cb) => {
190
184
  cb(null, [
191
185
  [field, '2'],
@@ -216,9 +210,6 @@ describe('HttpMetricsRedisStore', () => {
216
210
  method: 'POST',
217
211
  route: '/r',
218
212
  status_code: '201',
219
- appId: 'a1',
220
- databaseId: 'd1',
221
- pid: '222',
222
213
  },
223
214
  2
224
215
  )
@@ -227,17 +218,14 @@ describe('HttpMetricsRedisStore', () => {
227
218
  method: 'POST',
228
219
  route: '/r',
229
220
  status_code: '201',
230
- appId: 'a1',
231
- databaseId: 'd1',
232
- pid: '222',
233
221
  },
234
222
  50
235
223
  )
236
224
  })
237
225
 
238
- it('legacy 5-part hash fields get pid=legacy', async () => {
226
+ it('legacy 6-part hash fields still drain (labels from first three segments)', async () => {
239
227
  const redis = createRedisV3Mock()
240
- const legacyField = ['GET', '/old', '200', '', ''].join(FIELD_SEP)
228
+ const legacyField = ['GET', '/old', '200', 'a', 'd', '999'].join(FIELD_SEP)
241
229
  redis.eval.mockImplementation((lua, numKeys, k1, k2, cb) => {
242
230
  cb(null, [
243
231
  [legacyField, '3'],
@@ -254,11 +242,19 @@ describe('HttpMetricsRedisStore', () => {
254
242
  const ok = await store.flushToCounters(applyCount, applyDuration)
255
243
  expect(ok).toBe(true)
256
244
  expect(applyCount).toHaveBeenCalledWith(
257
- expect.objectContaining({ pid: 'legacy' }),
245
+ {
246
+ method: 'GET',
247
+ route: '/old',
248
+ status_code: '200',
249
+ },
258
250
  3
259
251
  )
260
252
  expect(applyDuration).toHaveBeenCalledWith(
261
- expect.objectContaining({ pid: 'legacy' }),
253
+ {
254
+ method: 'GET',
255
+ route: '/old',
256
+ status_code: '200',
257
+ },
262
258
  9
263
259
  )
264
260
  })
@@ -292,7 +288,7 @@ describe('HttpMetricsRedisStore', () => {
292
288
  for (let i = 0; i < routes.length; i++) {
293
289
  const method = i % 2 === 0 ? 'GET' : 'POST'
294
290
  const status = i % 5 === 0 ? 500 : 200
295
- store.record(method, routes[i], status, `app-${i % 4}`, `db-${i % 3}`, 10 + i)
291
+ store.record(method, routes[i], status, 10 + i)
296
292
  }
297
293
  expect(redis._fieldCount(store.countKey)).toBe(routes.length)
298
294
 
@@ -324,7 +320,7 @@ describe('HttpMetricsRedisStore', () => {
324
320
  })
325
321
  const n = 80
326
322
  for (let i = 0; i < n; i++) {
327
- store.record('GET', '/hot', 200, 'a1', 'd1', 5)
323
+ store.record('GET', '/hot', 200, 5)
328
324
  }
329
325
  expect(redis._fieldCount(store.countKey)).toBe(1)
330
326
 
@@ -338,14 +334,11 @@ describe('HttpMetricsRedisStore', () => {
338
334
  method: 'GET',
339
335
  route: '/hot',
340
336
  status_code: '200',
341
- appId: 'a1',
342
- databaseId: 'd1',
343
- pid: String(process.pid),
344
337
  }),
345
338
  n
346
339
  )
347
340
  expect(applyDuration).toHaveBeenCalledWith(
348
- expect.objectContaining({ route: '/hot', pid: String(process.pid) }),
341
+ expect.objectContaining({ route: '/hot' }),
349
342
  n * 5
350
343
  )
351
344
  })
@@ -356,9 +349,9 @@ describe('HttpMetricsRedisStore', () => {
356
349
  const dynoA = new HttpMetricsRedisStore(opts)
357
350
  const dynoB = new HttpMetricsRedisStore(opts)
358
351
 
359
- dynoA.record('GET', '/a', 200, '', '', 3)
360
- dynoB.record('GET', '/b', 304, '', '', 7)
361
- dynoA.record('POST', '/c', 201, 'x', 'y', 11)
352
+ dynoA.record('GET', '/a', 200, 3)
353
+ dynoB.record('GET', '/b', 304, 7)
354
+ dynoA.record('POST', '/c', 201, 11)
362
355
 
363
356
  expect(redis._fieldCount(dynoA.countKey)).toBe(3)
364
357
 
@@ -384,7 +377,7 @@ describe('HttpMetricsRedisStore', () => {
384
377
  Promise.all(
385
378
  Array.from({ length: 15 }, (_, j) => {
386
379
  const route = `/w${worker}/r${j}`
387
- store.record('GET', route, 200, `app${worker}`, 'db0', 2)
380
+ store.record('GET', route, 200, 2)
388
381
  return Promise.resolve()
389
382
  })
390
383
  )
@@ -416,12 +409,12 @@ describe('HttpMetricsRedisStore', () => {
416
409
  processType: 'api',
417
410
  })
418
411
  const samples = [
419
- ['GET', '/x', 200, 'a', 'd', 1],
420
- ['GET', '/x', 404, 'a', 'd', 2],
421
- ['DELETE', '/x|y', 204, 'b', 'd', 3],
412
+ ['GET', '/x', 200, 1],
413
+ ['GET', '/x', 404, 2],
414
+ ['DELETE', '/x|y', 204, 3],
422
415
  ]
423
- for (const [m, r, s, a, d, dur] of samples) {
424
- store.record(m, r, s, a, d, dur)
416
+ for (const [m, r, s, dur] of samples) {
417
+ store.record(m, r, s, dur)
425
418
  }
426
419
 
427
420
  const applyCount = jest.fn()
@@ -0,0 +1,18 @@
1
+ # HTTP metrics via Redis (`HttpMetricsRedisRecorder` / `HttpMetricsRedisCollector`)
2
+
3
+ This file is a **quick entry point** for the Redis-backed HTTP aggregation path. **Full architecture, env vars, and diagrams** are in the repository guide:
4
+
5
+ **[docs/METRICS.md](../../docs/METRICS.md)**
6
+
7
+ ## Cheat sheet
8
+
9
+ | Concern | Detail |
10
+ |--------|--------|
11
+ | **Writers** | `HttpMetricsRedisRecorder` — Express middleware records `method`, `route`, `status_code`, duration into Redis hashes. |
12
+ | **Reader** | `HttpMetricsRedisCollector` — separate process calls `pushMetrics()`, which **drains** Redis (atomic Lua) and increments `app_requests_*`, then pushes to VM-agent. |
13
+ | **Redis key shape** | `metrics:http:v2:<encode(appName)>:<encode(processType)>:count` and `:dur` — two hashes per writer group. |
14
+ | **Hash field** | Three logical parts: method, route, HTTP status (joined with an internal `FIELD_SEP`). |
15
+ | **Prometheus labels** | `method`, `route`, `status_code` (plus default `app` / `process_type` without `dyno_id` on these counters). |
16
+ | **Backend** | Web: `backend/monitoring.ts` + `METRICS_HTTP_ENABLED`. Drain: `backend/http-metrics-collector.ts`, Procfile `http-metrics:`. |
17
+
18
+ For in-process HTTP metrics **without** Redis, see **`MetricsClient`** + `METRICS_HTTP_ENABLED` in **[docs/METRICS.md](../../docs/METRICS.md#http-metrics-two-valid-designs)**.
@@ -1 +1 @@
1
- {"version":3,"file":"httpMetricsRedisCollector.d.ts","sourceRoot":"","sources":["../../src/metrics/httpMetricsRedisCollector.js"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AACH;IACE;;;;;;;;;;;;;;;;OAgBG;IACH;qBAfW,OAAO,OAAO,EAAE,WAAW;;;;;;;;;;;;;;mBAmErC;IAlCC,8BAKE;CAkDL"}
1
+ {"version":3,"file":"httpMetricsRedisCollector.d.ts","sourceRoot":"","sources":["../../src/metrics/httpMetricsRedisCollector.js"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AACH;IACE;;;;;;;;;;;;;;;;OAgBG;IACH;qBAfW,OAAO,OAAO,EAAE,WAAW;;;;;;;;;;;;;;mBA6DrC;IA5BC,8BAKE;CA4CL"}
@@ -59,13 +59,13 @@ class HttpMetricsRedisCollector extends BaseMetricsClient {
59
59
  this.createCounter({
60
60
  name: 'app_requests_total',
61
61
  help: 'Total number of HTTP requests',
62
- labelNames: this.withDefaultLabelsWithoutDynoId(['method', 'route', 'appId', 'databaseId', 'status_code', 'pid']),
62
+ labelNames: this.withDefaultLabelsWithoutDynoId(['method', 'route', 'status_code']),
63
63
  useLabelsWithoutDynoId: true
64
64
  });
65
65
  this.createCounter({
66
66
  name: 'app_requests_total_duration',
67
67
  help: 'Total duration of HTTP requests in milliseconds',
68
- labelNames: this.withDefaultLabelsWithoutDynoId(['method', 'route', 'appId', 'databaseId', 'status_code', 'pid']),
68
+ labelNames: this.withDefaultLabelsWithoutDynoId(['method', 'route', 'status_code']),
69
69
  useLabelsWithoutDynoId: true
70
70
  });
71
71
  }
@@ -1 +1 @@
1
- {"version":3,"file":"httpMetricsRedisCollector.js","names":["BaseMetricsClient","require","HttpMetricsRedisStore","HttpMetricsRedisCollector","constructor","config","redisClient","Error","blockNodeDefaultMetrics","keyProcessType","redisProcessTypeForKeys","defaultLabelsWithoutDynoId","app","appName","process_type","_store","processType","ttlSec","createCounter","name","help","labelNames","withDefaultLabelsWithoutDynoId","useLabelsWithoutDynoId","pushMetrics","countersFunctions","app_requests_total","app_requests_total_duration","flushToCounters","labels","value","_pushMetrics","module","exports"],"sources":["../../src/metrics/httpMetricsRedisCollector.js"],"sourcesContent":["const { BaseMetricsClient } = require('./baseMetricsClient')\nconst { HttpMetricsRedisStore } = require('./httpMetricsRedisStore')\n\n/**\n * Drain worker: reads HTTP aggregates from Redis (written by {@link HttpMetricsRedisRecorder}),\n * applies them to `app_requests_*` counters, then pushes the registry to the VM-agent (same as {@link BaseMetricsClient}).\n * **Minimal usage:** `{ redisClient }` only. Redis keys use segment **`web`** unless you pass **`redisProcessTypeForKeys`**.\n * `processType` / `appName` / `dynoId` follow {@link BaseMetricsClient} defaults (e.g. `BUILD_DYNO_PROCESS_TYPE`) and do **not** select Redis hash names.\n * Always passes `blockNodeDefaultMetrics: true` (HTTP-focused registry).\n *\n * @extends BaseMetricsClient\n */\nclass HttpMetricsRedisCollector extends BaseMetricsClient {\n /**\n * @param {Object} [config]\n * @param {import('redis').RedisClient} config.redisClient **Required.** Injected client (same pattern as {@link RedisMetricsClient}).\n * @param {string} [config.appName] Application name (defaults per {@link BaseMetricsClient})\n * @param {string} [config.dynoId] Dyno/instance ID\n * @param {string} [config.processType] Label `process_type` on push (default from env / base)\n * @param {boolean} [config.enabled] Enable collection and push\n * @param {boolean} [config.logValues] Log metric JSON to console\n * @param {string} [config.pushgatewayUrl] VM-agent import URL\n * @param {string} [config.pushgatewaySecret] Basic auth secret (Base64)\n * @param {number} [config.intervalSec] Push interval (seconds)\n * @param {boolean} [config.removeOldMetrics] Clear old series on shutdown where supported\n * @param {function} [config.startupValidation] Run before first push\n * @param {boolean} [config.disablePushgateway] Skip POST to VM-agent\n * @param {string} [config.redisProcessTypeForKeys] Segment in Redis keys for HTTP hashes (default **`web`**). Optional; only if writers use a non-`web` segment.\n * @param {number} [config.ttlSec] Passed to {@link HttpMetricsRedisStore} (should match writers)\n */\n constructor(config = {}) {\n const { redisClient } = config\n if (redisClient == null) {\n throw new Error('HttpMetricsRedisCollector: redisClient is required')\n }\n\n super({\n ...config,\n blockNodeDefaultMetrics: true,\n })\n\n const keyProcessType = config.redisProcessTypeForKeys || 'web'\n\n this.defaultLabelsWithoutDynoId = {\n app: this.appName,\n process_type: keyProcessType,\n }\n\n this._store = new HttpMetricsRedisStore({\n redisClient,\n appName: this.appName,\n processType: keyProcessType,\n ttlSec: config.ttlSec,\n })\n\n this.createCounter({\n name: 'app_requests_total',\n help: 'Total number of HTTP requests',\n labelNames: this.withDefaultLabelsWithoutDynoId([\n 'method',\n 'route',\n 'appId',\n 'databaseId',\n 'status_code',\n 'pid',\n ]),\n useLabelsWithoutDynoId: true,\n })\n\n this.createCounter({\n name: 'app_requests_total_duration',\n help: 'Total duration of HTTP requests in milliseconds',\n labelNames: this.withDefaultLabelsWithoutDynoId([\n 'method',\n 'route',\n 'appId',\n 'databaseId',\n 'status_code',\n 'pid',\n ]),\n useLabelsWithoutDynoId: true,\n })\n }\n\n /**\n * Drains Redis into counters, then runs gauge updates and VM-agent push ({@link BaseMetricsClient#_pushMetrics}).\n * @returns {Promise<void>}\n */\n pushMetrics = async () => {\n if (\n this._store &&\n this.countersFunctions?.app_requests_total &&\n this.countersFunctions?.app_requests_total_duration\n ) {\n await this._store.flushToCounters(\n (labels, value) =>\n this.countersFunctions.app_requests_total(labels, value),\n (labels, value) =>\n this.countersFunctions.app_requests_total_duration(labels, value)\n )\n }\n return this._pushMetrics()\n }\n}\n\nmodule.exports = { HttpMetricsRedisCollector }\n"],"mappings":";;AAAA,MAAM;EAAEA;AAAkB,CAAC,GAAGC,OAAO,CAAC,qBAAqB,CAAC;AAC5D,MAAM;EAAEC;AAAsB,CAAC,GAAGD,OAAO,CAAC,yBAAyB,CAAC;;AAEpE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAME,yBAAyB,SAASH,iBAAiB,CAAC;EACxD;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACEI,WAAWA,CAACC,MAAM,GAAG,CAAC,CAAC,EAAE;IACvB,MAAM;MAAEC;IAAY,CAAC,GAAGD,MAAM;IAC9B,IAAIC,WAAW,IAAI,IAAI,EAAE;MACvB,MAAM,IAAIC,KAAK,CAAC,oDAAoD,CAAC;IACvE;IAEA,KAAK,CAAC;MACJ,GAAGF,MAAM;MACTG,uBAAuB,EAAE;IAC3B,CAAC,CAAC;IAEF,MAAMC,cAAc,GAAGJ,MAAM,CAACK,uBAAuB,IAAI,KAAK;IAE9D,IAAI,CAACC,0BAA0B,GAAG;MAChCC,GAAG,EAAE,IAAI,CAACC,OAAO;MACjBC,YAAY,EAAEL;IAChB,CAAC;IAED,IAAI,CAACM,MAAM,GAAG,IAAIb,qBAAqB,CAAC;MACtCI,WAAW;MACXO,OAAO,EAAE,IAAI,CAACA,OAAO;MACrBG,WAAW,EAAEP,cAAc;MAC3BQ,MAAM,EAAEZ,MAAM,CAACY;IACjB,CAAC,CAAC;IAEF,IAAI,CAACC,aAAa,CAAC;MACjBC,IAAI,EAAE,oBAAoB;MAC1BC,IAAI,EAAE,+BAA+B;MACrCC,UAAU,EAAE,IAAI,CAACC,8BAA8B,CAAC,CAC9C,QAAQ,EACR,OAAO,EACP,OAAO,EACP,YAAY,EACZ,aAAa,EACb,KAAK,CACN,CAAC;MACFC,sBAAsB,EAAE;IAC1B,CAAC,CAAC;IAEF,IAAI,CAACL,aAAa,CAAC;MACjBC,IAAI,EAAE,6BAA6B;MACnCC,IAAI,EAAE,iDAAiD;MACvDC,UAAU,EAAE,IAAI,CAACC,8BAA8B,CAAC,CAC9C,QAAQ,EACR,OAAO,EACP,OAAO,EACP,YAAY,EACZ,aAAa,EACb,KAAK,CACN,CAAC;MACFC,sBAAsB,EAAE;IAC1B,CAAC,CAAC;EACJ;;EAEA;AACF;AACA;AACA;EACEC,WAAW,GAAG,MAAAA,CAAA,KAAY;IACxB,IACE,IAAI,CAACT,MAAM,IACX,IAAI,CAACU,iBAAiB,EAAEC,kBAAkB,IAC1C,IAAI,CAACD,iBAAiB,EAAEE,2BAA2B,EACnD;MACA,MAAM,IAAI,CAACZ,MAAM,CAACa,eAAe,CAC/B,CAACC,MAAM,EAAEC,KAAK,KACZ,IAAI,CAACL,iBAAiB,CAACC,kBAAkB,CAACG,MAAM,EAAEC,KAAK,CAAC,EAC1D,CAACD,MAAM,EAAEC,KAAK,KACZ,IAAI,CAACL,iBAAiB,CAACE,2BAA2B,CAACE,MAAM,EAAEC,KAAK,CACpE,CAAC;IACH;IACA,OAAO,IAAI,CAACC,YAAY,CAAC,CAAC;EAC5B,CAAC;AACH;AAEAC,MAAM,CAACC,OAAO,GAAG;EAAE9B;AAA0B,CAAC","ignoreList":[]}
1
+ {"version":3,"file":"httpMetricsRedisCollector.js","names":["BaseMetricsClient","require","HttpMetricsRedisStore","HttpMetricsRedisCollector","constructor","config","redisClient","Error","blockNodeDefaultMetrics","keyProcessType","redisProcessTypeForKeys","defaultLabelsWithoutDynoId","app","appName","process_type","_store","processType","ttlSec","createCounter","name","help","labelNames","withDefaultLabelsWithoutDynoId","useLabelsWithoutDynoId","pushMetrics","countersFunctions","app_requests_total","app_requests_total_duration","flushToCounters","labels","value","_pushMetrics","module","exports"],"sources":["../../src/metrics/httpMetricsRedisCollector.js"],"sourcesContent":["const { BaseMetricsClient } = require('./baseMetricsClient')\nconst { HttpMetricsRedisStore } = require('./httpMetricsRedisStore')\n\n/**\n * Drain worker: reads HTTP aggregates from Redis (written by {@link HttpMetricsRedisRecorder}),\n * applies them to `app_requests_*` counters, then pushes the registry to the VM-agent (same as {@link BaseMetricsClient}).\n * **Minimal usage:** `{ redisClient }` only. Redis keys use segment **`web`** unless you pass **`redisProcessTypeForKeys`**.\n * `processType` / `appName` / `dynoId` follow {@link BaseMetricsClient} defaults (e.g. `BUILD_DYNO_PROCESS_TYPE`) and do **not** select Redis hash names.\n * Always passes `blockNodeDefaultMetrics: true` (HTTP-focused registry).\n *\n * @extends BaseMetricsClient\n */\nclass HttpMetricsRedisCollector extends BaseMetricsClient {\n /**\n * @param {Object} [config]\n * @param {import('redis').RedisClient} config.redisClient **Required.** Injected client (same pattern as {@link RedisMetricsClient}).\n * @param {string} [config.appName] Application name (defaults per {@link BaseMetricsClient})\n * @param {string} [config.dynoId] Dyno/instance ID\n * @param {string} [config.processType] Label `process_type` on push (default from env / base)\n * @param {boolean} [config.enabled] Enable collection and push\n * @param {boolean} [config.logValues] Log metric JSON to console\n * @param {string} [config.pushgatewayUrl] VM-agent import URL\n * @param {string} [config.pushgatewaySecret] Basic auth secret (Base64)\n * @param {number} [config.intervalSec] Push interval (seconds)\n * @param {boolean} [config.removeOldMetrics] Clear old series on shutdown where supported\n * @param {function} [config.startupValidation] Run before first push\n * @param {boolean} [config.disablePushgateway] Skip POST to VM-agent\n * @param {string} [config.redisProcessTypeForKeys] Segment in Redis keys for HTTP hashes (default **`web`**). Optional; only if writers use a non-`web` segment.\n * @param {number} [config.ttlSec] Passed to {@link HttpMetricsRedisStore} (should match writers)\n */\n constructor(config = {}) {\n const { redisClient } = config\n if (redisClient == null) {\n throw new Error('HttpMetricsRedisCollector: redisClient is required')\n }\n\n super({\n ...config,\n blockNodeDefaultMetrics: true,\n })\n\n const keyProcessType = config.redisProcessTypeForKeys || 'web'\n\n this.defaultLabelsWithoutDynoId = {\n app: this.appName,\n process_type: keyProcessType,\n }\n\n this._store = new HttpMetricsRedisStore({\n redisClient,\n appName: this.appName,\n processType: keyProcessType,\n ttlSec: config.ttlSec,\n })\n\n this.createCounter({\n name: 'app_requests_total',\n help: 'Total number of HTTP requests',\n labelNames: this.withDefaultLabelsWithoutDynoId([\n 'method',\n 'route',\n 'status_code',\n ]),\n useLabelsWithoutDynoId: true,\n })\n\n this.createCounter({\n name: 'app_requests_total_duration',\n help: 'Total duration of HTTP requests in milliseconds',\n labelNames: this.withDefaultLabelsWithoutDynoId([\n 'method',\n 'route',\n 'status_code',\n ]),\n useLabelsWithoutDynoId: true,\n })\n }\n\n /**\n * Drains Redis into counters, then runs gauge updates and VM-agent push ({@link BaseMetricsClient#_pushMetrics}).\n * @returns {Promise<void>}\n */\n pushMetrics = async () => {\n if (\n this._store &&\n this.countersFunctions?.app_requests_total &&\n this.countersFunctions?.app_requests_total_duration\n ) {\n await this._store.flushToCounters(\n (labels, value) =>\n this.countersFunctions.app_requests_total(labels, value),\n (labels, value) =>\n this.countersFunctions.app_requests_total_duration(labels, value)\n )\n }\n return this._pushMetrics()\n }\n}\n\nmodule.exports = { HttpMetricsRedisCollector }\n"],"mappings":";;AAAA,MAAM;EAAEA;AAAkB,CAAC,GAAGC,OAAO,CAAC,qBAAqB,CAAC;AAC5D,MAAM;EAAEC;AAAsB,CAAC,GAAGD,OAAO,CAAC,yBAAyB,CAAC;;AAEpE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAME,yBAAyB,SAASH,iBAAiB,CAAC;EACxD;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACEI,WAAWA,CAACC,MAAM,GAAG,CAAC,CAAC,EAAE;IACvB,MAAM;MAAEC;IAAY,CAAC,GAAGD,MAAM;IAC9B,IAAIC,WAAW,IAAI,IAAI,EAAE;MACvB,MAAM,IAAIC,KAAK,CAAC,oDAAoD,CAAC;IACvE;IAEA,KAAK,CAAC;MACJ,GAAGF,MAAM;MACTG,uBAAuB,EAAE;IAC3B,CAAC,CAAC;IAEF,MAAMC,cAAc,GAAGJ,MAAM,CAACK,uBAAuB,IAAI,KAAK;IAE9D,IAAI,CAACC,0BAA0B,GAAG;MAChCC,GAAG,EAAE,IAAI,CAACC,OAAO;MACjBC,YAAY,EAAEL;IAChB,CAAC;IAED,IAAI,CAACM,MAAM,GAAG,IAAIb,qBAAqB,CAAC;MACtCI,WAAW;MACXO,OAAO,EAAE,IAAI,CAACA,OAAO;MACrBG,WAAW,EAAEP,cAAc;MAC3BQ,MAAM,EAAEZ,MAAM,CAACY;IACjB,CAAC,CAAC;IAEF,IAAI,CAACC,aAAa,CAAC;MACjBC,IAAI,EAAE,oBAAoB;MAC1BC,IAAI,EAAE,+BAA+B;MACrCC,UAAU,EAAE,IAAI,CAACC,8BAA8B,CAAC,CAC9C,QAAQ,EACR,OAAO,EACP,aAAa,CACd,CAAC;MACFC,sBAAsB,EAAE;IAC1B,CAAC,CAAC;IAEF,IAAI,CAACL,aAAa,CAAC;MACjBC,IAAI,EAAE,6BAA6B;MACnCC,IAAI,EAAE,iDAAiD;MACvDC,UAAU,EAAE,IAAI,CAACC,8BAA8B,CAAC,CAC9C,QAAQ,EACR,OAAO,EACP,aAAa,CACd,CAAC;MACFC,sBAAsB,EAAE;IAC1B,CAAC,CAAC;EACJ;;EAEA;AACF;AACA;AACA;EACEC,WAAW,GAAG,MAAAA,CAAA,KAAY;IACxB,IACE,IAAI,CAACT,MAAM,IACX,IAAI,CAACU,iBAAiB,EAAEC,kBAAkB,IAC1C,IAAI,CAACD,iBAAiB,EAAEE,2BAA2B,EACnD;MACA,MAAM,IAAI,CAACZ,MAAM,CAACa,eAAe,CAC/B,CAACC,MAAM,EAAEC,KAAK,KACZ,IAAI,CAACL,iBAAiB,CAACC,kBAAkB,CAACG,MAAM,EAAEC,KAAK,CAAC,EAC1D,CAACD,MAAM,EAAEC,KAAK,KACZ,IAAI,CAACL,iBAAiB,CAACE,2BAA2B,CAACE,MAAM,EAAEC,KAAK,CACpE,CAAC;IACH;IACA,OAAO,IAAI,CAACC,YAAY,CAAC,CAAC;EAC5B,CAAC;AACH;AAEAC,MAAM,CAACC,OAAO,GAAG;EAAE9B;AAA0B,CAAC","ignoreList":[]}
@@ -26,16 +26,12 @@ export class HttpMetricsRedisRecorder {
26
26
  * @param {string} params.method
27
27
  * @param {string} params.route
28
28
  * @param {number} params.status_code
29
- * @param {string} [params.appId]
30
- * @param {string} [params.databaseId]
31
29
  * @param {number} params.duration
32
30
  */
33
- trackHttpRequest({ method, route, status_code, appId, databaseId, duration, }: {
31
+ trackHttpRequest({ method, route, status_code, duration }: {
34
32
  method: string;
35
33
  route: string;
36
34
  status_code: number;
37
- appId?: string | undefined;
38
- databaseId?: string | undefined;
39
35
  duration: number;
40
36
  }): void;
41
37
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"httpMetricsRedisRecorder.d.ts","sourceRoot":"","sources":["../../src/metrics/httpMetricsRedisRecorder.js"],"names":[],"mappings":"AAOA;;;;;GAKG;AACH;IACE;;;;;;OAMG;IACH;QAL6C,WAAW,EAA7C,OAAO,OAAO,EAAE,WAAW;QACb,OAAO;QACP,WAAW;QACX,MAAM;OAe9B;IARC,oBAA8B;IAC9B,gBAA8B;IAC9B,8BAKE;IAGJ;;;;;;;;OAQG;IACH;QAP0B,MAAM,EAArB,MAAM;QACS,KAAK,EAApB,MAAM;QACS,WAAW,EAA1B,MAAM;QACU,KAAK;QACL,UAAU;QACX,QAAQ,EAAvB,MAAM;aAiBhB;IAED;;;;;;;OAOG;IACH,kCAJW,OAAO,MAAM,EAAE,eAAe,OAC9B,OAAO,MAAM,EAAE,cAAc,0BAkCvC;CACF"}
1
+ {"version":3,"file":"httpMetricsRedisRecorder.d.ts","sourceRoot":"","sources":["../../src/metrics/httpMetricsRedisRecorder.js"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH;IACE;;;;;;OAMG;IACH;QAL6C,WAAW,EAA7C,OAAO,OAAO,EAAE,WAAW;QACb,OAAO;QACP,WAAW;QACX,MAAM;OAe9B;IARC,oBAA8B;IAC9B,gBAA8B;IAC9B,8BAKE;IAGJ;;;;;;OAMG;IACH;QAL0B,MAAM,EAArB,MAAM;QACS,KAAK,EAApB,MAAM;QACS,WAAW,EAA1B,MAAM;QACS,QAAQ,EAAvB,MAAM;aAIhB;IAED;;;;;;;OAOG;IACH,kCAJW,OAAO,MAAM,EAAE,eAAe,OAC9B,OAAO,MAAM,EAAE,cAAc,0BAsBvC;CACF"}
@@ -1,13 +1,8 @@
1
1
  "use strict";
2
2
 
3
3
  const {
4
- HttpMetricsRedisStore,
5
- httpMetricsTraceLog
4
+ HttpMetricsRedisStore
6
5
  } = require('./httpMetricsRedisStore');
7
- function trunc(s, max = 120) {
8
- const t = String(s);
9
- return t.length > max ? `${t.slice(0, max - 3)}...` : t;
10
- }
11
6
 
12
7
  /**
13
8
  * Records HTTP request aggregates only to Redis (no in-process Prometheus counters on this path).
@@ -48,20 +43,15 @@ class HttpMetricsRedisRecorder {
48
43
  * @param {string} params.method
49
44
  * @param {string} params.route
50
45
  * @param {number} params.status_code
51
- * @param {string} [params.appId]
52
- * @param {string} [params.databaseId]
53
46
  * @param {number} params.duration
54
47
  */
55
48
  trackHttpRequest({
56
49
  method,
57
50
  route,
58
51
  status_code,
59
- appId = '',
60
- databaseId = '',
61
52
  duration
62
53
  }) {
63
- httpMetricsTraceLog(`track_request pid=${process.pid} app=${this.appName} segment=${this.processType} ` + `method=${method} route=${trunc(route)} status=${status_code} durationMs=${duration} ` + `appId=${trunc(appId || '—', 40)} databaseId=${trunc(databaseId || '—', 40)} ` + `(then save_to_redis lines from store)`);
64
- this._store.record(method, route, status_code, appId, databaseId, duration);
54
+ this._store.record(method, route, status_code, duration);
65
55
  }
66
56
 
67
57
  /**
@@ -80,14 +70,10 @@ class HttpMetricsRedisRecorder {
80
70
  const start = Date.now();
81
71
  res.on('finish', () => {
82
72
  const route = req.route?.path || req.path || 'unknown';
83
- const appId = req.params?.appId || req.body?.appId || req.query?.appId || '';
84
- const databaseId = req.params?.databaseId || req.body?.databaseId || req.query?.databaseId || req.params?.datasourceId || req.body?.datasourceId || req.query?.datasourceId || '';
85
73
  this.trackHttpRequest({
86
74
  method: req.method,
87
75
  route,
88
76
  status_code: res.statusCode,
89
- appId,
90
- databaseId,
91
77
  duration: Date.now() - start
92
78
  });
93
79
  });
@@ -1 +1 @@
1
- {"version":3,"file":"httpMetricsRedisRecorder.js","names":["HttpMetricsRedisStore","httpMetricsTraceLog","require","trunc","s","max","t","String","length","slice","HttpMetricsRedisRecorder","constructor","redisClient","appName","processType","ttlSec","Error","resolvedAppName","process","env","BUILD_APP_NAME","_store","trackHttpRequest","method","route","status_code","appId","databaseId","duration","pid","record","trackHttpRequestMiddleware","req","res","next","start","Date","now","on","path","params","body","query","datasourceId","statusCode","module","exports"],"sources":["../../src/metrics/httpMetricsRedisRecorder.js"],"sourcesContent":["const { HttpMetricsRedisStore, httpMetricsTraceLog } = require('./httpMetricsRedisStore')\n\nfunction trunc(s, max = 120) {\n const t = String(s)\n return t.length > max ? `${t.slice(0, max - 3)}...` : t\n}\n\n/**\n * Records HTTP request aggregates only to Redis (no in-process Prometheus counters on this path).\n * Pair with {@link HttpMetricsRedisCollector} on a drain process to flush into counters and push to the VM-agent.\n *\n * @see HttpMetricsRedisStore\n */\nclass HttpMetricsRedisRecorder {\n /**\n * @param {Object} opts\n * @param {import('redis').RedisClient} opts.redisClient **Required.** Injected client (same pattern as {@link RedisMetricsClient}).\n * @param {string} [opts.appName] Optional override; default same as {@link BaseMetricsClient}: `BUILD_APP_NAME` or `unknown-app`.\n * @param {string} [opts.processType] Redis key segment (default **`web`**, same as {@link HttpMetricsRedisCollector}).\n * @param {number} [opts.ttlSec] Redis hash key TTL in seconds (sliding; default 120).\n */\n constructor({ redisClient, appName, processType = 'web', ttlSec } = {}) {\n if (redisClient == null) {\n throw new Error('HttpMetricsRedisRecorder: redisClient is required')\n }\n const resolvedAppName = appName || process.env.BUILD_APP_NAME || 'unknown-app'\n this.processType = processType\n this.appName = resolvedAppName\n this._store = new HttpMetricsRedisStore({\n redisClient,\n appName: resolvedAppName,\n processType,\n ttlSec,\n })\n }\n\n /**\n * @param {Object} params\n * @param {string} params.method\n * @param {string} params.route\n * @param {number} params.status_code\n * @param {string} [params.appId]\n * @param {string} [params.databaseId]\n * @param {number} params.duration\n */\n trackHttpRequest({\n method,\n route,\n status_code,\n appId = '',\n databaseId = '',\n duration,\n }) {\n httpMetricsTraceLog(\n `track_request pid=${process.pid} app=${this.appName} segment=${this.processType} ` +\n `method=${method} route=${trunc(route)} status=${status_code} durationMs=${duration} ` +\n `appId=${trunc(appId || '—', 40)} databaseId=${trunc(databaseId || '—', 40)} ` +\n `(then save_to_redis lines from store)`\n )\n this._store.record(method, route, status_code, appId, databaseId, duration)\n }\n\n /**\n * Express middleware: appends a `finish` listener and writes one aggregate row per request to Redis.\n * Does not check `METRICS_ENABLED`; the app should only mount this when HTTP Redis metrics should run.\n *\n * @param {import('http').IncomingMessage} req\n * @param {import('http').ServerResponse} res\n * @param {function} next\n */\n trackHttpRequestMiddleware = (req, res, next) => {\n if (req.method === 'OPTIONS') {\n next()\n return\n }\n\n const start = Date.now()\n res.on('finish', () => {\n const route = req.route?.path || req.path || 'unknown'\n const appId =\n req.params?.appId || req.body?.appId || req.query?.appId || ''\n const databaseId =\n req.params?.databaseId ||\n req.body?.databaseId ||\n req.query?.databaseId ||\n req.params?.datasourceId ||\n req.body?.datasourceId ||\n req.query?.datasourceId ||\n ''\n\n this.trackHttpRequest({\n method: req.method,\n route,\n status_code: res.statusCode,\n appId,\n databaseId,\n duration: Date.now() - start,\n })\n })\n\n next()\n }\n}\n\nmodule.exports = { HttpMetricsRedisRecorder }\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"httpMetricsRedisRecorder.js","names":["HttpMetricsRedisStore","require","HttpMetricsRedisRecorder","constructor","redisClient","appName","processType","ttlSec","Error","resolvedAppName","process","env","BUILD_APP_NAME","_store","trackHttpRequest","method","route","status_code","duration","record","trackHttpRequestMiddleware","req","res","next","start","Date","now","on","path","statusCode","module","exports"],"sources":["../../src/metrics/httpMetricsRedisRecorder.js"],"sourcesContent":["const { HttpMetricsRedisStore } = require('./httpMetricsRedisStore')\n\n/**\n * Records HTTP request aggregates only to Redis (no in-process Prometheus counters on this path).\n * Pair with {@link HttpMetricsRedisCollector} on a drain process to flush into counters and push to the VM-agent.\n *\n * @see HttpMetricsRedisStore\n */\nclass HttpMetricsRedisRecorder {\n /**\n * @param {Object} opts\n * @param {import('redis').RedisClient} opts.redisClient **Required.** Injected client (same pattern as {@link RedisMetricsClient}).\n * @param {string} [opts.appName] Optional override; default same as {@link BaseMetricsClient}: `BUILD_APP_NAME` or `unknown-app`.\n * @param {string} [opts.processType] Redis key segment (default **`web`**, same as {@link HttpMetricsRedisCollector}).\n * @param {number} [opts.ttlSec] Redis hash key TTL in seconds (sliding; default 120).\n */\n constructor({ redisClient, appName, processType = 'web', ttlSec } = {}) {\n if (redisClient == null) {\n throw new Error('HttpMetricsRedisRecorder: redisClient is required')\n }\n const resolvedAppName = appName || process.env.BUILD_APP_NAME || 'unknown-app'\n this.processType = processType\n this.appName = resolvedAppName\n this._store = new HttpMetricsRedisStore({\n redisClient,\n appName: resolvedAppName,\n processType,\n ttlSec,\n })\n }\n\n /**\n * @param {Object} params\n * @param {string} params.method\n * @param {string} params.route\n * @param {number} params.status_code\n * @param {number} params.duration\n */\n trackHttpRequest({ method, route, status_code, duration }) {\n this._store.record(method, route, status_code, duration)\n }\n\n /**\n * Express middleware: appends a `finish` listener and writes one aggregate row per request to Redis.\n * Does not check `METRICS_ENABLED`; the app should only mount this when HTTP Redis metrics should run.\n *\n * @param {import('http').IncomingMessage} req\n * @param {import('http').ServerResponse} res\n * @param {function} next\n */\n trackHttpRequestMiddleware = (req, res, next) => {\n if (req.method === 'OPTIONS') {\n next()\n return\n }\n\n const start = Date.now()\n res.on('finish', () => {\n const route = req.route?.path || req.path || 'unknown'\n\n this.trackHttpRequest({\n method: req.method,\n route,\n status_code: res.statusCode,\n duration: Date.now() - start,\n })\n })\n\n next()\n }\n}\n\nmodule.exports = { HttpMetricsRedisRecorder }\n"],"mappings":";;AAAA,MAAM;EAAEA;AAAsB,CAAC,GAAGC,OAAO,CAAC,yBAAyB,CAAC;;AAEpE;AACA;AACA;AACA;AACA;AACA;AACA,MAAMC,wBAAwB,CAAC;EAC7B;AACF;AACA;AACA;AACA;AACA;AACA;EACEC,WAAWA,CAAC;IAAEC,WAAW;IAAEC,OAAO;IAAEC,WAAW,GAAG,KAAK;IAAEC;EAAO,CAAC,GAAG,CAAC,CAAC,EAAE;IACtE,IAAIH,WAAW,IAAI,IAAI,EAAE;MACvB,MAAM,IAAII,KAAK,CAAC,mDAAmD,CAAC;IACtE;IACA,MAAMC,eAAe,GAAGJ,OAAO,IAAIK,OAAO,CAACC,GAAG,CAACC,cAAc,IAAI,aAAa;IAC9E,IAAI,CAACN,WAAW,GAAGA,WAAW;IAC9B,IAAI,CAACD,OAAO,GAAGI,eAAe;IAC9B,IAAI,CAACI,MAAM,GAAG,IAAIb,qBAAqB,CAAC;MACtCI,WAAW;MACXC,OAAO,EAAEI,eAAe;MACxBH,WAAW;MACXC;IACF,CAAC,CAAC;EACJ;;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;EACEO,gBAAgBA,CAAC;IAAEC,MAAM;IAAEC,KAAK;IAAEC,WAAW;IAAEC;EAAS,CAAC,EAAE;IACzD,IAAI,CAACL,MAAM,CAACM,MAAM,CAACJ,MAAM,EAAEC,KAAK,EAAEC,WAAW,EAAEC,QAAQ,CAAC;EAC1D;;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;AACA;EACEE,0BAA0B,GAAGA,CAACC,GAAG,EAAEC,GAAG,EAAEC,IAAI,KAAK;IAC/C,IAAIF,GAAG,CAACN,MAAM,KAAK,SAAS,EAAE;MAC5BQ,IAAI,CAAC,CAAC;MACN;IACF;IAEA,MAAMC,KAAK,GAAGC,IAAI,CAACC,GAAG,CAAC,CAAC;IACxBJ,GAAG,CAACK,EAAE,CAAC,QAAQ,EAAE,MAAM;MACrB,MAAMX,KAAK,GAAGK,GAAG,CAACL,KAAK,EAAEY,IAAI,IAAIP,GAAG,CAACO,IAAI,IAAI,SAAS;MAEtD,IAAI,CAACd,gBAAgB,CAAC;QACpBC,MAAM,EAAEM,GAAG,CAACN,MAAM;QAClBC,KAAK;QACLC,WAAW,EAAEK,GAAG,CAACO,UAAU;QAC3BX,QAAQ,EAAEO,IAAI,CAACC,GAAG,CAAC,CAAC,GAAGF;MACzB,CAAC,CAAC;IACJ,CAAC,CAAC;IAEFD,IAAI,CAAC,CAAC;EACR,CAAC;AACH;AAEAO,MAAM,CAACC,OAAO,GAAG;EAAE7B;AAAyB,CAAC","ignoreList":[]}
@@ -2,11 +2,8 @@
2
2
  * Redis HTTP aggregate store. Uses an **injected** client (same pattern as {@link RedisMetricsClient}).
3
3
  * Expects `redis` v3-style API: `multi().hincrby().expire().exec(cb)`, `eval`.
4
4
  *
5
- * **Structure:** `countKey` / `durKey` are each **one Redis hash**. The hash has **many fields** — not
6
- * one bucket for all traffic. Each **field name** encodes one label group `(method, route, status_code,
7
- * appId, databaseId, pid)`. The **value** in `:count` is **HINCRBY 1** per request for that group; in `:dur`
8
- * it is **HINCRBY** of that request’s **duration ms** (so many requests → summed ms per same field).
9
- * Different routes → different hash fields. Drain runs atomic Lua (HGETALL + DEL) per tick.
5
+ * **Structure:** `countKey` / `durKey` are each **one Redis hash**. Each **field name** encodes
6
+ * `(method, route, status_code)`. Values: `:count` is HINCRBY 1 per request; `:dur` is summed ms.
10
7
  */
11
8
  export class HttpMetricsRedisStore {
12
9
  /**
@@ -35,11 +32,9 @@ export class HttpMetricsRedisStore {
35
32
  * @param {string} method
36
33
  * @param {string} route
37
34
  * @param {number} statusCode
38
- * @param {string} appId
39
- * @param {string} databaseId
40
35
  * @param {number} durationMs
41
36
  */
42
- record(method: string, route: string, statusCode: number, appId: string, databaseId: string, durationMs: number): void;
37
+ record(method: string, route: string, statusCode: number, durationMs: number): void;
43
38
  /**
44
39
  * @param {(labels: Object, value: number) => void} applyCount
45
40
  * @param {(labels: Object, value: number) => void} applyDuration
@@ -51,31 +46,17 @@ export class HttpMetricsRedisStore {
51
46
  * @param {string} method
52
47
  * @param {string} route
53
48
  * @param {number} statusCode
54
- * @param {string} appId
55
- * @param {string} databaseId
56
- * @param {string} pid - OS process id (string); distinguishes cluster workers / processes in one Redis hash
57
49
  * @returns {string}
58
50
  */
59
- export function buildFieldKey(method: string, route: string, statusCode: number, appId: string, databaseId: string, pid: string): string;
51
+ export function buildFieldKey(method: string, route: string, statusCode: number): string;
60
52
  /**
61
53
  * Record separator for hash fields (avoids collisions when route contains "|").
62
54
  * @type {string}
63
55
  */
64
56
  export const FIELD_SEP: string;
65
- /**
66
- * @returns {boolean} Whether the `redis` npm package is resolvable (optional peer).
67
- */
68
- export function isRedisPeerInstalled(): boolean;
69
57
  /**
70
58
  * Default Redis key TTL in seconds (sliding: refreshed on each `record` write).
71
59
  * @type {number}
72
60
  */
73
61
  export const DEFAULT_HTTP_METRICS_REDIS_TTL_SEC: number;
74
- /**
75
- * Trace line for SAVE (web → Redis) and GET/drain (collector ← Redis).
76
- * Single `console.log` only: many hosts (e.g. Heroku) merge stdout+stderr into one stream and would
77
- * show duplicate lines if we also wrote the same text to stderr.
78
- * @param {string} body - Line body after `[http-metrics-redis]` prefix.
79
- */
80
- export function httpMetricsTraceLog(body: string): void;
81
62
  //# sourceMappingURL=httpMetricsRedisStore.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"httpMetricsRedisStore.d.ts","sourceRoot":"","sources":["../../src/metrics/httpMetricsRedisStore.js"],"names":[],"mappings":"AA8FA;;;;;;;;;GASG;AACH;IACE;;;;;;OAMG;IACH;QAL6C,WAAW,EAA7C,OAAO,OAAO,EAAE,WAAW;QACd,OAAO,EAApB,MAAM;QACO,WAAW,EAAxB,MAAM;QACQ,MAAM;OAgB9B;IAVC,qCAA0B;IAC1B,eAGwC;IAIxC,iBAAiD;IACjD,eAA6C;IAG/C;;;OAGG;IACH,sBAEC;IAED;;;;;;;OAOG;IACH,eAPW,MAAM,SACN,MAAM,cACN,MAAM,SACN,MAAM,cACN,MAAM,cACN,MAAM,QAoChB;IAED;;;;OAIG;IACH,qCAJoB,MAAM,SAAS,MAAM,KAAK,IAAI,0BAC9B,MAAM,SAAS,MAAM,KAAK,IAAI,GACrC,QAAQ,OAAO,CAAC,CAkG5B;CACF;AAtND;;;;;;;;GAQG;AACH,sCARW,MAAM,SACN,MAAM,cACN,MAAM,SACN,MAAM,cACN,MAAM,OACN,MAAM,GACJ,MAAM,CAMlB;AAjFD;;;GAGG;AACH,wBAFU,MAAM,CAEQ;AAoDxB;;GAEG;AACH,wCAFa,OAAO,CASnB;AA5DD;;;GAGG;AACH,iDAFU,MAAM,CAE8B;AA0B9C;;;;;GAKG;AACH,0CAFW,MAAM,QAKhB"}
1
+ {"version":3,"file":"httpMetricsRedisStore.d.ts","sourceRoot":"","sources":["../../src/metrics/httpMetricsRedisStore.js"],"names":[],"mappings":"AA0CA;;;;;;GAMG;AACH;IACE;;;;;;OAMG;IACH;QAL6C,WAAW,EAA7C,OAAO,OAAO,EAAE,WAAW;QACd,OAAO,EAApB,MAAM;QACO,WAAW,EAAxB,MAAM;QACQ,MAAM;OAgB9B;IAVC,qCAA0B;IAC1B,eAGwC;IAIxC,iBAAiD;IACjD,eAA6C;IAG/C;;;OAGG;IACH,sBAEC;IAED;;;;;OAKG;IACH,eALW,MAAM,SACN,MAAM,cACN,MAAM,cACN,MAAM,QAqBhB;IAED;;;;OAIG;IACH,qCAJoB,MAAM,SAAS,MAAM,KAAK,IAAI,0BAC9B,MAAM,SAAS,MAAM,KAAK,IAAI,GACrC,QAAQ,OAAO,CAAC,CAoE5B;CACF;AA/JD;;;;;GAKG;AACH,sCALW,MAAM,SACN,MAAM,cACN,MAAM,GACJ,MAAM,CAIlB;AA7BD;;;GAGG;AACH,wBAFU,MAAM,CAEQ;AAExB;;;GAGG;AACH,iDAFU,MAAM,CAE8B"}