@adalo/metrics 0.1.174 → 0.1.176

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 (30) hide show
  1. package/README.md +13 -14
  2. package/__tests__/httpMetricsRedisCollector.test.js +105 -1
  3. package/__tests__/httpMetricsRedisRecorder.test.js +22 -3
  4. package/lib/metrics/baseMetricsClient.d.ts +1 -1
  5. package/lib/metrics/baseMetricsClient.d.ts.map +1 -1
  6. package/lib/metrics/baseMetricsClient.js +1 -1
  7. package/lib/metrics/baseMetricsClient.js.map +1 -1
  8. package/lib/metrics/httpMetricsRedisCollector.d.ts +30 -24
  9. package/lib/metrics/httpMetricsRedisCollector.d.ts.map +1 -1
  10. package/lib/metrics/httpMetricsRedisCollector.js +65 -34
  11. package/lib/metrics/httpMetricsRedisCollector.js.map +1 -1
  12. package/lib/metrics/httpMetricsRedisRecorder.d.ts +9 -17
  13. package/lib/metrics/httpMetricsRedisRecorder.d.ts.map +1 -1
  14. package/lib/metrics/httpMetricsRedisRecorder.js +18 -16
  15. package/lib/metrics/httpMetricsRedisRecorder.js.map +1 -1
  16. package/lib/metrics/metricsClient.d.ts +1 -63
  17. package/lib/metrics/metricsClient.d.ts.map +1 -1
  18. package/lib/metrics/metricsClient.js +1 -77
  19. package/lib/metrics/metricsClient.js.map +1 -1
  20. package/lib/metrics/metricsProcessTypeUtils.d.ts +17 -9
  21. package/lib/metrics/metricsProcessTypeUtils.d.ts.map +1 -1
  22. package/lib/metrics/metricsProcessTypeUtils.js +18 -9
  23. package/lib/metrics/metricsProcessTypeUtils.js.map +1 -1
  24. package/package.json +3 -2
  25. package/src/metrics/baseMetricsClient.js +1 -1
  26. package/src/metrics/httpMetricsRedisCollector.js +78 -43
  27. package/src/metrics/httpMetricsRedisRecorder.js +23 -15
  28. package/src/metrics/metricsClient.js +1 -93
  29. package/src/metrics/metricsProcessTypeUtils.js +20 -9
  30. package/docs/http-metrics-redis.md +0 -19
@@ -8,35 +8,43 @@
8
8
  export function resolveMetricsProcessType(metricsConfig: {
9
9
  processType?: string;
10
10
  }, defaultProcessType: string): string;
11
+ /**
12
+ * @param {{ processType?: string, enabled?: boolean }} metricsConfig
13
+ * @returns {boolean}
14
+ */
15
+ export function shouldEnforceProcessType(metricsConfig: {
16
+ processType?: string;
17
+ enabled?: boolean;
18
+ }): boolean;
11
19
  /**
12
20
  * Exit with no logs if the resolved process type is not exactly `expectedProcessType`.
21
+ * No-op when `METRICS_ENABLED` is not `true` (avoids exit/restart loops when metrics are off).
13
22
  *
14
- * @param {{ processType?: string }} metricsConfig
23
+ * @param {{ processType?: string, enabled?: boolean }} metricsConfig
15
24
  * @param {string} expectedProcessType - Single allowed value (use a module constant, e.g. {@link METRICS_PROCESS_TYPE_DATABASE})
16
25
  * @returns {void}
17
26
  */
18
27
  export function exitUnlessProcessTypeIs(metricsConfig: {
19
28
  processType?: string;
29
+ enabled?: boolean;
20
30
  }, expectedProcessType: string): void;
21
31
  /**
22
32
  * Exit with no logs if the resolved process type is not in `allowedProcessTypes`.
33
+ * No-op when `METRICS_ENABLED` is not `true`.
23
34
  *
24
- * @param {{ processType?: string }} metricsConfig
35
+ * @param {{ processType?: string, enabled?: boolean }} metricsConfig
25
36
  * @param {readonly string[]} allowedProcessTypes
26
37
  * @param {string} defaultWhenUnspecified - Used only to resolve when config/env omit `processType` (e.g. {@link METRICS_PROCESS_TYPE_QUEUE} for {@link RedisMetricsClient})
27
38
  * @returns {void}
28
39
  */
29
40
  export function exitUnlessProcessTypeIn(metricsConfig: {
30
41
  processType?: string;
42
+ enabled?: boolean;
31
43
  }, allowedProcessTypes: readonly string[], defaultWhenUnspecified: string): void;
32
44
  /**
33
- * Helpers for resolving `processType` and silently exiting when a specialized metrics client
34
- * is constructed on the wrong dyno / process (no log output).
35
- *
36
- * **Canonical names** align with typical Procfile processes (e.g. backend: `web`, `worker`,
37
- * `queue-metrics`, `database-metrics`, `http-metrics`). The compile **worker** dyno serves queues/builds
38
- * — it does not run HTTP request metrics. HTTP Redis **key segment** for API traffic is fixed **`web`**
39
- * (`HttpMetricsRedisCollector` / `HttpMetricsRedisRecorder`), not `BUILD_DYNO_PROCESS_TYPE`.
45
+ * Helpers for resolving `processType` and optional silent exit when a specialized metrics client
46
+ * is constructed on the wrong dyno (no log output). If `METRICS_ENABLED` is not `true`, the check
47
+ * is skipped so the process is not stopped.
40
48
  *
41
49
  * @module metrics/metricsProcessTypeUtils
42
50
  */
