@adalo/metrics 0.0.0-staging.32 → 0.0.0-staging.33

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.
@@ -129,34 +129,54 @@ describe('HttpMetricsRedisCollector', () => {
129
129
  })
130
130
 
131
131
  it('uses METRICS_HTTP_INSTANCE_ROTATE_MS when set (ms)', async () => {
132
- process.env.METRICS_HTTP_INSTANCE_ROTATE_MS = '500'
133
- const redis = createRedisV3Mock()
134
- const field = buildFieldKey('GET', '/x', 200)
135
- redis.eval.mockImplementation((lua, numKeys, k1, k2, cb) => {
136
- cb(null, [
137
- [field, '1'],
138
- [field, '0'],
139
- ])
132
+ const savedRotate = process.env.METRICS_HTTP_INSTANCE_ROTATE_MS
133
+ // Rotation ms is read at module load; isolateModules loads collector after env is set.
134
+ let IsolatedCollector
135
+ let isolatedRotateMs
136
+ jest.isolateModules(() => {
137
+ process.env.METRICS_HTTP_INSTANCE_ROTATE_MS = '500'
138
+ process.env.METRICS_DISABLE_PUSHGATEWAY = 'true'
139
+ const m = require('../src/metrics/httpMetricsRedisCollector')
140
+ IsolatedCollector = m.HttpMetricsRedisCollector
141
+ isolatedRotateMs = m.HTTP_PUSH_INSTANCE_ROTATE_MS
140
142
  })
141
-
142
- const collector = new HttpMetricsRedisCollector({
143
- redisClient: redis,
144
- appName: 'test-app',
145
- processType: 'http-metrics',
146
- redisProcessTypeForKeys: 'web',
147
- disablePushgateway: true,
148
- })
149
-
150
- const clearSpy = jest.spyOn(collector, 'clearAllCounters')
151
- await collector.pushMetrics()
152
- collector._httpPushInstanceStartedAt = Date.now() - 501
153
- collector._httpRotationLastCheck = 0
154
- redis.eval.mockImplementation((lua, numKeys, k1, k2, cb) => {
155
- cb(null, [[], []])
156
- })
157
- await collector.pushMetrics()
158
- expect(clearSpy).toHaveBeenCalled()
159
- clearSpy.mockRestore()
143
+ try {
144
+ expect(isolatedRotateMs).toBe(500)
145
+
146
+ const redis = createRedisV3Mock()
147
+ const field = buildFieldKey('GET', '/x', 200)
148
+ redis.eval.mockImplementation((lua, numKeys, k1, k2, cb) => {
149
+ cb(null, [
150
+ [field, '1'],
151
+ [field, '0'],
152
+ ])
153
+ })
154
+
155
+ const collector = new IsolatedCollector({
156
+ redisClient: redis,
157
+ appName: 'test-app',
158
+ processType: 'http-metrics',
159
+ redisProcessTypeForKeys: 'web',
160
+ disablePushgateway: true,
161
+ })
162
+
163
+ const clearSpy = jest.spyOn(collector, 'clearAllCounters')
164
+ await collector.pushMetrics()
165
+ collector._httpPushInstanceStartedAt = Date.now() - 501
166
+ collector._httpRotationLastCheck = 0
167
+ redis.eval.mockImplementation((lua, numKeys, k1, k2, cb) => {
168
+ cb(null, [[], []])
169
+ })
170
+ await collector.pushMetrics()
171
+ expect(clearSpy).toHaveBeenCalled()
172
+ clearSpy.mockRestore()
173
+ } finally {
174
+ if (savedRotate === undefined) {
175
+ delete process.env.METRICS_HTTP_INSTANCE_ROTATE_MS
176
+ } else {
177
+ process.env.METRICS_HTTP_INSTANCE_ROTATE_MS = savedRotate
178
+ }
179
+ }
160
180
  })
161
181
 
162
182
  it('primes new label sets with 0 then applies count (extra push only when labels are new)', async () => {
@@ -16,7 +16,7 @@ export class HttpMetricsRedisCollector extends BaseMetricsClient {
16
16
  * @param {string} [config.redisProcessTypeForKeys] Segment in Redis keys for HTTP hashes (default `web`).
17
17
  * @param {number} [config.ttlSec] Redis hash TTL in seconds; should match writers’ {@link HttpMetricsRedisStore}.
18
18
  *
19
- * Env `METRICS_HTTP_INSTANCE_ROTATE_MS`: optional positive integer (ms) for `http_instance_id` rotation; default is {@link HTTP_PUSH_INSTANCE_ROTATE_MS}.
19
+ * `http_instance_id` rotation interval: {@link HTTP_PUSH_INSTANCE_ROTATE_MS} (set at process start from env, see constant JSDoc).
20
20
  */
21
21
  constructor(config?: {
22
22
  redisClient: import('redis').RedisClient;
@@ -46,7 +46,10 @@ export class HttpMetricsRedisCollector extends BaseMetricsClient {
46
46
  /** @param {number} now */
47
47
  _maybeRotateHttpPushInstance(now?: number): void;
48
48
  }
49
- /** Default interval before a new `http_instance_id` and in-memory counter reset (overridable by env). */
49
+ /**
50
+ * Ms between `http_instance_id` rotation + in-memory counter reset in {@link HttpMetricsRedisCollector}.
51
+ * From `process.env.METRICS_HTTP_INSTANCE_ROTATE_MS` if positive integer, else 7 days. Resolved at module load.
52
+ */
50
53
  export const HTTP_PUSH_INSTANCE_ROTATE_MS: number;
51
54
  import { BaseMetricsClient } from "./baseMetricsClient";
52
55
  import { HttpMetricsRedisStore } from "./httpMetricsRedisStore";
@@ -1 +1 @@
1
- {"version":3,"file":"httpMetricsRedisCollector.d.ts","sourceRoot":"","sources":["../../src/metrics/httpMetricsRedisCollector.js"],"names":[],"mappings":"AAqBA;IACE;;;;;;;;;;;;;;;;;;OAkBG;IACH;qBAjBW,OAAO,OAAO,EAAE,WAAW;;;;;;;;;;;;;;mBAiErC;IAnCC,8BAKE;IAGF,yBAAmC;IACnC,mCAAqC;IACrC,+BAA+B;IAE/B,0BAA0B;IAC1B,wBADW,IAAI,MAAM,CAAC,CACiB;IAwBzC,wCAKC;IAED,0BAA0B;IAC1B,6BADY,MAAM,QAMjB;IAED,0BAA0B;IAC1B,mCADY,MAAM,QAajB;CAiDF;AAjKD,yGAAyG;AACzG,kDAA4D"}
1
+ {"version":3,"file":"httpMetricsRedisCollector.d.ts","sourceRoot":"","sources":["../../src/metrics/httpMetricsRedisCollector.js"],"names":[],"mappings":"AAgBA;IACE;;;;;;;;;;;;;;;;;;OAkBG;IACH;qBAjBW,OAAO,OAAO,EAAE,WAAW;;;;;;;;;;;;;;mBAiErC;IAnCC,8BAKE;IAGF,yBAAmC;IACnC,mCAAqC;IACrC,+BAA+B;IAE/B,0BAA0B;IAC1B,wBADW,IAAI,MAAM,CAAC,CACiB;IAwBzC,wCAKC;IAED,0BAA0B;IAC1B,6BADY,MAAM,QAMjB;IAED,0BAA0B;IAC1B,mCADY,MAAM,QAUjB;CAiDF;AAzJD;;;GAGG;AACH,kDAEyB"}
@@ -11,13 +11,12 @@ const {
11
11
  buildFieldKey
12
12
  } = require('./httpMetricsRedisStore');
13
13
 
14
- /** Default interval before a new `http_instance_id` and in-memory counter reset (overridable by env). */
15
- const HTTP_PUSH_INSTANCE_ROTATE_MS = 7 * 24 * 60 * 60 * 1000;
14
+ /**
15
+ * Ms between `http_instance_id` rotation + in-memory counter reset in {@link HttpMetricsRedisCollector}.
16
+ * From `process.env.METRICS_HTTP_INSTANCE_ROTATE_MS` if positive integer, else 7 days. Resolved at module load.
17
+ */
18
+ const HTTP_PUSH_INSTANCE_ROTATE_MS = parseInt(process.env.METRICS_HTTP_INSTANCE_ROTATE_MS || '', 10) || 7 * 24 * 60 * 60 * 1000;
16
19
  const HTTP_PUSH_ROTATION_CHECK_THROTTLE_MS = 60 * 1000;
17
- function effectiveHttpPushInstanceRotateMs() {
18
- const envMs = parseInt(process.env.METRICS_HTTP_INSTANCE_ROTATE_MS || '', 10);
19
- return Number.isFinite(envMs) && envMs > 0 ? envMs : HTTP_PUSH_INSTANCE_ROTATE_MS;
20
- }
21
20
  class HttpMetricsRedisCollector extends BaseMetricsClient {
22
21
  /**
23
22
  * @param {Object} [config]
@@ -36,7 +35,7 @@ class HttpMetricsRedisCollector extends BaseMetricsClient {
36
35
  * @param {string} [config.redisProcessTypeForKeys] Segment in Redis keys for HTTP hashes (default `web`).
37
36
  * @param {number} [config.ttlSec] Redis hash TTL in seconds; should match writers’ {@link HttpMetricsRedisStore}.
38
37
  *
39
- * Env `METRICS_HTTP_INSTANCE_ROTATE_MS`: optional positive integer (ms) for `http_instance_id` rotation; default is {@link HTTP_PUSH_INSTANCE_ROTATE_MS}.
38
+ * `http_instance_id` rotation interval: {@link HTTP_PUSH_INSTANCE_ROTATE_MS} (set at process start from env, see constant JSDoc).
40
39
  */
41
40
  constructor(config = {}) {
42
41
  const {
@@ -98,7 +97,7 @@ class HttpMetricsRedisCollector extends BaseMetricsClient {
98
97
  return;
99
98
  }
100
99
  this._httpRotationLastCheck = now;
101
- if (now - this._httpPushInstanceStartedAt >= effectiveHttpPushInstanceRotateMs()) {
100
+ if (now - this._httpPushInstanceStartedAt >= HTTP_PUSH_INSTANCE_ROTATE_MS) {
102
101
  this._rotateHttpPushInstance(now);
103
102
  }
104
103
  }
@@ -1 +1 @@
1
- {"version":3,"file":"httpMetricsRedisCollector.js","names":["v4","uuidv4","require","BaseMetricsClient","HttpMetricsRedisStore","buildFieldKey","HTTP_PUSH_INSTANCE_ROTATE_MS","HTTP_PUSH_ROTATION_CHECK_THROTTLE_MS","effectiveHttpPushInstanceRotateMs","envMs","parseInt","process","env","METRICS_HTTP_INSTANCE_ROTATE_MS","Number","isFinite","HttpMetricsRedisCollector","constructor","config","redisClient","Error","blockNodeDefaultMetrics","keyProcessType","redisProcessTypeForKeys","_store","appName","processType","ttlSec","now","Date","_httpPushInstanceId","_httpPushInstanceStartedAt","_httpRotationLastCheck","_httpCounterPrimedKeys","Set","httpLabelNames","withDefaultLabelsWithoutDynoId","createCounter","name","help","labelNames","useLabelsWithoutDynoId","_labelsForHttpPush","rowLabels","http_instance_id","_rotateHttpPushInstance","clearAllCounters","clear","_maybeRotateHttpPushInstance","pushMetrics","countersFunctions","app_requests_total","app_requests_total_duration","ok","rows","drainRows","length","applyCount","labels","value","applyDur","primedAny","row","key","method","route","status_code","L","has","add","gatewayPush","count","dur","_pushMetrics","module","exports"],"sources":["../../src/metrics/httpMetricsRedisCollector.js"],"sourcesContent":["const { v4: uuidv4 } = require('uuid')\nconst { BaseMetricsClient } = require('./baseMetricsClient')\nconst {\n HttpMetricsRedisStore,\n buildFieldKey,\n} = require('./httpMetricsRedisStore')\n\n/** Default interval before a new `http_instance_id` and in-memory counter reset (overridable by env). */\nconst HTTP_PUSH_INSTANCE_ROTATE_MS = 7 * 24 * 60 * 60 * 1000\nconst HTTP_PUSH_ROTATION_CHECK_THROTTLE_MS = 60 * 1000\n\nfunction effectiveHttpPushInstanceRotateMs() {\n const envMs = parseInt(\n process.env.METRICS_HTTP_INSTANCE_ROTATE_MS || '',\n 10\n )\n return Number.isFinite(envMs) && envMs > 0\n ? envMs\n : HTTP_PUSH_INSTANCE_ROTATE_MS\n}\n\nclass HttpMetricsRedisCollector extends BaseMetricsClient {\n /**\n * @param {Object} [config]\n * @param {import('redis').RedisClient} config.redisClient\n * @param {string} [config.appName] Name of the application (defaults per {@link BaseMetricsClient})\n * @param {string} [config.dynoId] Dyno/instance ID (defaults per {@link BaseMetricsClient})\n * @param {string} [config.processType] Process type for labels (defaults per {@link BaseMetricsClient})\n * @param {boolean} [config.enabled] Enable metrics collection (defaults per {@link BaseMetricsClient})\n * @param {boolean} [config.logValues] Log metrics values to console (defaults per {@link BaseMetricsClient})\n * @param {string} [config.pushgatewayUrl] VM-agent import URL (defaults per {@link BaseMetricsClient})\n * @param {string} [config.pushgatewaySecret] Basic auth secret, Base64 (defaults per {@link BaseMetricsClient})\n * @param {number} [config.intervalSec] Push interval in seconds (defaults per {@link BaseMetricsClient})\n * @param {boolean} [config.removeOldMetrics] Enable clearing old metrics (defaults per {@link BaseMetricsClient})\n * @param {function} [config.startupValidation] Run before first push (defaults per {@link BaseMetricsClient})\n * @param {boolean} [config.disablePushgateway] Skip POST to VM-agent (defaults per {@link BaseMetricsClient})\n * @param {string} [config.redisProcessTypeForKeys] Segment in Redis keys for HTTP hashes (default `web`).\n * @param {number} [config.ttlSec] Redis hash TTL in seconds; should match writers’ {@link HttpMetricsRedisStore}.\n *\n * Env `METRICS_HTTP_INSTANCE_ROTATE_MS`: optional positive integer (ms) for `http_instance_id` rotation; default is {@link HTTP_PUSH_INSTANCE_ROTATE_MS}.\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._store = new HttpMetricsRedisStore({\n redisClient,\n appName: this.appName,\n processType: keyProcessType,\n ttlSec: config.ttlSec,\n })\n\n const now = Date.now()\n this._httpPushInstanceId = uuidv4()\n this._httpPushInstanceStartedAt = now\n this._httpRotationLastCheck = 0\n\n /** @type {Set<string>} */\n this._httpCounterPrimedKeys = new Set()\n\n const httpLabelNames = this.withDefaultLabelsWithoutDynoId([\n 'method',\n 'route',\n 'status_code',\n 'http_instance_id',\n ])\n\n this.createCounter({\n name: 'app_requests_total',\n help: 'Total number of HTTP requests',\n labelNames: httpLabelNames,\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: httpLabelNames,\n useLabelsWithoutDynoId: true,\n })\n }\n\n _labelsForHttpPush(rowLabels) {\n return {\n ...rowLabels,\n http_instance_id: this._httpPushInstanceId,\n }\n }\n\n /** @param {number} now */\n _rotateHttpPushInstance(now) {\n this._httpPushInstanceId = uuidv4()\n this._httpPushInstanceStartedAt = now\n this.clearAllCounters()\n this._httpCounterPrimedKeys.clear()\n }\n\n /** @param {number} now */\n _maybeRotateHttpPushInstance(now = Date.now()) {\n if (now - this._httpRotationLastCheck < HTTP_PUSH_ROTATION_CHECK_THROTTLE_MS) {\n return\n }\n this._httpRotationLastCheck = now\n\n if (\n now - this._httpPushInstanceStartedAt >=\n effectiveHttpPushInstanceRotateMs()\n ) {\n this._rotateHttpPushInstance(now)\n }\n }\n\n /**\n * @returns {Promise<void>}\n */\n pushMetrics = async () => {\n this._maybeRotateHttpPushInstance()\n\n if (\n this._store &&\n this.countersFunctions?.app_requests_total &&\n this.countersFunctions?.app_requests_total_duration\n ) {\n const { ok, rows } = await this._store.drainRows()\n if (ok && rows.length > 0) {\n const applyCount = (labels, value) =>\n this.countersFunctions.app_requests_total(labels, value)\n const applyDur = (labels, value) =>\n this.countersFunctions.app_requests_total_duration(labels, value)\n\n let primedAny = false\n for (const row of rows) {\n const key = buildFieldKey(\n row.labels.method,\n row.labels.route,\n row.labels.status_code\n )\n const L = this._labelsForHttpPush(row.labels)\n if (!this._httpCounterPrimedKeys.has(key)) {\n this._httpCounterPrimedKeys.add(key)\n applyCount(L, 0)\n applyDur(L, 0)\n primedAny = true\n }\n }\n if (primedAny) {\n await this.gatewayPush()\n }\n for (const row of rows) {\n const L = this._labelsForHttpPush(row.labels)\n applyCount(L, row.count)\n if (row.dur > 0) {\n applyDur(L, row.dur)\n }\n }\n }\n }\n return this._pushMetrics()\n }\n}\n\nmodule.exports = {\n HttpMetricsRedisCollector,\n HTTP_PUSH_INSTANCE_ROTATE_MS,\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"httpMetricsRedisCollector.js","names":["v4","uuidv4","require","BaseMetricsClient","HttpMetricsRedisStore","buildFieldKey","HTTP_PUSH_INSTANCE_ROTATE_MS","parseInt","process","env","METRICS_HTTP_INSTANCE_ROTATE_MS","HTTP_PUSH_ROTATION_CHECK_THROTTLE_MS","HttpMetricsRedisCollector","constructor","config","redisClient","Error","blockNodeDefaultMetrics","keyProcessType","redisProcessTypeForKeys","_store","appName","processType","ttlSec","now","Date","_httpPushInstanceId","_httpPushInstanceStartedAt","_httpRotationLastCheck","_httpCounterPrimedKeys","Set","httpLabelNames","withDefaultLabelsWithoutDynoId","createCounter","name","help","labelNames","useLabelsWithoutDynoId","_labelsForHttpPush","rowLabels","http_instance_id","_rotateHttpPushInstance","clearAllCounters","clear","_maybeRotateHttpPushInstance","pushMetrics","countersFunctions","app_requests_total","app_requests_total_duration","ok","rows","drainRows","length","applyCount","labels","value","applyDur","primedAny","row","key","method","route","status_code","L","has","add","gatewayPush","count","dur","_pushMetrics","module","exports"],"sources":["../../src/metrics/httpMetricsRedisCollector.js"],"sourcesContent":["const { v4: uuidv4 } = require('uuid')\nconst { BaseMetricsClient } = require('./baseMetricsClient')\nconst {\n HttpMetricsRedisStore,\n buildFieldKey,\n} = require('./httpMetricsRedisStore')\n\n/**\n * Ms between `http_instance_id` rotation + in-memory counter reset in {@link HttpMetricsRedisCollector}.\n * From `process.env.METRICS_HTTP_INSTANCE_ROTATE_MS` if positive integer, else 7 days. Resolved at module load.\n */\nconst HTTP_PUSH_INSTANCE_ROTATE_MS =\n parseInt(process.env.METRICS_HTTP_INSTANCE_ROTATE_MS || '', 10) ||\n 7 * 24 * 60 * 60 * 1000\nconst HTTP_PUSH_ROTATION_CHECK_THROTTLE_MS = 60 * 1000\n\nclass HttpMetricsRedisCollector extends BaseMetricsClient {\n /**\n * @param {Object} [config]\n * @param {import('redis').RedisClient} config.redisClient\n * @param {string} [config.appName] Name of the application (defaults per {@link BaseMetricsClient})\n * @param {string} [config.dynoId] Dyno/instance ID (defaults per {@link BaseMetricsClient})\n * @param {string} [config.processType] Process type for labels (defaults per {@link BaseMetricsClient})\n * @param {boolean} [config.enabled] Enable metrics collection (defaults per {@link BaseMetricsClient})\n * @param {boolean} [config.logValues] Log metrics values to console (defaults per {@link BaseMetricsClient})\n * @param {string} [config.pushgatewayUrl] VM-agent import URL (defaults per {@link BaseMetricsClient})\n * @param {string} [config.pushgatewaySecret] Basic auth secret, Base64 (defaults per {@link BaseMetricsClient})\n * @param {number} [config.intervalSec] Push interval in seconds (defaults per {@link BaseMetricsClient})\n * @param {boolean} [config.removeOldMetrics] Enable clearing old metrics (defaults per {@link BaseMetricsClient})\n * @param {function} [config.startupValidation] Run before first push (defaults per {@link BaseMetricsClient})\n * @param {boolean} [config.disablePushgateway] Skip POST to VM-agent (defaults per {@link BaseMetricsClient})\n * @param {string} [config.redisProcessTypeForKeys] Segment in Redis keys for HTTP hashes (default `web`).\n * @param {number} [config.ttlSec] Redis hash TTL in seconds; should match writers’ {@link HttpMetricsRedisStore}.\n *\n * `http_instance_id` rotation interval: {@link HTTP_PUSH_INSTANCE_ROTATE_MS} (set at process start from env, see constant JSDoc).\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._store = new HttpMetricsRedisStore({\n redisClient,\n appName: this.appName,\n processType: keyProcessType,\n ttlSec: config.ttlSec,\n })\n\n const now = Date.now()\n this._httpPushInstanceId = uuidv4()\n this._httpPushInstanceStartedAt = now\n this._httpRotationLastCheck = 0\n\n /** @type {Set<string>} */\n this._httpCounterPrimedKeys = new Set()\n\n const httpLabelNames = this.withDefaultLabelsWithoutDynoId([\n 'method',\n 'route',\n 'status_code',\n 'http_instance_id',\n ])\n\n this.createCounter({\n name: 'app_requests_total',\n help: 'Total number of HTTP requests',\n labelNames: httpLabelNames,\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: httpLabelNames,\n useLabelsWithoutDynoId: true,\n })\n }\n\n _labelsForHttpPush(rowLabels) {\n return {\n ...rowLabels,\n http_instance_id: this._httpPushInstanceId,\n }\n }\n\n /** @param {number} now */\n _rotateHttpPushInstance(now) {\n this._httpPushInstanceId = uuidv4()\n this._httpPushInstanceStartedAt = now\n this.clearAllCounters()\n this._httpCounterPrimedKeys.clear()\n }\n\n /** @param {number} now */\n _maybeRotateHttpPushInstance(now = Date.now()) {\n if (now - this._httpRotationLastCheck < HTTP_PUSH_ROTATION_CHECK_THROTTLE_MS) {\n return\n }\n this._httpRotationLastCheck = now\n\n if (now - this._httpPushInstanceStartedAt >= HTTP_PUSH_INSTANCE_ROTATE_MS) {\n this._rotateHttpPushInstance(now)\n }\n }\n\n /**\n * @returns {Promise<void>}\n */\n pushMetrics = async () => {\n this._maybeRotateHttpPushInstance()\n\n if (\n this._store &&\n this.countersFunctions?.app_requests_total &&\n this.countersFunctions?.app_requests_total_duration\n ) {\n const { ok, rows } = await this._store.drainRows()\n if (ok && rows.length > 0) {\n const applyCount = (labels, value) =>\n this.countersFunctions.app_requests_total(labels, value)\n const applyDur = (labels, value) =>\n this.countersFunctions.app_requests_total_duration(labels, value)\n\n let primedAny = false\n for (const row of rows) {\n const key = buildFieldKey(\n row.labels.method,\n row.labels.route,\n row.labels.status_code\n )\n const L = this._labelsForHttpPush(row.labels)\n if (!this._httpCounterPrimedKeys.has(key)) {\n this._httpCounterPrimedKeys.add(key)\n applyCount(L, 0)\n applyDur(L, 0)\n primedAny = true\n }\n }\n if (primedAny) {\n await this.gatewayPush()\n }\n for (const row of rows) {\n const L = this._labelsForHttpPush(row.labels)\n applyCount(L, row.count)\n if (row.dur > 0) {\n applyDur(L, row.dur)\n }\n }\n }\n }\n return this._pushMetrics()\n }\n}\n\nmodule.exports = {\n HttpMetricsRedisCollector,\n HTTP_PUSH_INSTANCE_ROTATE_MS,\n}\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.0.0-staging.32",
3
+ "version": "0.0.0-staging.33",
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",
@@ -27,7 +27,7 @@
27
27
  "bee-queue": "^1.2.2",
28
28
  "dotenv": "^8.2.0",
29
29
  "mysql2": "^3.11.0",
30
- "pg": "^8.16.3",
30
+ "pg": "8.11.3",
31
31
  "prom-client": "^15.1.3",
32
32
  "uuid": "9.0.1"
33
33
  },
@@ -5,20 +5,15 @@ const {
5
5
  buildFieldKey,
6
6
  } = require('./httpMetricsRedisStore')
7
7
 
8
- /** Default interval before a new `http_instance_id` and in-memory counter reset (overridable by env). */
9
- const HTTP_PUSH_INSTANCE_ROTATE_MS = 7 * 24 * 60 * 60 * 1000
8
+ /**
9
+ * Ms between `http_instance_id` rotation + in-memory counter reset in {@link HttpMetricsRedisCollector}.
10
+ * From `process.env.METRICS_HTTP_INSTANCE_ROTATE_MS` if positive integer, else 7 days. Resolved at module load.
11
+ */
12
+ const HTTP_PUSH_INSTANCE_ROTATE_MS =
13
+ parseInt(process.env.METRICS_HTTP_INSTANCE_ROTATE_MS || '', 10) ||
14
+ 7 * 24 * 60 * 60 * 1000
10
15
  const HTTP_PUSH_ROTATION_CHECK_THROTTLE_MS = 60 * 1000
11
16
 
12
- function effectiveHttpPushInstanceRotateMs() {
13
- const envMs = parseInt(
14
- process.env.METRICS_HTTP_INSTANCE_ROTATE_MS || '',
15
- 10
16
- )
17
- return Number.isFinite(envMs) && envMs > 0
18
- ? envMs
19
- : HTTP_PUSH_INSTANCE_ROTATE_MS
20
- }
21
-
22
17
  class HttpMetricsRedisCollector extends BaseMetricsClient {
23
18
  /**
24
19
  * @param {Object} [config]
@@ -37,7 +32,7 @@ class HttpMetricsRedisCollector extends BaseMetricsClient {
37
32
  * @param {string} [config.redisProcessTypeForKeys] Segment in Redis keys for HTTP hashes (default `web`).
38
33
  * @param {number} [config.ttlSec] Redis hash TTL in seconds; should match writers’ {@link HttpMetricsRedisStore}.
39
34
  *
40
- * Env `METRICS_HTTP_INSTANCE_ROTATE_MS`: optional positive integer (ms) for `http_instance_id` rotation; default is {@link HTTP_PUSH_INSTANCE_ROTATE_MS}.
35
+ * `http_instance_id` rotation interval: {@link HTTP_PUSH_INSTANCE_ROTATE_MS} (set at process start from env, see constant JSDoc).
41
36
  */
42
37
  constructor(config = {}) {
43
38
  const { redisClient } = config
@@ -111,10 +106,7 @@ class HttpMetricsRedisCollector extends BaseMetricsClient {
111
106
  }
112
107
  this._httpRotationLastCheck = now
113
108
 
114
- if (
115
- now - this._httpPushInstanceStartedAt >=
116
- effectiveHttpPushInstanceRotateMs()
117
- ) {
109
+ if (now - this._httpPushInstanceStartedAt >= HTTP_PUSH_INSTANCE_ROTATE_MS) {
118
110
  this._rotateHttpPushInstance(now)
119
111
  }
120
112
  }