@adalo/metrics 0.1.175 → 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.
- package/README.md +13 -14
- package/__tests__/httpMetricsRedisCollector.test.js +105 -1
- package/__tests__/httpMetricsRedisRecorder.test.js +22 -3
- package/lib/metrics/baseMetricsClient.d.ts +1 -1
- package/lib/metrics/baseMetricsClient.d.ts.map +1 -1
- package/lib/metrics/baseMetricsClient.js +1 -1
- package/lib/metrics/baseMetricsClient.js.map +1 -1
- package/lib/metrics/httpMetricsRedisCollector.d.ts +30 -24
- package/lib/metrics/httpMetricsRedisCollector.d.ts.map +1 -1
- package/lib/metrics/httpMetricsRedisCollector.js +67 -34
- package/lib/metrics/httpMetricsRedisCollector.js.map +1 -1
- package/lib/metrics/httpMetricsRedisRecorder.d.ts +9 -17
- package/lib/metrics/httpMetricsRedisRecorder.d.ts.map +1 -1
- package/lib/metrics/httpMetricsRedisRecorder.js +18 -16
- package/lib/metrics/httpMetricsRedisRecorder.js.map +1 -1
- package/lib/metrics/metricsClient.d.ts +1 -63
- package/lib/metrics/metricsClient.d.ts.map +1 -1
- package/lib/metrics/metricsClient.js +1 -77
- package/lib/metrics/metricsClient.js.map +1 -1
- package/lib/metrics/metricsProcessTypeUtils.d.ts +17 -9
- package/lib/metrics/metricsProcessTypeUtils.d.ts.map +1 -1
- package/lib/metrics/metricsProcessTypeUtils.js +18 -9
- package/lib/metrics/metricsProcessTypeUtils.js.map +1 -1
- package/package.json +3 -2
- package/src/metrics/baseMetricsClient.js +1 -1
- package/src/metrics/httpMetricsRedisCollector.js +80 -35
- package/src/metrics/httpMetricsRedisRecorder.js +23 -15
- package/src/metrics/metricsClient.js +1 -93
- package/src/metrics/metricsProcessTypeUtils.js +20 -9
- 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
|
|
34
|
-
* is constructed on the wrong dyno
|
|
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":"
|
|
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
|
|
5
|
-
* is constructed on the wrong dyno
|
|
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
|
|
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.
|
|
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
|
-
"
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
9
|
-
*
|
|
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
|
|
20
|
-
* @param {string} [config.appName]
|
|
21
|
-
* @param {string} [config.dynoId] Dyno/instance ID
|
|
22
|
-
* @param {string} [config.processType]
|
|
23
|
-
* @param {boolean} [config.enabled] Enable collection
|
|
24
|
-
* @param {boolean} [config.logValues] Log
|
|
25
|
-
* @param {string} [config.pushgatewayUrl] VM-agent import URL
|
|
26
|
-
* @param {string} [config.pushgatewaySecret] Basic auth secret (
|
|
27
|
-
* @param {number} [config.intervalSec] Push interval (
|
|
28
|
-
* @param {boolean} [config.removeOldMetrics]
|
|
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
|
|
32
|
-
* @param {number} [config.ttlSec]
|
|
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,27 +54,69 @@ class HttpMetricsRedisCollector extends BaseMetricsClient {
|
|
|
56
54
|
ttlSec: config.ttlSec,
|
|
57
55
|
})
|
|
58
56
|
|
|
59
|
-
|
|
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:
|
|
75
|
+
labelNames: httpLabelNames,
|
|
76
|
+
useLabelsWithoutDynoId: true,
|
|
66
77
|
})
|
|
67
78
|
|
|
68
79
|
this.createCounter({
|
|
69
80
|
name: 'app_requests_total_duration',
|
|
70
81
|
help: 'Total duration of HTTP requests in milliseconds',
|
|
71
|
-
labelNames:
|
|
82
|
+
labelNames: httpLabelNames,
|
|
83
|
+
useLabelsWithoutDynoId: true,
|
|
72
84
|
})
|
|
73
85
|
}
|
|
74
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
|
+
|
|
75
114
|
/**
|
|
76
|
-
* Drains Redis into counters, then runs gauge updates and VM-agent push ({@link BaseMetricsClient#_pushMetrics}).
|
|
77
115
|
* @returns {Promise<void>}
|
|
78
116
|
*/
|
|
79
117
|
pushMetrics = async () => {
|
|
118
|
+
this._maybeRotateHttpPushInstance()
|
|
119
|
+
|
|
80
120
|
if (
|
|
81
121
|
this._store &&
|
|
82
122
|
this.countersFunctions?.app_requests_total &&
|
|
@@ -96,10 +136,11 @@ class HttpMetricsRedisCollector extends BaseMetricsClient {
|
|
|
96
136
|
row.labels.route,
|
|
97
137
|
row.labels.status_code
|
|
98
138
|
)
|
|
139
|
+
const L = this._labelsForHttpPush(row.labels)
|
|
99
140
|
if (!this._httpCounterPrimedKeys.has(key)) {
|
|
100
141
|
this._httpCounterPrimedKeys.add(key)
|
|
101
|
-
applyCount(
|
|
102
|
-
applyDur(
|
|
142
|
+
applyCount(L, 0)
|
|
143
|
+
applyDur(L, 0)
|
|
103
144
|
primedAny = true
|
|
104
145
|
}
|
|
105
146
|
}
|
|
@@ -107,9 +148,10 @@ class HttpMetricsRedisCollector extends BaseMetricsClient {
|
|
|
107
148
|
await this.gatewayPush()
|
|
108
149
|
}
|
|
109
150
|
for (const row of rows) {
|
|
110
|
-
|
|
151
|
+
const L = this._labelsForHttpPush(row.labels)
|
|
152
|
+
applyCount(L, row.count)
|
|
111
153
|
if (row.dur > 0) {
|
|
112
|
-
applyDur(
|
|
154
|
+
applyDur(L, row.dur)
|
|
113
155
|
}
|
|
114
156
|
}
|
|
115
157
|
}
|
|
@@ -118,4 +160,7 @@ class HttpMetricsRedisCollector extends BaseMetricsClient {
|
|
|
118
160
|
}
|
|
119
161
|
}
|
|
120
162
|
|
|
121
|
-
module.exports = {
|
|
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
|
|
13
|
-
* @param {string} [opts.appName]
|
|
14
|
-
* @param {string} [opts.processType]
|
|
15
|
-
* @param {number} [opts.ttlSec]
|
|
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(
|
|
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
|
-
*
|
|
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
|
|
3
|
-
* is constructed on the wrong dyno
|
|
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,
|
|
@@ -1,19 +0,0 @@
|
|
|
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
|
-
| **Prime 0 on new routes** | The first time a `(method, route, status_code)` appears **in this process**, the collector `inc(..., 0)` for count+duration on **that label set only**, **POST**, then applies drained counts for **all** rows. Existing series are **not** touched in the prime loop — e.g. in-memory `b` was **18**, drain has new `a:1` and `b:20`: prime push exports **`a=0`, `b=18`**; after apply, final push has **`a=1`, `b=38`**. If **all** label sets were already seen, there is **no** extra prime `POST`. |
|
|
18
|
-
|
|
19
|
-
For in-process HTTP metrics **without** Redis, see **`MetricsClient`** + `METRICS_HTTP_ENABLED` in **[docs/METRICS.md](../../docs/METRICS.md#http-metrics-two-valid-designs)**.
|