@@ -1 +1 @@
1
- {"version":3,"file":"metricsProcessTypeUtils.d.ts","sourceRoot":"","sources":["../../src/metrics/metricsProcessTypeUtils.js"],"names":[],"mappings":"AAoCA;;;;;;GAMG;AACH,yDAJW;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,sBACxB,MAAM,GACJ,MAAM,CAQlB;AAED;;;;;;GAMG;AACH,uDAJW;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,uBACxB,MAAM,GACJ,IAAI,CAOhB;AAED;;;;;;;GAOG;AACH,uDALW;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,uBACxB,SAAS,MAAM,EAAE,0BACjB,MAAM,GACJ,IAAI,CAchB;AArFD;;;;;;;;;;GAUG;AAEH,6DAA6D;AAC7D,+DAAwD;AAExD,gEAAgE;AAChE,yDAAkD;AAElD,yEAAyE;AACzE,yDAAkD;AAElD,kGAAkG;AAClG,6CAAsC;AAEtC,gGAAgG;AAChG,mDAA4C;AAE5C;;;GAGG;AACH,yDAFU,SAAS,MAAM,EAAE,CAKzB"}
1
+ {"version":3,"file":"metricsProcessTypeUtils.d.ts","sourceRoot":"","sources":["../../src/metrics/metricsProcessTypeUtils.js"],"names":[],"mappings":"AAgCA;;;;;;GAMG;AACH,yDAJW;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,sBACxB,MAAM,GACJ,MAAM,CAQlB;AAED;;;GAGG;AACH,wDAHW;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACzC,OAAO,CAMnB;AAED;;;;;;;GAOG;AACH,uDAJW;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,uBAC3C,MAAM,GACJ,IAAI,CAQhB;AAED;;;;;;;;GAQG;AACH,uDALW;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,uBAC3C,SAAS,MAAM,EAAE,0BACjB,MAAM,GACJ,IAAI,CAehB;AA/FD;;;;;;GAMG;AAEH,6DAA6D;AAC7D,+DAAwD;AAExD,gEAAgE;AAChE,yDAAkD;AAElD,yEAAyE;AACzE,yDAAkD;AAElD,kGAAkG;AAClG,6CAAsC;AAEtC,gGAAgG;AAChG,mDAA4C;AAE5C;;;GAGG;AACH,yDAFU,SAAS,MAAM,EAAE,CAKzB"}
@@ -1,13 +1,9 @@
1
1
  "use strict";
2
2
 
3
3
  /**
4
- * Helpers for resolving `processType` and silently exiting when a specialized metrics client
5
- * is constructed on the wrong dyno / process (no log output).
6
- *
7
- * **Canonical names** align with typical Procfile processes (e.g. backend: `web`, `worker`,
8
- * `queue-metrics`, `database-metrics`, `http-metrics`). The compile **worker** dyno serves queues/builds
9
- * — it does not run HTTP request metrics. HTTP Redis **key segment** for API traffic is fixed **`web`**
10
- * (`HttpMetricsRedisCollector` / `HttpMetricsRedisRecorder`), not `BUILD_DYNO_PROCESS_TYPE`.
4
+ * Helpers for resolving `processType` and optional silent exit when a specialized metrics client
5
+ * is constructed on the wrong dyno (no log output). If `METRICS_ENABLED` is not `true`, the check
6
+ * is skipped so the process is not stopped.
11
7
  *
12
8
  * @module metrics/metricsProcessTypeUtils
13
9
  */
@@ -44,14 +40,24 @@ function resolveMetricsProcessType(metricsConfig, defaultProcessType) {
44
40
  return metricsConfig.processType || process.env.BUILD_DYNO_PROCESS_TYPE || defaultProcessType;
45
41
  }
46
42
 
43
+ /**
44
+ * @param {{ processType?: string, enabled?: boolean }} metricsConfig
45
+ * @returns {boolean}
46
+ */
47
+ function shouldEnforceProcessType(metricsConfig) {
48
+ return (metricsConfig.enabled ?? process.env.METRICS_ENABLED === 'true') === true;
49
+ }
50
+
47
51
  /**
48
52
  * Exit with no logs if the resolved process type is not exactly `expectedProcessType`.
53
+ * No-op when `METRICS_ENABLED` is not `true` (avoids exit/restart loops when metrics are off).
49
54
  *
50
- * @param {{ processType?: string }} metricsConfig
55
+ * @param {{ processType?: string, enabled?: boolean }} metricsConfig
51
56
  * @param {string} expectedProcessType - Single allowed value (use a module constant, e.g. {@link METRICS_PROCESS_TYPE_DATABASE})
52
57
  * @returns {void}
53
58
  */
