@adalo/metrics 0.0.0-staging.23 → 0.0.0-staging.24
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/__tests__/httpMetricsRedisStore.test.js +51 -0
- package/lib/metrics/httpMetricsRedisRecorder.d.ts.map +1 -1
- package/lib/metrics/httpMetricsRedisRecorder.js +3 -3
- package/lib/metrics/httpMetricsRedisRecorder.js.map +1 -1
- package/lib/metrics/httpMetricsRedisStore.d.ts +8 -1
- package/lib/metrics/httpMetricsRedisStore.d.ts.map +1 -1
- package/lib/metrics/httpMetricsRedisStore.js +34 -6
- package/lib/metrics/httpMetricsRedisStore.js.map +1 -1
- package/package.json +5 -5
- package/src/metrics/httpMetricsRedisRecorder.js +3 -5
- package/src/metrics/httpMetricsRedisStore.js +37 -9
|
@@ -97,6 +97,57 @@ describe('HttpMetricsRedisStore', () => {
|
|
|
97
97
|
expect(redis.eval).not.toHaveBeenCalled()
|
|
98
98
|
})
|
|
99
99
|
|
|
100
|
+
it('applies aggregated count and summed duration for one route (many requests → one field)', async () => {
|
|
101
|
+
const redis = createRedisV3Mock()
|
|
102
|
+
redis.set.mockImplementation((key, val, mode, ttl, nx, cb) => {
|
|
103
|
+
cb(null, 'OK')
|
|
104
|
+
})
|
|
105
|
+
const field = buildFieldKey('GET', '/api/items', 200, '', '')
|
|
106
|
+
redis.eval.mockImplementation((lua, numKeys, k1, k2, cb) => {
|
|
107
|
+
cb(null, [
|
|
108
|
+
[field, '100'],
|
|
109
|
+
[field, '4500'],
|
|
110
|
+
])
|
|
111
|
+
})
|
|
112
|
+
redis.del.mockImplementation((key, cb) => {
|
|
113
|
+
if (cb) {
|
|
114
|
+
cb()
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
const store = new HttpMetricsRedisStore({
|
|
119
|
+
redisClient: redis,
|
|
120
|
+
appName: 'app',
|
|
121
|
+
processType: 'web',
|
|
122
|
+
})
|
|
123
|
+
const applyCount = jest.fn()
|
|
124
|
+
const applyDuration = jest.fn()
|
|
125
|
+
|
|
126
|
+
const ok = await store.flushToCounters(applyCount, applyDuration)
|
|
127
|
+
|
|
128
|
+
expect(ok).toBe(true)
|
|
129
|
+
expect(applyCount).toHaveBeenCalledWith(
|
|
130
|
+
{
|
|
131
|
+
method: 'GET',
|
|
132
|
+
route: '/api/items',
|
|
133
|
+
status_code: '200',
|
|
134
|
+
appId: '',
|
|
135
|
+
databaseId: '',
|
|
136
|
+
},
|
|
137
|
+
100
|
|
138
|
+
)
|
|
139
|
+
expect(applyDuration).toHaveBeenCalledWith(
|
|
140
|
+
{
|
|
141
|
+
method: 'GET',
|
|
142
|
+
route: '/api/items',
|
|
143
|
+
status_code: '200',
|
|
144
|
+
appId: '',
|
|
145
|
+
databaseId: '',
|
|
146
|
+
},
|
|
147
|
+
4500
|
|
148
|
+
)
|
|
149
|
+
})
|
|
150
|
+
|
|
100
151
|
it('drains hashes and applies count and duration', async () => {
|
|
101
152
|
const redis = createRedisV3Mock()
|
|
102
153
|
redis.set.mockImplementation((key, val, mode, ttl, nx, cb) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"httpMetricsRedisRecorder.d.ts","sourceRoot":"","sources":["../../src/metrics/httpMetricsRedisRecorder.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"httpMetricsRedisRecorder.d.ts","sourceRoot":"","sources":["../../src/metrics/httpMetricsRedisRecorder.js"],"names":[],"mappings":"AAOA;;;;;GAKG;AACH;IACE;;;;;;OAMG;IACH;QAL6C,WAAW,EAA7C,OAAO,OAAO,EAAE,WAAW;QACd,OAAO,EAApB,MAAM;QACO,WAAW,EAAxB,MAAM;QACQ,MAAM;OAc9B;IARC,oBAA8B;IAC9B,gBAAsB;IACtB,8BAKE;IAGJ;;;;;;;;OAQG;IACH;QAP0B,MAAM,EAArB,MAAM;QACS,KAAK,EAApB,MAAM;QACS,WAAW,EAA1B,MAAM;QACU,KAAK;QACL,UAAU;QACX,QAAQ,EAAvB,MAAM;aAgBhB;IAED;;;;;;;OAOG;IACH,kCAJW,OAAO,MAAM,EAAE,eAAe,OAC9B,OAAO,MAAM,EAAE,cAAc,0BAkCvC;CACF"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
HttpMetricsRedisStore
|
|
4
|
+
HttpMetricsRedisStore,
|
|
5
|
+
httpMetricsTraceLog
|
|
5
6
|
} = require('./httpMetricsRedisStore');
|
|
6
|
-
const LOG = '[http-metrics-redis]';
|
|
7
7
|
function trunc(s, max = 120) {
|
|
8
8
|
const t = String(s);
|
|
9
9
|
return t.length > max ? `${t.slice(0, max - 3)}...` : t;
|
|
@@ -59,7 +59,7 @@ class HttpMetricsRedisRecorder {
|
|
|
59
59
|
databaseId = '',
|
|
60
60
|
duration
|
|
61
61
|
}) {
|
|
62
|
-
|
|
62
|
+
httpMetricsTraceLog(`1_track pid=${process.pid} app=${this.appName} segment=${this.processType} ` + `method=${method} route=${trunc(route)} status=${status_code} durationMs=${duration} ` + `appId=${trunc(appId || '—', 40)} databaseId=${trunc(databaseId || '—', 40)}`);
|
|
63
63
|
this._store.record(method, route, status_code, appId, databaseId, duration);
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"httpMetricsRedisRecorder.js","names":["HttpMetricsRedisStore","
|
|
1
|
+
{"version":3,"file":"httpMetricsRedisRecorder.js","names":["HttpMetricsRedisStore","httpMetricsTraceLog","require","trunc","s","max","t","String","length","slice","HttpMetricsRedisRecorder","constructor","redisClient","appName","processType","ttlSec","Error","_store","trackHttpRequest","method","route","status_code","appId","databaseId","duration","process","pid","record","trackHttpRequestMiddleware","req","res","next","start","Date","now","on","path","params","body","query","datasourceId","statusCode","module","exports"],"sources":["../../src/metrics/httpMetricsRedisRecorder.js"],"sourcesContent":["const { HttpMetricsRedisStore, httpMetricsTraceLog } = require('./httpMetricsRedisStore')\n\nfunction trunc(s, max = 120) {\n const t = String(s)\n return t.length > max ? `${t.slice(0, max - 3)}...` : t\n}\n\n/**\n * Records HTTP request aggregates only to Redis (no in-process Prometheus counters on this path).\n * Pair with {@link HttpMetricsRedisCollector} on a drain process to flush into counters and push to the VM-agent.\n *\n * @see HttpMetricsRedisStore\n */\nclass HttpMetricsRedisRecorder {\n /**\n * @param {Object} opts\n * @param {import('redis').RedisClient} opts.redisClient **Required.** Injected client (same pattern as {@link RedisMetricsClient}).\n * @param {string} opts.appName Application name (must match collector; typically `BUILD_APP_NAME`).\n * @param {string} opts.processType Logical process segment in Redis keys (e.g. `web`; must match collector’s key segment).\n * @param {number} [opts.ttlSec] Redis hash key TTL in seconds (sliding; default 120).\n */\n constructor({ redisClient, appName, processType, ttlSec }) {\n if (redisClient == null) {\n throw new Error('HttpMetricsRedisRecorder: redisClient is required')\n }\n this.processType = processType\n this.appName = appName\n this._store = new HttpMetricsRedisStore({\n redisClient,\n appName,\n processType,\n ttlSec,\n })\n }\n\n /**\n * @param {Object} params\n * @param {string} params.method\n * @param {string} params.route\n * @param {number} params.status_code\n * @param {string} [params.appId]\n * @param {string} [params.databaseId]\n * @param {number} params.duration\n */\n trackHttpRequest({\n method,\n route,\n status_code,\n appId = '',\n databaseId = '',\n duration,\n }) {\n httpMetricsTraceLog(\n `1_track pid=${process.pid} app=${this.appName} segment=${this.processType} ` +\n `method=${method} route=${trunc(route)} status=${status_code} durationMs=${duration} ` +\n `appId=${trunc(appId || '—', 40)} databaseId=${trunc(databaseId || '—', 40)}`\n )\n this._store.record(method, route, status_code, appId, databaseId, duration)\n }\n\n /**\n * Express middleware: appends a `finish` listener and writes one aggregate row per request to Redis.\n * Does not check `METRICS_ENABLED`; the app should only mount this when HTTP Redis metrics should run.\n *\n * @param {import('http').IncomingMessage} req\n * @param {import('http').ServerResponse} res\n * @param {function} next\n */\n trackHttpRequestMiddleware = (req, res, next) => {\n if (req.method === 'OPTIONS') {\n next()\n return\n }\n\n const start = Date.now()\n res.on('finish', () => {\n const route = req.route?.path || req.path || 'unknown'\n const appId =\n req.params?.appId || req.body?.appId || req.query?.appId || ''\n const databaseId =\n req.params?.databaseId ||\n req.body?.databaseId ||\n req.query?.databaseId ||\n req.params?.datasourceId ||\n req.body?.datasourceId ||\n req.query?.datasourceId ||\n ''\n\n this.trackHttpRequest({\n method: req.method,\n route,\n status_code: res.statusCode,\n appId,\n databaseId,\n duration: Date.now() - start,\n })\n })\n\n next()\n }\n}\n\nmodule.exports = { HttpMetricsRedisRecorder }\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
* Expects `redis` v3-style API: `multi().hincrby().expire().exec(cb)`, `eval`, `set`, `del`.
|
|
4
4
|
*
|
|
5
5
|
* **Stored metrics:** two hashes per app/segment — `:count` (HINCRBY per route group) and `:dur`
|
|
6
|
-
* (sum of duration ms per same field).
|
|
6
|
+
* (sum of duration ms per same field). Same `(method, route, status, appId, databaseId)` → same hash field;
|
|
7
|
+
* many requests in an interval **add** to count and **sum** durations (Redis `HINCRBY`). Drain applies those
|
|
8
|
+
* totals to `app_requests_total` / `app_requests_total_duration`.
|
|
7
9
|
*/
|
|
8
10
|
export class HttpMetricsRedisStore {
|
|
9
11
|
/**
|
|
@@ -68,4 +70,9 @@ export function isRedisPeerInstalled(): boolean;
|
|
|
68
70
|
* @type {number}
|
|
69
71
|
*/
|
|
70
72
|
export const DEFAULT_HTTP_METRICS_REDIS_TTL_SEC: number;
|
|
73
|
+
/**
|
|
74
|
+
* Stdout trace (uses **console.log**, not warn — same as typical app request logs).
|
|
75
|
+
* @param {string} body - Line body after `[http-metrics-redis]` prefix.
|
|
76
|
+
*/
|
|
77
|
+
export function httpMetricsTraceLog(body: string): void;
|
|
71
78
|
//# sourceMappingURL=httpMetricsRedisStore.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"httpMetricsRedisStore.d.ts","sourceRoot":"","sources":["../../src/metrics/httpMetricsRedisStore.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"httpMetricsRedisStore.d.ts","sourceRoot":"","sources":["../../src/metrics/httpMetricsRedisStore.js"],"names":[],"mappings":"AAmFA;;;;;;;;GAQG;AACH;IACE;;;;;;OAMG;IACH;QAL6C,WAAW,EAA7C,OAAO,OAAO,EAAE,WAAW;QACd,OAAO,EAApB,MAAM;QACO,WAAW,EAAxB,MAAM;QACQ,MAAM;OAiB9B;IAXC,qCAA0B;IAC1B,eAGwC;IAIxC,iBAAiD;IACjD,eAA6C;IAC7C,gBAAqD;IAGvD;;;OAGG;IACH,sBAEC;IAED;;;;;;;OAOG;IACH,eAPW,MAAM,SACN,MAAM,cACN,MAAM,SACN,MAAM,cACN,MAAM,cACN,MAAM,QAyBhB;IAED;;;;OAIG;IACH,qCAJoB,MAAM,SAAS,MAAM,KAAK,IAAI,0BAC9B,MAAM,SAAS,MAAM,KAAK,IAAI,GACrC,QAAQ,OAAO,CAAC,CAgG5B;CACF;AAtMD;;;;;;;GAOG;AACH,sCAPW,MAAM,SACN,MAAM,cACN,MAAM,SACN,MAAM,cACN,MAAM,GACJ,MAAM,CAIlB;AAtED;;;GAGG;AACH,wBAFU,MAAM,CAEQ;AA4CxB;;GAEG;AACH,wCAFa,OAAO,CASnB;AApDD;;;GAGG;AACH,iDAFU,MAAM,CAE8B;AAqB9C;;;GAGG;AACH,0CAFW,MAAM,QAIhB"}
|
|
@@ -12,6 +12,31 @@ const FIELD_SEP = '\x1e';
|
|
|
12
12
|
*/
|
|
13
13
|
const DEFAULT_HTTP_METRICS_REDIS_TTL_SEC = 120;
|
|
14
14
|
const LOG = '[http-metrics-redis]';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* When Node runs behind `cluster`, HTTP is usually served from workers — include worker id in traces.
|
|
18
|
+
* @returns {string}
|
|
19
|
+
*/
|
|
20
|
+
function clusterHint() {
|
|
21
|
+
try {
|
|
22
|
+
// eslint-disable-next-line global-require
|
|
23
|
+
const c = require('cluster');
|
|
24
|
+
if (c.isWorker && c.worker != null) {
|
|
25
|
+
return ` cluster_worker=${c.worker.id}`;
|
|
26
|
+
}
|
|
27
|
+
} catch (_) {
|
|
28
|
+
/* cluster not in use */
|
|
29
|
+
}
|
|
30
|
+
return '';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Stdout trace (uses **console.log**, not warn — same as typical app request logs).
|
|
35
|
+
* @param {string} body - Line body after `[http-metrics-redis]` prefix.
|
|
36
|
+
*/
|
|
37
|
+
function httpMetricsTraceLog(body) {
|
|
38
|
+
console.log(`${LOG} ${body}${clusterHint()}`);
|
|
39
|
+
}
|
|
15
40
|
const DRAIN_LUA = `
|
|
16
41
|
local function drain(key)
|
|
17
42
|
local v = redis.call('HGETALL', key)
|
|
@@ -60,7 +85,9 @@ function hgetallPairsToObject(pairs) {
|
|
|
60
85
|
* Expects `redis` v3-style API: `multi().hincrby().expire().exec(cb)`, `eval`, `set`, `del`.
|
|
61
86
|
*
|
|
62
87
|
* **Stored metrics:** two hashes per app/segment — `:count` (HINCRBY per route group) and `:dur`
|
|
63
|
-
* (sum of duration ms per same field).
|
|
88
|
+
* (sum of duration ms per same field). Same `(method, route, status, appId, databaseId)` → same hash field;
|
|
89
|
+
* many requests in an interval **add** to count and **sum** durations (Redis `HINCRBY`). Drain applies those
|
|
90
|
+
* totals to `app_requests_total` / `app_requests_total_duration`.
|
|
64
91
|
*/
|
|
65
92
|
class HttpMetricsRedisStore {
|
|
66
93
|
/**
|
|
@@ -113,7 +140,7 @@ class HttpMetricsRedisStore {
|
|
|
113
140
|
console.error('[HttpMetricsRedisStore] record failed:', err.message);
|
|
114
141
|
return;
|
|
115
142
|
}
|
|
116
|
-
|
|
143
|
+
httpMetricsTraceLog(`2_redis_write pid=${process.pid} countKey=${this.countKey} durKey=${this.durKey} ok`);
|
|
117
144
|
});
|
|
118
145
|
} catch (e) {
|
|
119
146
|
console.error('[HttpMetricsRedisStore] record:', e.message);
|
|
@@ -136,7 +163,7 @@ class HttpMetricsRedisStore {
|
|
|
136
163
|
return new Promise(resolve => {
|
|
137
164
|
client.set(this.lockKey, '1', 'EX', 25, 'NX', (setErr, ok) => {
|
|
138
165
|
if (setErr || ok !== 'OK') {
|
|
139
|
-
|
|
166
|
+
httpMetricsTraceLog(`3_redis_drain_skip pid=${process.pid} countKey=${this.countKey} reason=${setErr ? `error:${setErr.message}` : 'lock_not_acquired'}`);
|
|
140
167
|
resolve(false);
|
|
141
168
|
return;
|
|
142
169
|
}
|
|
@@ -151,7 +178,7 @@ class HttpMetricsRedisStore {
|
|
|
151
178
|
}
|
|
152
179
|
try {
|
|
153
180
|
if (!raw || !Array.isArray(raw) || raw.length < 2) {
|
|
154
|
-
|
|
181
|
+
httpMetricsTraceLog(`3_redis_drain pid=${process.pid} countKey=${this.countKey} hash_fields=0 sum_requests=0 (empty_after_lua)`);
|
|
155
182
|
finish();
|
|
156
183
|
return;
|
|
157
184
|
}
|
|
@@ -187,7 +214,7 @@ class HttpMetricsRedisStore {
|
|
|
187
214
|
applyDuration(labels, dur);
|
|
188
215
|
}
|
|
189
216
|
}
|
|
190
|
-
|
|
217
|
+
httpMetricsTraceLog(`3_redis_drain pid=${process.pid} countKey=${this.countKey} hash_fields=${fieldKeys.length} sum_requests=${sumRequests} sample=${samples.join(' | ') || '—'}`);
|
|
191
218
|
} catch (e) {
|
|
192
219
|
console.error('[HttpMetricsRedisStore] flush apply failed:', e.message);
|
|
193
220
|
}
|
|
@@ -202,6 +229,7 @@ module.exports = {
|
|
|
202
229
|
buildFieldKey,
|
|
203
230
|
FIELD_SEP,
|
|
204
231
|
isRedisPeerInstalled,
|
|
205
|
-
DEFAULT_HTTP_METRICS_REDIS_TTL_SEC
|
|
232
|
+
DEFAULT_HTTP_METRICS_REDIS_TTL_SEC,
|
|
233
|
+
httpMetricsTraceLog
|
|
206
234
|
};
|
|
207
235
|
//# sourceMappingURL=httpMetricsRedisStore.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"httpMetricsRedisStore.js","names":["FIELD_SEP","DEFAULT_HTTP_METRICS_REDIS_TTL_SEC","LOG","DRAIN_LUA","isRedisPeerInstalled","require","resolve","buildFieldKey","method","route","statusCode","appId","databaseId","String","join","hgetallPairsToObject","pairs","o","length","i","HttpMetricsRedisStore","constructor","redisClient","appName","processType","ttlSec","Error","_client","keySeg","encodeURIComponent","countKey","durKey","lockKey","_ensureClient","record","durationMs","client","field","dur","Math","max","round","Number","multi","hincrby","expire","exec","err","console","error","message","warn","process","pid","e","flushToCounters","applyCount","applyDuration","Promise","set","setErr","ok","eval","evalErr","raw","finish","del","Array","isArray","counts","durs","fieldKeys","Object","keys","sumRequests","samples","count","parseInt","parts","split","m","statusStr","aid","did","push","labels","status_code","module","exports"],"sources":["../../src/metrics/httpMetricsRedisStore.js"],"sourcesContent":["/**\n * Record separator for hash fields (avoids collisions when route contains \"|\").\n * @type {string}\n */\nconst FIELD_SEP = '\\x1e'\n\n/**\n * Default Redis key TTL in seconds (sliding: refreshed on each `record` write).\n * @type {number}\n */\nconst DEFAULT_HTTP_METRICS_REDIS_TTL_SEC = 120\n\nconst LOG = '[http-metrics-redis]'\n\nconst DRAIN_LUA = `\nlocal function drain(key)\n local v = redis.call('HGETALL', key)\n redis.call('DEL', key)\n return v\nend\nreturn {drain(KEYS[1]), drain(KEYS[2])}\n`\n\n/**\n * @returns {boolean} Whether the `redis` npm package is resolvable (optional peer).\n */\nfunction isRedisPeerInstalled() {\n try {\n require.resolve('redis')\n return true\n } catch {\n return false\n }\n}\n\n/**\n * @param {string} method\n * @param {string} route\n * @param {number} statusCode\n * @param {string} appId\n * @param {string} databaseId\n * @returns {string}\n */\nfunction buildFieldKey(method, route, statusCode, appId, databaseId) {\n return [method, route, String(statusCode), appId, databaseId].join(FIELD_SEP)\n}\n\nfunction hgetallPairsToObject(pairs) {\n const o = {}\n if (!pairs || !pairs.length) {\n return o\n }\n for (let i = 0; i < pairs.length; i += 2) {\n o[pairs[i]] = pairs[i + 1]\n }\n return o\n}\n\n/**\n * Redis HTTP aggregate store. Uses an **injected** client (same pattern as {@link RedisMetricsClient}).\n * Expects `redis` v3-style API: `multi().hincrby().expire().exec(cb)`, `eval`, `set`, `del`.\n *\n * **Stored metrics:** two hashes per app/segment — `:count` (HINCRBY per route group) and `:dur`\n * (sum of duration ms per same field). Hash field = `method\\\\x1eroute\\\\x1estatus\\\\x1eappId\\\\x1edatabaseId`.\n */\nclass HttpMetricsRedisStore {\n /**\n * @param {Object} opts\n * @param {import('redis').RedisClient} opts.redisClient\n * @param {string} opts.appName BUILD_APP_NAME (key segment)\n * @param {string} opts.processType logical process for key (e.g. web)\n * @param {number} [opts.ttlSec] Expire keys after this many seconds (default 120)\n */\n constructor({ redisClient, appName, processType, ttlSec }) {\n if (redisClient == null) {\n throw new Error('HttpMetricsRedisStore: redisClient is required')\n }\n this._client = redisClient\n this.ttlSec =\n typeof ttlSec === 'number' && ttlSec > 0\n ? ttlSec\n : DEFAULT_HTTP_METRICS_REDIS_TTL_SEC\n const keySeg = `${encodeURIComponent(appName)}:${encodeURIComponent(\n processType\n )}`\n this.countKey = `metrics:http:v2:${keySeg}:count`\n this.durKey = `metrics:http:v2:${keySeg}:dur`\n this.lockKey = `metrics:http:v2:${keySeg}:drain_lock`\n }\n\n /**\n * @returns {import('redis').RedisClient}\n * @private\n */\n _ensureClient() {\n return this._client\n }\n\n /**\n * @param {string} method\n * @param {string} route\n * @param {number} statusCode\n * @param {string} appId\n * @param {string} databaseId\n * @param {number} durationMs\n */\n record(method, route, statusCode, appId, databaseId, durationMs) {\n try {\n const client = this._ensureClient()\n const field = buildFieldKey(method, route, statusCode, appId, databaseId)\n const dur = Math.max(0, Math.round(Number(durationMs) || 0))\n client\n .multi()\n .hincrby(this.countKey, field, 1)\n .hincrby(this.durKey, field, dur)\n .expire(this.countKey, this.ttlSec)\n .expire(this.durKey, this.ttlSec)\n .exec(err => {\n if (err) {\n console.error('[HttpMetricsRedisStore] record failed:', err.message)\n return\n }\n console.warn(\n `${LOG} 2_redis_write pid=${process.pid} countKey=${this.countKey} durKey=${this.durKey} ok`\n )\n })\n } catch (e) {\n console.error('[HttpMetricsRedisStore] record:', e.message)\n }\n }\n\n /**\n * @param {(labels: Object, value: number) => void} applyCount\n * @param {(labels: Object, value: number) => void} applyDuration\n * @returns {Promise<boolean>}\n */\n flushToCounters(applyCount, applyDuration) {\n let client\n try {\n client = this._ensureClient()\n } catch (e) {\n console.error('[HttpMetricsRedisStore] flush:', e.message)\n return Promise.resolve(false)\n }\n return new Promise(resolve => {\n client.set(this.lockKey, '1', 'EX', 25, 'NX', (setErr, ok) => {\n if (setErr || ok !== 'OK') {\n console.warn(\n `${LOG} 3_redis_drain_skip pid=${process.pid} countKey=${this.countKey} reason=${\n setErr ? `error:${setErr.message}` : 'lock_not_acquired'\n }`\n )\n resolve(false)\n return\n }\n client.eval(\n DRAIN_LUA,\n 2,\n this.countKey,\n this.durKey,\n (evalErr, raw) => {\n const finish = () => {\n client.del(this.lockKey, () => resolve(true))\n }\n\n if (evalErr) {\n console.error(\n '[HttpMetricsRedisStore] drain failed:',\n evalErr.message\n )\n finish()\n return\n }\n\n try {\n if (!raw || !Array.isArray(raw) || raw.length < 2) {\n console.warn(\n `${LOG} 3_redis_drain pid=${process.pid} countKey=${this.countKey} hash_fields=0 sum_requests=0 (empty_after_lua)`\n )\n finish()\n return\n }\n const counts = hgetallPairsToObject(raw[0])\n const durs = hgetallPairsToObject(raw[1])\n const fieldKeys = Object.keys(counts)\n let sumRequests = 0\n const samples = []\n for (const field of fieldKeys) {\n const count = parseInt(counts[field], 10)\n if (!count || count < 1) {\n continue\n }\n const dur = parseInt(durs[field] || '0', 10) || 0\n const parts = field.split(FIELD_SEP)\n if (parts.length !== 5) {\n continue\n }\n const [m, route, statusStr, aid, did] = parts\n sumRequests += count\n if (samples.length < 3) {\n samples.push(`${m} ${route} ${statusStr} x${count}`)\n }\n const labels = {\n method: m,\n route,\n status_code: statusStr,\n appId: aid,\n databaseId: did,\n }\n applyCount(labels, count)\n if (dur > 0) {\n applyDuration(labels, dur)\n }\n }\n console.warn(\n `${LOG} 3_redis_drain pid=${process.pid} countKey=${this.countKey} hash_fields=${\n fieldKeys.length\n } sum_requests=${sumRequests} sample=${samples.join(' | ') || '—'}`\n )\n } catch (e) {\n console.error(\n '[HttpMetricsRedisStore] flush apply failed:',\n e.message\n )\n }\n finish()\n }\n )\n })\n })\n }\n}\n\nmodule.exports = {\n HttpMetricsRedisStore,\n buildFieldKey,\n FIELD_SEP,\n isRedisPeerInstalled,\n DEFAULT_HTTP_METRICS_REDIS_TTL_SEC,\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"httpMetricsRedisStore.js","names":["FIELD_SEP","DEFAULT_HTTP_METRICS_REDIS_TTL_SEC","LOG","clusterHint","c","require","isWorker","worker","id","_","httpMetricsTraceLog","body","console","log","DRAIN_LUA","isRedisPeerInstalled","resolve","buildFieldKey","method","route","statusCode","appId","databaseId","String","join","hgetallPairsToObject","pairs","o","length","i","HttpMetricsRedisStore","constructor","redisClient","appName","processType","ttlSec","Error","_client","keySeg","encodeURIComponent","countKey","durKey","lockKey","_ensureClient","record","durationMs","client","field","dur","Math","max","round","Number","multi","hincrby","expire","exec","err","error","message","process","pid","e","flushToCounters","applyCount","applyDuration","Promise","set","setErr","ok","eval","evalErr","raw","finish","del","Array","isArray","counts","durs","fieldKeys","Object","keys","sumRequests","samples","count","parseInt","parts","split","m","statusStr","aid","did","push","labels","status_code","module","exports"],"sources":["../../src/metrics/httpMetricsRedisStore.js"],"sourcesContent":["/**\n * Record separator for hash fields (avoids collisions when route contains \"|\").\n * @type {string}\n */\nconst FIELD_SEP = '\\x1e'\n\n/**\n * Default Redis key TTL in seconds (sliding: refreshed on each `record` write).\n * @type {number}\n */\nconst DEFAULT_HTTP_METRICS_REDIS_TTL_SEC = 120\n\nconst LOG = '[http-metrics-redis]'\n\n/**\n * When Node runs behind `cluster`, HTTP is usually served from workers — include worker id in traces.\n * @returns {string}\n */\nfunction clusterHint() {\n try {\n // eslint-disable-next-line global-require\n const c = require('cluster')\n if (c.isWorker && c.worker != null) {\n return ` cluster_worker=${c.worker.id}`\n }\n } catch (_) {\n /* cluster not in use */\n }\n return ''\n}\n\n/**\n * Stdout trace (uses **console.log**, not warn — same as typical app request logs).\n * @param {string} body - Line body after `[http-metrics-redis]` prefix.\n */\nfunction httpMetricsTraceLog(body) {\n console.log(`${LOG} ${body}${clusterHint()}`)\n}\n\nconst DRAIN_LUA = `\nlocal function drain(key)\n local v = redis.call('HGETALL', key)\n redis.call('DEL', key)\n return v\nend\nreturn {drain(KEYS[1]), drain(KEYS[2])}\n`\n\n/**\n * @returns {boolean} Whether the `redis` npm package is resolvable (optional peer).\n */\nfunction isRedisPeerInstalled() {\n try {\n require.resolve('redis')\n return true\n } catch {\n return false\n }\n}\n\n/**\n * @param {string} method\n * @param {string} route\n * @param {number} statusCode\n * @param {string} appId\n * @param {string} databaseId\n * @returns {string}\n */\nfunction buildFieldKey(method, route, statusCode, appId, databaseId) {\n return [method, route, String(statusCode), appId, databaseId].join(FIELD_SEP)\n}\n\nfunction hgetallPairsToObject(pairs) {\n const o = {}\n if (!pairs || !pairs.length) {\n return o\n }\n for (let i = 0; i < pairs.length; i += 2) {\n o[pairs[i]] = pairs[i + 1]\n }\n return o\n}\n\n/**\n * Redis HTTP aggregate store. Uses an **injected** client (same pattern as {@link RedisMetricsClient}).\n * Expects `redis` v3-style API: `multi().hincrby().expire().exec(cb)`, `eval`, `set`, `del`.\n *\n * **Stored metrics:** two hashes per app/segment — `:count` (HINCRBY per route group) and `:dur`\n * (sum of duration ms per same field). Same `(method, route, status, appId, databaseId)` → same hash field;\n * many requests in an interval **add** to count and **sum** durations (Redis `HINCRBY`). Drain applies those\n * totals to `app_requests_total` / `app_requests_total_duration`.\n */\nclass HttpMetricsRedisStore {\n /**\n * @param {Object} opts\n * @param {import('redis').RedisClient} opts.redisClient\n * @param {string} opts.appName BUILD_APP_NAME (key segment)\n * @param {string} opts.processType logical process for key (e.g. web)\n * @param {number} [opts.ttlSec] Expire keys after this many seconds (default 120)\n */\n constructor({ redisClient, appName, processType, ttlSec }) {\n if (redisClient == null) {\n throw new Error('HttpMetricsRedisStore: redisClient is required')\n }\n this._client = redisClient\n this.ttlSec =\n typeof ttlSec === 'number' && ttlSec > 0\n ? ttlSec\n : DEFAULT_HTTP_METRICS_REDIS_TTL_SEC\n const keySeg = `${encodeURIComponent(appName)}:${encodeURIComponent(\n processType\n )}`\n this.countKey = `metrics:http:v2:${keySeg}:count`\n this.durKey = `metrics:http:v2:${keySeg}:dur`\n this.lockKey = `metrics:http:v2:${keySeg}:drain_lock`\n }\n\n /**\n * @returns {import('redis').RedisClient}\n * @private\n */\n _ensureClient() {\n return this._client\n }\n\n /**\n * @param {string} method\n * @param {string} route\n * @param {number} statusCode\n * @param {string} appId\n * @param {string} databaseId\n * @param {number} durationMs\n */\n record(method, route, statusCode, appId, databaseId, durationMs) {\n try {\n const client = this._ensureClient()\n const field = buildFieldKey(method, route, statusCode, appId, databaseId)\n const dur = Math.max(0, Math.round(Number(durationMs) || 0))\n client\n .multi()\n .hincrby(this.countKey, field, 1)\n .hincrby(this.durKey, field, dur)\n .expire(this.countKey, this.ttlSec)\n .expire(this.durKey, this.ttlSec)\n .exec(err => {\n if (err) {\n console.error('[HttpMetricsRedisStore] record failed:', err.message)\n return\n }\n httpMetricsTraceLog(\n `2_redis_write pid=${process.pid} countKey=${this.countKey} durKey=${this.durKey} ok`\n )\n })\n } catch (e) {\n console.error('[HttpMetricsRedisStore] record:', e.message)\n }\n }\n\n /**\n * @param {(labels: Object, value: number) => void} applyCount\n * @param {(labels: Object, value: number) => void} applyDuration\n * @returns {Promise<boolean>}\n */\n flushToCounters(applyCount, applyDuration) {\n let client\n try {\n client = this._ensureClient()\n } catch (e) {\n console.error('[HttpMetricsRedisStore] flush:', e.message)\n return Promise.resolve(false)\n }\n return new Promise(resolve => {\n client.set(this.lockKey, '1', 'EX', 25, 'NX', (setErr, ok) => {\n if (setErr || ok !== 'OK') {\n httpMetricsTraceLog(\n `3_redis_drain_skip pid=${process.pid} countKey=${this.countKey} reason=${\n setErr ? `error:${setErr.message}` : 'lock_not_acquired'\n }`\n )\n resolve(false)\n return\n }\n client.eval(\n DRAIN_LUA,\n 2,\n this.countKey,\n this.durKey,\n (evalErr, raw) => {\n const finish = () => {\n client.del(this.lockKey, () => resolve(true))\n }\n\n if (evalErr) {\n console.error(\n '[HttpMetricsRedisStore] drain failed:',\n evalErr.message\n )\n finish()\n return\n }\n\n try {\n if (!raw || !Array.isArray(raw) || raw.length < 2) {\n httpMetricsTraceLog(\n `3_redis_drain pid=${process.pid} countKey=${this.countKey} hash_fields=0 sum_requests=0 (empty_after_lua)`\n )\n finish()\n return\n }\n const counts = hgetallPairsToObject(raw[0])\n const durs = hgetallPairsToObject(raw[1])\n const fieldKeys = Object.keys(counts)\n let sumRequests = 0\n const samples = []\n for (const field of fieldKeys) {\n const count = parseInt(counts[field], 10)\n if (!count || count < 1) {\n continue\n }\n const dur = parseInt(durs[field] || '0', 10) || 0\n const parts = field.split(FIELD_SEP)\n if (parts.length !== 5) {\n continue\n }\n const [m, route, statusStr, aid, did] = parts\n sumRequests += count\n if (samples.length < 3) {\n samples.push(`${m} ${route} ${statusStr} x${count}`)\n }\n const labels = {\n method: m,\n route,\n status_code: statusStr,\n appId: aid,\n databaseId: did,\n }\n applyCount(labels, count)\n if (dur > 0) {\n applyDuration(labels, dur)\n }\n }\n httpMetricsTraceLog(\n `3_redis_drain pid=${process.pid} countKey=${this.countKey} hash_fields=${\n fieldKeys.length\n } sum_requests=${sumRequests} sample=${samples.join(' | ') || '—'}`\n )\n } catch (e) {\n console.error(\n '[HttpMetricsRedisStore] flush apply failed:',\n e.message\n )\n }\n finish()\n }\n )\n })\n })\n }\n}\n\nmodule.exports = {\n HttpMetricsRedisStore,\n buildFieldKey,\n FIELD_SEP,\n isRedisPeerInstalled,\n DEFAULT_HTTP_METRICS_REDIS_TTL_SEC,\n httpMetricsTraceLog,\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.
|
|
3
|
+
"version": "0.0.0-staging.24",
|
|
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",
|
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
"@types/pg": "^8.15.6"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@babel/cli": "7.
|
|
35
|
-
"@babel/core": "7.
|
|
36
|
-
"@babel/preset-env": "7.
|
|
37
|
-
"@babel/preset-typescript": "7.
|
|
34
|
+
"@babel/cli": "7.24.7",
|
|
35
|
+
"@babel/core": "7.24.7",
|
|
36
|
+
"@babel/preset-env": "7.24.7",
|
|
37
|
+
"@babel/preset-typescript": "7.24.7",
|
|
38
38
|
"@types/ioredis": "^5.0.0",
|
|
39
39
|
"@types/jest": "28.1.6",
|
|
40
40
|
"@types/node": "17.0.38",
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
const { HttpMetricsRedisStore } = require('./httpMetricsRedisStore')
|
|
2
|
-
|
|
3
|
-
const LOG = '[http-metrics-redis]'
|
|
1
|
+
const { HttpMetricsRedisStore, httpMetricsTraceLog } = require('./httpMetricsRedisStore')
|
|
4
2
|
|
|
5
3
|
function trunc(s, max = 120) {
|
|
6
4
|
const t = String(s)
|
|
@@ -52,8 +50,8 @@ class HttpMetricsRedisRecorder {
|
|
|
52
50
|
databaseId = '',
|
|
53
51
|
duration,
|
|
54
52
|
}) {
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
httpMetricsTraceLog(
|
|
54
|
+
`1_track pid=${process.pid} app=${this.appName} segment=${this.processType} ` +
|
|
57
55
|
`method=${method} route=${trunc(route)} status=${status_code} durationMs=${duration} ` +
|
|
58
56
|
`appId=${trunc(appId || '—', 40)} databaseId=${trunc(databaseId || '—', 40)}`
|
|
59
57
|
)
|
|
@@ -12,6 +12,31 @@ const DEFAULT_HTTP_METRICS_REDIS_TTL_SEC = 120
|
|
|
12
12
|
|
|
13
13
|
const LOG = '[http-metrics-redis]'
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* When Node runs behind `cluster`, HTTP is usually served from workers — include worker id in traces.
|
|
17
|
+
* @returns {string}
|
|
18
|
+
*/
|
|
19
|
+
function clusterHint() {
|
|
20
|
+
try {
|
|
21
|
+
// eslint-disable-next-line global-require
|
|
22
|
+
const c = require('cluster')
|
|
23
|
+
if (c.isWorker && c.worker != null) {
|
|
24
|
+
return ` cluster_worker=${c.worker.id}`
|
|
25
|
+
}
|
|
26
|
+
} catch (_) {
|
|
27
|
+
/* cluster not in use */
|
|
28
|
+
}
|
|
29
|
+
return ''
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Stdout trace (uses **console.log**, not warn — same as typical app request logs).
|
|
34
|
+
* @param {string} body - Line body after `[http-metrics-redis]` prefix.
|
|
35
|
+
*/
|
|
36
|
+
function httpMetricsTraceLog(body) {
|
|
37
|
+
console.log(`${LOG} ${body}${clusterHint()}`)
|
|
38
|
+
}
|
|
39
|
+
|
|
15
40
|
const DRAIN_LUA = `
|
|
16
41
|
local function drain(key)
|
|
17
42
|
local v = redis.call('HGETALL', key)
|
|
@@ -61,7 +86,9 @@ function hgetallPairsToObject(pairs) {
|
|
|
61
86
|
* Expects `redis` v3-style API: `multi().hincrby().expire().exec(cb)`, `eval`, `set`, `del`.
|
|
62
87
|
*
|
|
63
88
|
* **Stored metrics:** two hashes per app/segment — `:count` (HINCRBY per route group) and `:dur`
|
|
64
|
-
* (sum of duration ms per same field).
|
|
89
|
+
* (sum of duration ms per same field). Same `(method, route, status, appId, databaseId)` → same hash field;
|
|
90
|
+
* many requests in an interval **add** to count and **sum** durations (Redis `HINCRBY`). Drain applies those
|
|
91
|
+
* totals to `app_requests_total` / `app_requests_total_duration`.
|
|
65
92
|
*/
|
|
66
93
|
class HttpMetricsRedisStore {
|
|
67
94
|
/**
|
|
@@ -120,8 +147,8 @@ class HttpMetricsRedisStore {
|
|
|
120
147
|
console.error('[HttpMetricsRedisStore] record failed:', err.message)
|
|
121
148
|
return
|
|
122
149
|
}
|
|
123
|
-
|
|
124
|
-
|
|
150
|
+
httpMetricsTraceLog(
|
|
151
|
+
`2_redis_write pid=${process.pid} countKey=${this.countKey} durKey=${this.durKey} ok`
|
|
125
152
|
)
|
|
126
153
|
})
|
|
127
154
|
} catch (e) {
|
|
@@ -145,8 +172,8 @@ class HttpMetricsRedisStore {
|
|
|
145
172
|
return new Promise(resolve => {
|
|
146
173
|
client.set(this.lockKey, '1', 'EX', 25, 'NX', (setErr, ok) => {
|
|
147
174
|
if (setErr || ok !== 'OK') {
|
|
148
|
-
|
|
149
|
-
|
|
175
|
+
httpMetricsTraceLog(
|
|
176
|
+
`3_redis_drain_skip pid=${process.pid} countKey=${this.countKey} reason=${
|
|
150
177
|
setErr ? `error:${setErr.message}` : 'lock_not_acquired'
|
|
151
178
|
}`
|
|
152
179
|
)
|
|
@@ -174,8 +201,8 @@ class HttpMetricsRedisStore {
|
|
|
174
201
|
|
|
175
202
|
try {
|
|
176
203
|
if (!raw || !Array.isArray(raw) || raw.length < 2) {
|
|
177
|
-
|
|
178
|
-
|
|
204
|
+
httpMetricsTraceLog(
|
|
205
|
+
`3_redis_drain pid=${process.pid} countKey=${this.countKey} hash_fields=0 sum_requests=0 (empty_after_lua)`
|
|
179
206
|
)
|
|
180
207
|
finish()
|
|
181
208
|
return
|
|
@@ -212,8 +239,8 @@ class HttpMetricsRedisStore {
|
|
|
212
239
|
applyDuration(labels, dur)
|
|
213
240
|
}
|
|
214
241
|
}
|
|
215
|
-
|
|
216
|
-
|
|
242
|
+
httpMetricsTraceLog(
|
|
243
|
+
`3_redis_drain pid=${process.pid} countKey=${this.countKey} hash_fields=${
|
|
217
244
|
fieldKeys.length
|
|
218
245
|
} sum_requests=${sumRequests} sample=${samples.join(' | ') || '—'}`
|
|
219
246
|
)
|
|
@@ -237,4 +264,5 @@ module.exports = {
|
|
|
237
264
|
FIELD_SEP,
|
|
238
265
|
isRedisPeerInstalled,
|
|
239
266
|
DEFAULT_HTTP_METRICS_REDIS_TTL_SEC,
|
|
267
|
+
httpMetricsTraceLog,
|
|
240
268
|
}
|