54
59
  function exitUnlessProcessTypeIs(metricsConfig, expectedProcessType) {
60
+ if (!shouldEnforceProcessType(metricsConfig)) return;
55
61
  const resolved = resolveMetricsProcessType(metricsConfig, expectedProcessType);
56
62
  if (resolved !== expectedProcessType) {
57
63
  process.exit(0);
@@ -60,13 +66,15 @@ function exitUnlessProcessTypeIs(metricsConfig, expectedProcessType) {
60
66
 
61
67
  /**
62
68
  * Exit with no logs if the resolved process type is not in `allowedProcessTypes`.
69
+ * No-op when `METRICS_ENABLED` is not `true`.
63
70
  *
64
- * @param {{ processType?: string }} metricsConfig
71
+ * @param {{ processType?: string, enabled?: boolean }} metricsConfig
65
72
  * @param {readonly string[]} allowedProcessTypes
66
73
  * @param {string} defaultWhenUnspecified - Used only to resolve when config/env omit `processType` (e.g. {@link METRICS_PROCESS_TYPE_QUEUE} for {@link RedisMetricsClient})
67
74
  * @returns {void}
68
75
  */
69
76
  function exitUnlessProcessTypeIn(metricsConfig, allowedProcessTypes, defaultWhenUnspecified) {
77
+ if (!shouldEnforceProcessType(metricsConfig)) return;
70
78
  const resolved = resolveMetricsProcessType(metricsConfig, defaultWhenUnspecified);
71
79
  if (!allowedProcessTypes.includes(resolved)) {
72
80
  process.exit(0);
@@ -74,6 +82,7 @@ function exitUnlessProcessTypeIn(metricsConfig, allowedProcessTypes, defaultWhen
74
82
  }
75
83
  module.exports = {
76
84
  resolveMetricsProcessType,
85
+ shouldEnforceProcessType,
77
86
  exitUnlessProcessTypeIs,
78
87
  exitUnlessProcessTypeIn,
79
88
  METRICS_PROCESS_TYPE_DATABASE,
@@ -1 +1 @@
1
- {"version":3,"file":"metricsProcessTypeUtils.js","names":["METRICS_PROCESS_TYPE_DATABASE","METRICS_PROCESS_TYPE_QUEUE","METRICS_PROCESS_TYPE_REDIS","METRICS_PROCESS_TYPE_WEB","METRICS_PROCESS_TYPE_WORKER","REDIS_METRICS_CLIENT_ALLOWED_PROCESS_TYPES","Object","freeze","resolveMetricsProcessType","metricsConfig","defaultProcessType","processType","process","env","BUILD_DYNO_PROCESS_TYPE","exitUnlessProcessTypeIs","expectedProcessType","resolved","exit","exitUnlessProcessTypeIn","allowedProcessTypes","defaultWhenUnspecified","includes","module","exports"],"sources":["../../src/metrics/metricsProcessTypeUtils.js"],"sourcesContent":["/**\n * Helpers for resolving `processType` and silently exiting when a specialized metrics client\n * is constructed on the wrong dyno / process (no log output).\n *\n * **Canonical names** align with typical Procfile processes (e.g. backend: `web`, `worker`,\n * `queue-metrics`, `database-metrics`, `http-metrics`). The compile **worker** dyno serves queues/builds\n * it does not run HTTP request metrics. HTTP Redis **key segment** for API traffic is fixed **`web`**\n * (`HttpMetricsRedisCollector` / `HttpMetricsRedisRecorder`), not `BUILD_DYNO_PROCESS_TYPE`.\n *\n * @module metrics/metricsProcessTypeUtils\n */\n\n/** DB-only metrics dyno (`database-metrics` in Procfile). */\nconst METRICS_PROCESS_TYPE_DATABASE = 'database-metrics'\n\n/** Queue + Redis metrics dyno (`queue-metrics` in Procfile). */\nconst METRICS_PROCESS_TYPE_QUEUE = 'queue-metrics'\n\n/** Redis-only metrics dyno (no Bee Queue; optional separate process). */\nconst METRICS_PROCESS_TYPE_REDIS = 'redis-metrics'\n\n/** Web servers — HTTP traffic, HTTP Redis **writers** typically use this in Redis key segment. */\nconst METRICS_PROCESS_TYPE_WEB = 'web'\n\n/** Build/compile workers and similar (e.g. backend `worker:`) — no HTTP server metrics here. */\nconst METRICS_PROCESS_TYPE_WORKER = 'worker'\n\n/**\n * Parent {@link RedisMetricsClient} allows either redis-only or queue stack (`QueueRedisMetricsClient`).\n * @type {readonly string[]}\n */\nconst REDIS_METRICS_CLIENT_ALLOWED_PROCESS_TYPES = Object.freeze([\n METRICS_PROCESS_TYPE_REDIS,\n METRICS_PROCESS_TYPE_QUEUE,\n])\n\n/**\n * Resolve logical process type the same way specialized metrics clients do.\n *\n * @param {{ processType?: string }} metricsConfig - Remainder of constructor options (e.g. after destructuring `redisClient`, `databaseUrl`, …)\n * @param {string} defaultProcessType - Fallback when `metricsConfig.processType` and `BUILD_DYNO_PROCESS_TYPE` are unset\n * @returns {string}\n */\nfunction resolveMetricsProcessType(metricsConfig, defaultProcessType) {\n return (\n metricsConfig.processType ||\n process.env.BUILD_DYNO_PROCESS_TYPE ||\n defaultProcessType\n )\n}\n\n/**\n * Exit with no logs if the resolved process type is not exactly `expectedProcessType`.\n *\n * @param {{ processType?: string }} metricsConfig\n * @param {string} expectedProcessType - Single allowed value (use a module constant, e.g. {@link METRICS_PROCESS_TYPE_DATABASE})\n * @returns {void}\n */\nfunction exitUnlessProcessTypeIs(metricsConfig, expectedProcessType) {\n const resolved = resolveMetricsProcessType(metricsConfig, expectedProcessType)\n if (resolved !== expectedProcessType) {\n process.exit(0)\n }\n}\n\n/**\n * Exit with no logs if the resolved process type is not in `allowedProcessTypes`.\n *\n * @param {{ processType?: string }} metricsConfig\n * @param {readonly string[]} allowedProcessTypes\n * @param {string} defaultWhenUnspecified - Used only to resolve when config/env omit `processType` (e.g. {@link METRICS_PROCESS_TYPE_QUEUE} for {@link RedisMetricsClient})\n * @returns {void}\n */\nfunction exitUnlessProcessTypeIn(\n metricsConfig,\n allowedProcessTypes,\n defaultWhenUnspecified\n) {\n const resolved = resolveMetricsProcessType(\n metricsConfig,\n defaultWhenUnspecified\n )\n if (!allowedProcessTypes.includes(resolved)) {\n process.exit(0)\n }\n}\n\nmodule.exports = {\n resolveMetricsProcessType,\n exitUnlessProcessTypeIs,\n exitUnlessProcessTypeIn,\n METRICS_PROCESS_TYPE_DATABASE,\n METRICS_PROCESS_TYPE_QUEUE,\n METRICS_PROCESS_TYPE_REDIS,\n METRICS_PROCESS_TYPE_WEB,\n METRICS_PROCESS_TYPE_WORKER,\n REDIS_METRICS_CLIENT_ALLOWED_PROCESS_TYPES,\n}\n"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,MAAMA,6BAA6B,GAAG,kBAAkB;;AAExD;AACA,MAAMC,0BAA0B,GAAG,eAAe;;AAElD;AACA,MAAMC,0BAA0B,GAAG,eAAe;;AAElD;AACA,MAAMC,wBAAwB,GAAG,KAAK;;AAEtC;AACA,MAAMC,2BAA2B,GAAG,QAAQ;;AAE5C;AACA;AACA;AACA;AACA,MAAMC,0CAA0C,GAAGC,MAAM,CAACC,MAAM,CAAC,CAC/DL,0BAA0B,EAC1BD,0BAA0B,CAC3B,CAAC;;AAEF;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASO,yBAAyBA,CAACC,aAAa,EAAEC,kBAAkB,EAAE;EACpE,OACED,aAAa,CAACE,WAAW,IACzBC,OAAO,CAACC,GAAG,CAACC,uBAAuB,IACnCJ,kBAAkB;AAEtB;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASK,uBAAuBA,CAACN,aAAa,EAAEO,mBAAmB,EAAE;EACnE,MAAMC,QAAQ,GAAGT,yBAAyB,CAACC,aAAa,EAAEO,mBAAmB,CAAC;EAC9E,IAAIC,QAAQ,KAAKD,mBAAmB,EAAE;IACpCJ,OAAO,CAACM,IAAI,CAAC,CAAC,CAAC;EACjB;AACF;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASC,uBAAuBA,CAC9BV,aAAa,EACbW,mBAAmB,EACnBC,sBAAsB,EACtB;EACA,MAAMJ,QAAQ,GAAGT,yBAAyB,CACxCC,aAAa,EACbY,sBACF,CAAC;EACD,IAAI,CAACD,mBAAmB,CAACE,QAAQ,CAACL,QAAQ,CAAC,EAAE;IAC3CL,OAAO,CAACM,IAAI,CAAC,CAAC,CAAC;EACjB;AACF;AAEAK,MAAM,CAACC,OAAO,GAAG;EACfhB,yBAAyB;EACzBO,uBAAuB;EACvBI,uBAAuB;EACvBnB,6BAA6B;EAC7BC,0BAA0B;EAC1BC,0BAA0B;EAC1BC,wBAAwB;EACxBC,2BAA2B;EAC3BC;AACF,CAAC","ignoreList":[]}
1
+ {"version":3,"file":"metricsProcessTypeUtils.js","names":["METRICS_PROCESS_TYPE_DATABASE","METRICS_PROCESS_TYPE_QUEUE","METRICS_PROCESS_TYPE_REDIS","METRICS_PROCESS_TYPE_WEB","METRICS_PROCESS_TYPE_WORKER","REDIS_METRICS_CLIENT_ALLOWED_PROCESS_TYPES","Object","freeze","resolveMetricsProcessType","metricsConfig","defaultProcessType","processType","process","env","BUILD_DYNO_PROCESS_TYPE","shouldEnforceProcessType","enabled","METRICS_ENABLED","exitUnlessProcessTypeIs","expectedProcessType","resolved","exit","exitUnlessProcessTypeIn","allowedProcessTypes","defaultWhenUnspecified","includes","module","exports"],"sources":["../../src/metrics/metricsProcessTypeUtils.js"],"sourcesContent":["/**\n * Helpers for resolving `processType` and optional silent exit when a specialized metrics client\n * is constructed on the wrong dyno (no log output). If `METRICS_ENABLED` is not `true`, the check\n * is skipped so the process is not stopped.\n *\n * @module metrics/metricsProcessTypeUtils\n */\n\n/** DB-only metrics dyno (`database-metrics` in Procfile). */\nconst METRICS_PROCESS_TYPE_DATABASE = 'database-metrics'\n\n/** Queue + Redis metrics dyno (`queue-metrics` in Procfile). */\nconst METRICS_PROCESS_TYPE_QUEUE = 'queue-metrics'\n\n/** Redis-only metrics dyno (no Bee Queue; optional separate process). */\nconst METRICS_PROCESS_TYPE_REDIS = 'redis-metrics'\n\n/** Web servers — HTTP traffic, HTTP Redis **writers** typically use this in Redis key segment. */\nconst METRICS_PROCESS_TYPE_WEB = 'web'\n\n/** Build/compile workers and similar (e.g. backend `worker:`) — no HTTP server metrics here. */\nconst METRICS_PROCESS_TYPE_WORKER = 'worker'\n\n/**\n * Parent {@link RedisMetricsClient} allows either redis-only or queue stack (`QueueRedisMetricsClient`).\n * @type {readonly string[]}\n */\nconst REDIS_METRICS_CLIENT_ALLOWED_PROCESS_TYPES = Object.freeze([\n METRICS_PROCESS_TYPE_REDIS,\n METRICS_PROCESS_TYPE_QUEUE,\n])\n\n/**\n * Resolve logical process type the same way specialized metrics clients do.\n *\n * @param {{ processType?: string }} metricsConfig - Remainder of constructor options (e.g. after destructuring `redisClient`, `databaseUrl`, …)\n * @param {string} defaultProcessType - Fallback when `metricsConfig.processType` and `BUILD_DYNO_PROCESS_TYPE` are unset\n * @returns {string}\n */\nfunction resolveMetricsProcessType(metricsConfig, defaultProcessType) {\n return (\n metricsConfig.processType ||\n process.env.BUILD_DYNO_PROCESS_TYPE ||\n defaultProcessType\n )\n}\n\n/**\n * @param {{ processType?: string, enabled?: boolean }} metricsConfig\n * @returns {boolean}\n */\nfunction shouldEnforceProcessType(metricsConfig) {\n return (\n (metricsConfig.enabled ?? process.env.METRICS_ENABLED === 'true') === true\n )\n}\n\n/**\n * Exit with no logs if the resolved process type is not exactly `expectedProcessType`.\n * No-op when `METRICS_ENABLED` is not `true` (avoids exit/restart loops when metrics are off).\n *\n * @param {{ processType?: string, enabled?: boolean }} metricsConfig\n * @param {string} expectedProcessType - Single allowed value (use a module constant, e.g. {@link METRICS_PROCESS_TYPE_DATABASE})\n * @returns {void}\n */\nfunction exitUnlessProcessTypeIs(metricsConfig, expectedProcessType) {\n if (!shouldEnforceProcessType(metricsConfig)) return\n const resolved = resolveMetricsProcessType(metricsConfig, expectedProcessType)\n if (resolved !== expectedProcessType) {\n process.exit(0)\n }\n}\n\n/**\n * Exit with no logs if the resolved process type is not in `allowedProcessTypes`.\n * No-op when `METRICS_ENABLED` is not `true`.\n *\n * @param {{ processType?: string, enabled?: boolean }} metricsConfig\n * @param {readonly string[]} allowedProcessTypes\n * @param {string} defaultWhenUnspecified - Used only to resolve when config/env omit `processType` (e.g. {@link METRICS_PROCESS_TYPE_QUEUE} for {@link RedisMetricsClient})\n * @returns {void}\n */\nfunction exitUnlessProcessTypeIn(\n metricsConfig,\n allowedProcessTypes,\n defaultWhenUnspecified\n) {\n if (!shouldEnforceProcessType(metricsConfig)) return\n const resolved = resolveMetricsProcessType(\n metricsConfig,\n defaultWhenUnspecified\n )\n if (!allowedProcessTypes.includes(resolved)) {\n process.exit(0)\n }\n}\n\nmodule.exports = {\n resolveMetricsProcessType,\n shouldEnforceProcessType,\n exitUnlessProcessTypeIs,\n exitUnlessProcessTypeIn,\n METRICS_PROCESS_TYPE_DATABASE,\n METRICS_PROCESS_TYPE_QUEUE,\n METRICS_PROCESS_TYPE_REDIS,\n METRICS_PROCESS_TYPE_WEB,\n METRICS_PROCESS_TYPE_WORKER,\n REDIS_METRICS_CLIENT_ALLOWED_PROCESS_TYPES,\n}\n"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,MAAMA,6BAA6B,GAAG,kBAAkB;;AAExD;AACA,MAAMC,0BAA0B,GAAG,eAAe;;AAElD;AACA,MAAMC,0BAA0B,GAAG,eAAe;;AAElD;AACA,MAAMC,wBAAwB,GAAG,KAAK;;AAEtC;AACA,MAAMC,2BAA2B,GAAG,QAAQ;;AAE5C;AACA;AACA;AACA;AACA,MAAMC,0CAA0C,GAAGC,MAAM,CAACC,MAAM,CAAC,CAC/DL,0BAA0B,EAC1BD,0BAA0B,CAC3B,CAAC;;AAEF;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASO,yBAAyBA,CAACC,aAAa,EAAEC,kBAAkB,EAAE;EACpE,OACED,aAAa,CAACE,WAAW,IACzBC,OAAO,CAACC,GAAG,CAACC,uBAAuB,IACnCJ,kBAAkB;AAEtB;;AAEA;AACA;AACA;AACA;AACA,SAASK,wBAAwBA,CAACN,aAAa,EAAE;EAC/C,OACE,CAACA,aAAa,CAACO,OAAO,IAAIJ,OAAO,CAACC,GAAG,CAACI,eAAe,KAAK,MAAM,MAAM,IAAI;AAE9E;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASC,uBAAuBA,CAACT,aAAa,EAAEU,mBAAmB,EAAE;EACnE,IAAI,CAACJ,wBAAwB,CAACN,aAAa,CAAC,EAAE;EAC9C,MAAMW,QAAQ,GAAGZ,yBAAyB,CAACC,aAAa,EAAEU,mBAAmB,CAAC;EAC9E,IAAIC,QAAQ,KAAKD,mBAAmB,EAAE;IACpCP,OAAO,CAACS,IAAI,CAAC,CAAC,CAAC;EACjB;AACF;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASC,uBAAuBA,CAC9Bb,aAAa,EACbc,mBAAmB,EACnBC,sBAAsB,EACtB;EACA,IAAI,CAACT,wBAAwB,CAACN,aAAa,CAAC,EAAE;EAC9C,MAAMW,QAAQ,GAAGZ,yBAAyB,CACxCC,aAAa,EACbe,sBACF,CAAC;EACD,IAAI,CAACD,mBAAmB,CAACE,QAAQ,CAACL,QAAQ,CAAC,EAAE;IAC3CR,OAAO,CAACS,IAAI,CAAC,CAAC,CAAC;EACjB;AACF;AAEAK,MAAM,CAACC,OAAO,GAAG;EACfnB,yBAAyB;EACzBO,wBAAwB;EACxBG,uBAAuB;EACvBI,uBAAuB;EACvBtB,6BAA6B;EAC7BC,0BAA0B;EAC1BC,0BAA0B;EAC1BC,wBAAwB;EACxBC,2BAA2B;EAC3BC;AACF,CAAC","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adalo/metrics",
3
- "version": "0.1.174",
3
+ "version": "0.1.176",
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",
@@ -23,12 +23,13 @@
23
23
  "author": "@AdaloHQ",
24
24
  "license": "ISC",
25
25
  "dependencies": {
26
+ "@types/pg": "^8.15.6",
26
27
  "bee-queue": "^1.2.2",
27
28
  "dotenv": "^8.2.0",
28
29
  "mysql2": "^3.11.0",
29
30
  "pg": "^8.16.3",
30
31
  "prom-client": "^15.1.3",
31
- "@types/pg": "^8.15.6"
32
+ "uuid": "9.0.1"
32
33
  },
33
34
  "devDependencies": {
34
35
  "@babel/cli": "7.24.7",
@@ -63,7 +63,7 @@ class BaseMetricsClient {
63
63
  process_type: this.processType,
64
64
  }
65
65
 
66
- /** Default labels without dyno_id (for HTTP metrics so dyno_id is not included). */
66
+ /** Labels excluding `dyno_id` (for series keyed only by app + process_type). */
67
67
  this.defaultLabelsWithoutDynoId = {
68
68
  app: this.appName,
69
69
  process_type: this.processType,
@@ -1,3 +1,4 @@
1
+ const { v4: uuidv4 } = require('uuid')
1
2
  const { BaseMetricsClient } = require('./baseMetricsClient')
2
3
  const {
3
4
  HttpMetricsRedisStore,
@@ -5,31 +6,33 @@ const {
5
6
  } = require('./httpMetricsRedisStore')
6
7
 
7
8
  /**
8
- * Drain worker: reads HTTP aggregates from Redis (written by {@link HttpMetricsRedisRecorder}),
9
- * applies them to `app_requests_*` counters, then pushes the registry to the VM-agent (same as {@link BaseMetricsClient}).
10
- * **Minimal usage:** `{ redisClient }` only. Redis keys use segment **`web`** unless you pass **`redisProcessTypeForKeys`**.
11
- * `processType` / `appName` / `dynoId` follow {@link BaseMetricsClient} defaults (e.g. `BUILD_DYNO_PROCESS_TYPE`) and do **not** select Redis hash names.
12
- * Always passes `blockNodeDefaultMetrics: true` (HTTP-focused registry).
13
- *
14
- * @extends BaseMetricsClient
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.
15
11
  */
12
+ const HTTP_PUSH_INSTANCE_ROTATE_MS =
13
+ parseInt(process.env.METRICS_HTTP_INSTANCE_ROTATE_MS || '', 10) ||
14
+ 7 * 24 * 60 * 60 * 1000
15
+ const HTTP_PUSH_ROTATION_CHECK_THROTTLE_MS = 60 * 1000
16
+
16
17
  class HttpMetricsRedisCollector extends BaseMetricsClient {
17
18
  /**
18
19
  * @param {Object} [config]
19
- * @param {import('redis').RedisClient} config.redisClient **Required.** Injected client (same pattern as {@link RedisMetricsClient}).
20
- * @param {string} [config.appName] Application name (defaults per {@link BaseMetricsClient})
21
- * @param {string} [config.dynoId] Dyno/instance ID
22
- * @param {string} [config.processType] Label `process_type` on push (default from env / base)
23
- * @param {boolean} [config.enabled] Enable collection and push
24
- * @param {boolean} [config.logValues] Log metric JSON to console
25
- * @param {string} [config.pushgatewayUrl] VM-agent import URL
26
- * @param {string} [config.pushgatewaySecret] Basic auth secret (Base64)
27
- * @param {number} [config.intervalSec] Push interval (seconds)
28
- * @param {boolean} [config.removeOldMetrics] Clear old series on shutdown where supported
29
- * @param {function} [config.startupValidation] Run before first push
30
- * @param {boolean} [config.disablePushgateway] Skip POST to VM-agent
31
- * @param {string} [config.redisProcessTypeForKeys] Segment in Redis keys for HTTP hashes (default **`web`**). Optional; only if writers use a non-`web` segment.
32
- * @param {number} [config.ttlSec] Passed to {@link HttpMetricsRedisStore} (should match writers)
20
+ * @param {import('redis').RedisClient} config.redisClient
21
+ * @param {string} [config.appName] Name of the application (defaults per {@link BaseMetricsClient})
22
+ * @param {string} [config.dynoId] Dyno/instance ID (defaults per {@link BaseMetricsClient})
23
+ * @param {string} [config.processType] Process type for labels (defaults per {@link BaseMetricsClient})
24
+ * @param {boolean} [config.enabled] Enable metrics collection (defaults per {@link BaseMetricsClient})
25
+ * @param {boolean} [config.logValues] Log metrics values to console (defaults per {@link BaseMetricsClient})
26
+ * @param {string} [config.pushgatewayUrl] VM-agent import URL (defaults per {@link BaseMetricsClient})
27
+ * @param {string} [config.pushgatewaySecret] Basic auth secret, Base64 (defaults per {@link BaseMetricsClient})
28
+ * @param {number} [config.intervalSec] Push interval in seconds (defaults per {@link BaseMetricsClient})
29
+ * @param {boolean} [config.removeOldMetrics] Enable clearing old metrics (defaults per {@link BaseMetricsClient})
30
+ * @param {function} [config.startupValidation] Run before first push (defaults per {@link BaseMetricsClient})
31
+ * @param {boolean} [config.disablePushgateway] Skip POST to VM-agent (defaults per {@link BaseMetricsClient})
32
+ * @param {string} [config.redisProcessTypeForKeys] Segment in Redis keys for HTTP hashes (default `web`).
33
+ * @param {number} [config.ttlSec] Redis hash TTL in seconds; should match writers’ {@link HttpMetricsRedisStore}.
34
+ *
35
+ * `http_instance_id` rotation interval: {@link HTTP_PUSH_INSTANCE_ROTATE_MS} (set at process start from env, see constant JSDoc).
33
36
  */
34
37
  constructor(config = {}) {
35
38
  const { redisClient } = config
@@ -44,11 +47,6 @@ class HttpMetricsRedisCollector extends BaseMetricsClient {
44
47
 
45
48
  const keyProcessType = config.redisProcessTypeForKeys || 'web'
46
49
 
47
- this.defaultLabelsWithoutDynoId = {
48
- app: this.appName,
49
- process_type: keyProcessType,
50
- }
51
-
52
50
  this._store = new HttpMetricsRedisStore({
53
51
  redisClient,
54
52
  appName: this.appName,
@@ -56,37 +54,69 @@ class HttpMetricsRedisCollector extends BaseMetricsClient {
56
54
  ttlSec: config.ttlSec,
57
55
  })
58
56
 
59
- /** @type {Set<string>} Redis field keys already primed with inc(0) + push in this process */
57
+ const now = Date.now()
58
+ this._httpPushInstanceId = uuidv4()
59
+ this._httpPushInstanceStartedAt = now
60
+ this._httpRotationLastCheck = 0
61
+
62
+ /** @type {Set<string>} */
60
63
  this._httpCounterPrimedKeys = new Set()
61
64
 
65
+ const httpLabelNames = this.withDefaultLabelsWithoutDynoId([
66
+ 'method',
67
+ 'route',
68
+ 'status_code',
69
+ 'http_instance_id',
70
+ ])
71
+
62
72
  this.createCounter({
63
73
  name: 'app_requests_total',
64
74
  help: 'Total number of HTTP requests',
65
- labelNames: this.withDefaultLabelsWithoutDynoId([
66
- 'method',
67
- 'route',
68
- 'status_code',
69
- ]),
75
+ labelNames: httpLabelNames,
70
76
  useLabelsWithoutDynoId: true,
71
77
  })
72
78
 
73
79
  this.createCounter({
74
80
  name: 'app_requests_total_duration',
75
81
  help: 'Total duration of HTTP requests in milliseconds',
76
- labelNames: this.withDefaultLabelsWithoutDynoId([
77
- 'method',
78
- 'route',
79
- 'status_code',
80
- ]),
82
+ labelNames: httpLabelNames,
81
83
  useLabelsWithoutDynoId: true,
82
84
  })
83
85
  }
84
86
 
87
+ _labelsForHttpPush(rowLabels) {
88
+ return {
89
+ ...rowLabels,
90
+ http_instance_id: this._httpPushInstanceId,
91
+ }
92
+ }
93
+
94
+ /** @param {number} now */
95
+ _rotateHttpPushInstance(now) {
96
+ this._httpPushInstanceId = uuidv4()
97
+ this._httpPushInstanceStartedAt = now
98
+ this.clearAllCounters()
99
+ this._httpCounterPrimedKeys.clear()
100
+ }
101
+
102
+ /** @param {number} now */
103
+ _maybeRotateHttpPushInstance(now = Date.now()) {
104
+ if (now - this._httpRotationLastCheck < HTTP_PUSH_ROTATION_CHECK_THROTTLE_MS) {
105
+ return
106
+ }
107
+ this._httpRotationLastCheck = now
108
+
109
+ if (now - this._httpPushInstanceStartedAt >= HTTP_PUSH_INSTANCE_ROTATE_MS) {
110
+ this._rotateHttpPushInstance(now)
111
+ }
112
+ }
113
+
85
114
  /**
86
- * Drains Redis into counters, then runs gauge updates and VM-agent push ({@link BaseMetricsClient#_pushMetrics}).
87
115
  * @returns {Promise<void>}
88
116
  */
89
117
  pushMetrics = async () => {
118
+ this._maybeRotateHttpPushInstance()
119
+
90
120
  if (
91
121
  this._store &&
92
122
  this.countersFunctions?.app_requests_total &&
@@ -106,10 +136,11 @@ class HttpMetricsRedisCollector extends BaseMetricsClient {
106
136
  row.labels.route,
107
137
  row.labels.status_code
108
138
  )
139
+ const L = this._labelsForHttpPush(row.labels)
109
140
  if (!this._httpCounterPrimedKeys.has(key)) {
110
141
  this._httpCounterPrimedKeys.add(key)
111
- applyCount(row.labels, 0)
112
- applyDur(row.labels, 0)
142
+ applyCount(L, 0)
143
+ applyDur(L, 0)
113
144
  primedAny = true
114
145
  }
115
146
  }
@@ -117,9 +148,10 @@ class HttpMetricsRedisCollector extends BaseMetricsClient {
117
148
  await this.gatewayPush()
118
149
  }
119
150
  for (const row of rows) {
120
- applyCount(row.labels, row.count)
151
+ const L = this._labelsForHttpPush(row.labels)
152
+ applyCount(L, row.count)
121
153
  if (row.dur > 0) {
122
- applyDur(row.labels, row.dur)
154
+ applyDur(L, row.dur)
123
155
  }
124
156
  }
125
157
  }
@@ -128,4 +160,7 @@ class HttpMetricsRedisCollector extends BaseMetricsClient {
128
160
  }
129
161
  }
130
162
 
131
- module.exports = { HttpMetricsRedisCollector }
163
+ module.exports = {
164
+ HttpMetricsRedisCollector,
165
+ HTTP_PUSH_INSTANCE_ROTATE_MS,
166
+ }
@@ -1,23 +1,29 @@
1
1
  const { HttpMetricsRedisStore } = require('./httpMetricsRedisStore')
2
2
 
3
- /**
4
- * Records HTTP request aggregates only to Redis (no in-process Prometheus counters on this path).
5
- * Pair with {@link HttpMetricsRedisCollector} on a drain process to flush into counters and push to the VM-agent.
6
- *
7
- * @see HttpMetricsRedisStore
8
- */
9
3
  class HttpMetricsRedisRecorder {
10
4
  /**
11
- * @param {Object} opts
12
- * @param {import('redis').RedisClient} opts.redisClient **Required.** Injected client (same pattern as {@link RedisMetricsClient}).
13
- * @param {string} [opts.appName] Optional override; default same as {@link BaseMetricsClient}: `BUILD_APP_NAME` or `unknown-app`.
14
- * @param {string} [opts.processType] Redis key segment (default **`web`**, same as {@link HttpMetricsRedisCollector}).
15
- * @param {number} [opts.ttlSec] Redis hash key TTL in seconds (sliding; default 120).
5
+ * @param {Object} [opts]
6
+ * @param {import('redis').RedisClient} [opts.redisClient] Required if `METRICS_HTTP_ENABLED` env is `true`.
7
+ * @param {string} [opts.appName]
8
+ * @param {string} [opts.processType]
9
+ * @param {number} [opts.ttlSec]
16
10
  */
17
11
  constructor({ redisClient, appName, processType = 'web', ttlSec } = {}) {
12
+ this._enabled = process.env.METRICS_HTTP_ENABLED === 'true'
13
+
14
+ if (!this._enabled) {
15
+ this.processType = processType
16
+ this.appName = appName || process.env.BUILD_APP_NAME || 'unknown-app'
17
+ this._store = null
18
+ return
19
+ }
20
+
18
21
  if (redisClient == null) {
19
- throw new Error('HttpMetricsRedisRecorder: redisClient is required')
22
+ throw new Error(
23
+ 'HttpMetricsRedisRecorder: redisClient is required when METRICS_HTTP_ENABLED=true'
24
+ )
20
25
  }
26
+
21
27
  const resolvedAppName =
22
28
  appName || process.env.BUILD_APP_NAME || 'unknown-app'
23
29
  this.processType = processType
@@ -38,18 +44,20 @@ class HttpMetricsRedisRecorder {
38
44
  * @param {number} params.duration
39
45
  */
40
46
  trackHttpRequest({ method, route, status_code, duration }) {
47
+ if (!this._enabled || !this._store) return
41
48
  this._store.record(method, route, status_code, duration)
42
49
  }
43
50
 
44
51
  /**
45
- * Express middleware: appends a `finish` listener and writes one aggregate row per request to Redis.
46
- * Does not check `METRICS_ENABLED`; the app should only mount this when HTTP Redis metrics should run.
47
- *
48
52
  * @param {import('http').IncomingMessage} req
49
53
  * @param {import('http').ServerResponse} res
50
54
  * @param {function} next
51
55
  */
52
56
  trackHttpRequestMiddleware = (req, res, next) => {
57
+ if (!this._enabled) {
58
+ next()
59
+ return
60
+ }
53
61
  if (req.method === 'OPTIONS') {
54
62
  next()
55
63
  return
@@ -3,13 +3,7 @@ const os = require('os')
3
3
  const { BaseMetricsClient } = require('./baseMetricsClient')
4
4
 
5
5
  /**
6
- * MetricsClient handles Prometheus metrics collection and push.
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
- *
6
+ * Process gauges (CPU, memory, event loop lag, uptime, etc.) on top of {@link BaseMetricsClient}.
13
7
  * @extends BaseMetricsClient
14
8
  */
15
9
  class MetricsClient extends BaseMetricsClient {
@@ -19,7 +13,6 @@ class MetricsClient extends BaseMetricsClient {
19
13
  * @param {string} [config.dynoId] Dyno/instance ID
20
14
  * @param {string} [config.processType] Process type (web, worker, etc.)
21
15
  * @param {boolean} [config.enabled] Enable metrics collection
22
- * @param {boolean} [config.httpMetricsEnabled] Enable HTTP request metrics (`app_requests_total`, `app_requests_total_duration`); defaults from `METRICS_HTTP_ENABLED === 'true'`
23
16
  * @param {boolean} [config.logValues] Log metrics values to console
24
17
  * @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
18
  * @param {string} [config.pushgatewaySecret] Basic auth secret (Base64 of `user:password`)
@@ -32,11 +25,6 @@ class MetricsClient extends BaseMetricsClient {
32
25
  constructor(config = {}) {
33
26
  super(config)
34
27
 
35
- this.httpMetricsEnabled =
36
- config.httpMetricsEnabled !== undefined
37
- ? config.httpMetricsEnabled
38
- : process.env.METRICS_HTTP_ENABLED === 'true'
39
-
40
28
  this._lastUsageMicros = 0
41
29
  this._lastCheckTime = Date.now()
42
30
 
@@ -83,30 +71,6 @@ class MetricsClient extends BaseMetricsClient {
83
71
  help: 'How long the process has been running',
84
72
  updateFn: process.uptime,
85
73
  })
86
-
87
- if (this.httpMetricsEnabled) {
88
- this.createCounter({
89
- name: 'app_requests_total',
90
- help: 'Total number of HTTP requests',
91
- labelNames: this.withDefaultLabelsWithoutDynoId([
92
- 'method',
93
- 'route',
94
- 'status_code',
95
- ]),
96
- useLabelsWithoutDynoId: true,
97
- })
98
-
99
- this.createCounter({
100
- name: 'app_requests_total_duration',
101
- help: 'Total duration of HTTP requests in milliseconds',
102
- labelNames: this.withDefaultLabelsWithoutDynoId([
103
- 'method',
104
- 'route',
105
- 'status_code',
106
- ]),
107
- useLabelsWithoutDynoId: true,
108
- })
109
- }
110
74
  }
111
75
 
112
76
  /**
@@ -206,62 +170,6 @@ class MetricsClient extends BaseMetricsClient {
206
170
  setImmediate(() => resolve(Date.now() - start))
207
171
  })
208
172
  }
209
-
210
- /**
211
- * Increment HTTP request counters (in-process). No-op if `httpMetricsEnabled` is false.
212
- *
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
- */
219
- trackHttpRequest({ method, route, status_code, duration }) {
220
- if (!this.httpMetricsEnabled) return
221
-
222
- this.countersFunctions?.app_requests_total({
223
- method,
224
- route,
225
- status_code,
226
- })
227
- this.countersFunctions?.app_requests_total_duration(
228
- {
229
- method,
230
- route,
231
- status_code,
232
- },
233
- duration
234
- )
235
- }
236
-
237
- /**
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
244
- */
245
- trackHttpRequestMiddleware = (req, res, next) => {
246
- if (!this.enabled || !this.httpMetricsEnabled || req.method === 'OPTIONS') {
247
- next()
248
- return
249
- }
250
-
251
- const start = Date.now()
252
- res.on('finish', () => {
253
- const route = req.route?.path || req.path || 'unknown'
254
-
255
- this.trackHttpRequest({
256
- method: req.method,
257
- route,
258
- status_code: res.statusCode,
259
- duration: Date.now() - start,
260
- })
261
- })
262
-
263
- next()
264
- }
265
173
  }
266
174
 
267
175
  module.exports = { MetricsClient }
@@ -1,11 +1,7 @@
1
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`.
2
+ * Helpers for resolving `processType` and optional silent exit when a specialized metrics client
3
+ * is constructed on the wrong dyno (no log output). If `METRICS_ENABLED` is not `true`, the check
4
+ * is skipped so the process is not stopped.
9
5
  *
10
6
  * @module metrics/metricsProcessTypeUtils
11
7
  */
@@ -49,14 +45,26 @@ function resolveMetricsProcessType(metricsConfig, defaultProcessType) {
49
45
  )
50
46
  }
51
47
 
48
+ /**
49
+ * @param {{ processType?: string, enabled?: boolean }} metricsConfig
50
+ * @returns {boolean}
51
+ */
52
+ function shouldEnforceProcessType(metricsConfig) {
53
+ return (
54
+ (metricsConfig.enabled ?? process.env.METRICS_ENABLED === 'true') === true
55
+ )
56
+ }
57
+
52
58
  /**
53
59
  * Exit with no logs if the resolved process type is not exactly `expectedProcessType`.
60
+ * No-op when `METRICS_ENABLED` is not `true` (avoids exit/restart loops when metrics are off).
54
61
  *
55
- * @param {{ processType?: string }} metricsConfig
62
+ * @param {{ processType?: string, enabled?: boolean }} metricsConfig
56
63
  * @param {string} expectedProcessType - Single allowed value (use a module constant, e.g. {@link METRICS_PROCESS_TYPE_DATABASE})
57
64
  * @returns {void}
58
65
  */
59
66
  function exitUnlessProcessTypeIs(metricsConfig, expectedProcessType) {
67
+ if (!shouldEnforceProcessType(metricsConfig)) return
60
68
  const resolved = resolveMetricsProcessType(metricsConfig, expectedProcessType)
61
69
  if (resolved !== expectedProcessType) {
62
70
  process.exit(0)
@@ -65,8 +73,9 @@ function exitUnlessProcessTypeIs(metricsConfig, expectedProcessType) {
65
73
 
66
74
  /**
67
75
  * Exit with no logs if the resolved process type is not in `allowedProcessTypes`.
76
+ * No-op when `METRICS_ENABLED` is not `true`.
68
77
  *
69
- * @param {{ processType?: string }} metricsConfig
78
+ * @param {{ processType?: string, enabled?: boolean }} metricsConfig
70
79
  * @param {readonly string[]} allowedProcessTypes
71
80
  * @param {string} defaultWhenUnspecified - Used only to resolve when config/env omit `processType` (e.g. {@link METRICS_PROCESS_TYPE_QUEUE} for {@link RedisMetricsClient})
72
81
  * @returns {void}
@@ -76,6 +85,7 @@ function exitUnlessProcessTypeIn(
76
85
  allowedProcessTypes,
77
86
  defaultWhenUnspecified
78
87
  ) {
88
+ if (!shouldEnforceProcessType(metricsConfig)) return
79
89
  const resolved = resolveMetricsProcessType(
80
90
  metricsConfig,
81
91
  defaultWhenUnspecified
@@ -87,6 +97,7 @@ function exitUnlessProcessTypeIn(
87
97
 
88
98
  module.exports = {
89
99
  resolveMetricsProcessType,
100
+ shouldEnforceProcessType,
90
101
  exitUnlessProcessTypeIs,
91
102
  exitUnlessProcessTypeIn,
92
103
  METRICS_PROCESS_TYPE_DATABASE,