@adalo/metrics 0.1.56 → 0.1.58

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.
@@ -1 +1 @@
1
- {"version":3,"file":"metricsRedisClient.js","names":["Queue","require","MetricsClient","RedisMetricsClient","constructor","redisClient","metricsConfig","METRICS_QUEUE_INTERVAL_SEC","intervalSec","parseInt","process","env","getConfiguredQueueNames","METRICS_APP_REDIS_BQ","Error","allQueues","split","map","q","trim","filter","Boolean","length","Set","startupValidation","queueNames","console","info","error","message","scripDefaultMetrics","processType","queueCache","Map","queueJobsGauge","createGauge","name","help","labelNames","withDefaultLabels","redisConnectionsGauge","redisMemoryGauge","redisStatsGauge","_setCleanupHandlers","collectRedisMetrics","getRedisInfo","section","Promise","resolve","reject","err","result","clientsInfo","memoryInfo","statsInfo","all","labels","getDefaultLabels","parseRedisInfo","infoStr","Object","fromEntries","line","startsWith","parts","clients","memory","stats","connected_clients","set","connection_type","used_memory","memory_type","maxmemory","instantaneous_ops_per_sec","operation","warn","collectSingleQueueMetrics","queueName","has","redis","isWorker","getEvents","sendEvents","queue","get","health","checkHealth","queue_name","status","waiting","active","succeeded","failed","delayed","collectQueueMetrics","allSettled","gatewayPush","metricsLogValues","metricObjects","registry","getMetricsAsJSON","JSON","stringify","includes","startPush","_startPush","catch","on","cleanup","close","quit","exit","module","exports"],"sources":["../src/metricsRedisClient.js"],"sourcesContent":["const Queue = require('bee-queue')\nconst { MetricsClient } = require('.')\n\n/**\n * RedisMetricsClient extends MetricsClient to collect\n * Redis and Bee Queue metrics periodically and push them to Prometheus Pushgateway.\n *\n * @extends MetricsClient\n */\nclass RedisMetricsClient extends MetricsClient {\n /**\n * @param {Object} options - Metrics client options + Redis client\n * @param {any} options.redisClient - Redis client instance (required)\n * @param {Object} [options.metricsConfig] - MetricsClient configuration overrides\n */\n constructor({ redisClient, metricsConfig = {} } = {}) {\n const METRICS_QUEUE_INTERVAL_SEC =\n metricsConfig.intervalSec ||\n parseInt(process.env.METRICS_QUEUE_INTERVAL_SEC || '', 10) ||\n 5\n\n const getConfiguredQueueNames = () => {\n if (!process.env.METRICS_APP_REDIS_BQ) {\n throw new Error(\n 'No queues configured for monitoring. Set METRICS_APP_REDIS_BQ with comma-separated queue names'\n )\n }\n\n const allQueues = process.env.METRICS_APP_REDIS_BQ.split(',')\n .map(q => q.trim())\n .filter(Boolean)\n\n if (allQueues.length === 0) {\n throw new Error(\n 'METRICS_APP_REDIS_BQ is empty or contains only whitespace. ' +\n 'Example: METRICS_APP_REDIS_BQ=\"adalo-compile,adalo-migrations\"'\n )\n }\n\n return [...new Set(allQueues)]\n }\n\n const startupValidation = () => {\n try {\n const queueNames = getConfiguredQueueNames()\n console.info(\n `[queue-metrics] Queue & Redis metrics collection starting for ${queueNames.length} queues`\n )\n return true\n } catch (error) {\n console.error(`[queue-metrics] ❌ Cannot start: ${error.message}`)\n console.error(\n `[queue-metrics] 💡 Example: METRICS_APP_REDIS_BQ=\"adalo-compile,adalo-migrations\"`\n )\n console.error(\n `[queue-metrics] 💡 Optional: METRICS_QUEUE_INTERVAL_SEC=\"10\"`\n )\n console.error(`[queue-metrics] Skipping queue metrics collection`)\n return false\n }\n }\n\n super({\n ...metricsConfig,\n scripDefaultMetrics: true,\n processType: metricsConfig.processType || 'queue-metrics',\n intervalSec: METRICS_QUEUE_INTERVAL_SEC,\n startupValidation,\n })\n\n this.getConfiguredQueueNames = getConfiguredQueueNames\n this.startupValidation = startupValidation\n\n /** Redis client used for queue & Redis metrics */\n this.redisClient = redisClient\n\n /** Cache for queue objects to avoid multiple connections */\n this.queueCache = new Map()\n\n /** Gauge for queue jobs by status */\n this.queueJobsGauge = this.createGauge({\n name: 'app_queue_jobs_count',\n help: 'Number of app jobs in the queue by status',\n labelNames: this.withDefaultLabels(['queue_name', 'status']),\n })\n\n /** Gauge for Redis client connections */\n this.redisConnectionsGauge = this.createGauge({\n name: 'app_redis_connections_count',\n help: 'Number of Redis client connections',\n labelNames: this.withDefaultLabels(['connection_type']),\n })\n\n /** Gauge for Redis memory usage */\n this.redisMemoryGauge = this.createGauge({\n name: 'app_redis_memory_bytes',\n help: 'Redis memory usage in bytes',\n labelNames: this.withDefaultLabels(['memory_type']),\n })\n\n /** Gauge for Redis operation stats */\n this.redisStatsGauge = this.createGauge({\n name: 'app_redis_stats_total',\n help: 'Redis operation statistics',\n labelNames: this.withDefaultLabels(['operation']),\n })\n\n this._setCleanupHandlers()\n }\n\n /**\n * Collect basic Redis INFO metrics: clients, memory, stats\n * @returns {Promise<void>}\n */\n collectRedisMetrics = async () => {\n try {\n const getRedisInfo = section =>\n new Promise((resolve, reject) => {\n this.redisClient.info(section, (err, result) => {\n if (err) reject(err)\n else resolve(result)\n })\n })\n\n const [clientsInfo, memoryInfo, statsInfo] = await Promise.all([\n getRedisInfo('clients'),\n getRedisInfo('memory'),\n getRedisInfo('stats'),\n ])\n\n const labels = this.getDefaultLabels()\n\n const parseRedisInfo = infoStr =>\n Object.fromEntries(\n infoStr\n .split('\\r\\n')\n .filter(line => line && !line.startsWith('#'))\n .map(line => line.split(':', 2))\n .filter(parts => parts.length === 2 && parts[0] && parts[1])\n )\n\n const clients = parseRedisInfo(clientsInfo)\n const memory = parseRedisInfo(memoryInfo)\n const stats = parseRedisInfo(statsInfo)\n\n if (clients.connected_clients) {\n this.redisConnectionsGauge.set(\n { ...labels, connection_type: 'connected' },\n parseInt(clients.connected_clients, 10) || 0\n )\n }\n\n if (memory.used_memory) {\n this.redisMemoryGauge.set(\n { ...labels, memory_type: 'used' },\n parseInt(memory.used_memory, 10) || 0\n )\n }\n if (memory.maxmemory) {\n this.redisMemoryGauge.set(\n { ...labels, memory_type: 'max' },\n parseInt(memory.maxmemory, 10) || 0\n )\n }\n\n if (stats.instantaneous_ops_per_sec) {\n this.redisStatsGauge.set(\n { ...labels, operation: 'ops_per_sec' },\n parseInt(stats.instantaneous_ops_per_sec, 10) || 0\n )\n }\n } catch (error) {\n console.warn(\n `[redis-metrics] Failed to collect Redis metrics:`,\n error.message\n )\n }\n }\n\n /**\n * Collect metrics for a single Bee Queue and set gauges\n * @param {string} queueName - Name of the queue\n * @returns {Promise<void>}\n */\n collectSingleQueueMetrics = async queueName => {\n try {\n if (!this.queueCache.has(queueName)) {\n this.queueCache.set(\n queueName,\n new Queue(queueName, {\n redis: this.redisClient,\n isWorker: false,\n getEvents: false,\n sendEvents: false,\n })\n )\n }\n\n const queue = this.queueCache.get(queueName)\n const health = await queue.checkHealth()\n\n const labels = {\n ...this.getDefaultLabels(),\n queue_name: queueName,\n }\n\n this.queueJobsGauge.set(\n { ...labels, status: 'waiting' },\n health.waiting || 0\n )\n this.queueJobsGauge.set(\n { ...labels, status: 'active' },\n health.active || 0\n )\n this.queueJobsGauge.set(\n { ...labels, status: 'succeeded' },\n health.succeeded || 0\n )\n this.queueJobsGauge.set(\n { ...labels, status: 'failed' },\n health.failed || 0\n )\n this.queueJobsGauge.set(\n { ...labels, status: 'delayed' },\n health.delayed || 0\n )\n } catch (error) {\n console.warn(\n `[queue-metrics] Failed to collect metrics for queue ${queueName}:`,\n error.message\n )\n }\n }\n\n /**\n * Collect metrics for all queues and Redis, then push to Pushgateway\n * @returns {Promise<void>}\n */\n collectQueueMetrics = async () => {\n try {\n const queueNames = this.getConfiguredQueueNames()\n\n await this.collectRedisMetrics()\n await Promise.allSettled(\n queueNames.map(queueName => this.collectSingleQueueMetrics(queueName))\n )\n\n await this.gatewayPush()\n\n if (this.metricsLogValues) {\n const metricObjects = await this.registry.getMetricsAsJSON()\n console.info(\n `[queue-metrics] Collected metrics for ${queueNames.length} queues:`,\n JSON.stringify(metricObjects, null, 2)\n )\n }\n } catch (error) {\n if (\n error.message?.includes('No queues configured') ||\n error.message?.includes('METRICS_APP_REDIS_BQ')\n ) {\n console.error(\n `[queue-metrics] ❌ Configuration error: ${error.message}`\n )\n console.error(\n `[queue-metrics] 💡 Example config: METRICS_APP_REDIS_BQ=\"adalo-compile,adalo-migrations\"`\n )\n } else {\n console.error(\n `[queue-metrics] Failed to collect queue metrics: ${error.message}`\n )\n }\n throw error\n }\n }\n\n /**\n * Start periodic collection.\n * @param {number} [intervalSec=this.intervalSec] - Interval in seconds\n */\n startPush = (intervalSec = this.intervalSec) => {\n this._startPush(intervalSec, () => {\n this.collectQueueMetrics().catch(err => {\n console.error(\n `[queue-metrics] Failed to collect queue & Redis metrics:`,\n err\n )\n })\n })\n }\n\n _setCleanupHandlers = () => {\n process.on('SIGINT', this.cleanup)\n process.on('SIGTERM', this.cleanup)\n }\n\n /**\n * Cleanup queues and Redis client\n */\n cleanup = async () => {\n for (const [queueName, queue] of this.queueCache) {\n try {\n await queue.close()\n } catch (err) {\n console.error(`[queue-metrics] Error closing queue ${queueName}:`, err)\n }\n }\n\n // Close Redis connection\n this.redisClient?.quit()\n process.exit(0)\n }\n}\n\nmodule.exports = { RedisMetricsClient }\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"metricsRedisClient.js","names":["MetricsClient","require","RedisMetricsClient","constructor","redisClient","metricsConfig","intervalSec","parseInt","process","env","METRICS_QUEUE_INTERVAL_SEC","scripDefaultMetrics","processType","redisConnectionsGauge","createGauge","name","help","labelNames","withDefaultLabels","redisMemoryGauge","redisStatsGauge","_setCleanupHandlers","collectRedisMetrics","getRedisInfo","section","Promise","resolve","reject","info","err","result","clientsInfo","memoryInfo","statsInfo","all","labels","getDefaultLabels","parseRedisInfo","infoStr","Object","fromEntries","split","filter","line","startsWith","map","parts","length","clients","memory","stats","connected_clients","set","connection_type","used_memory","memory_type","maxmemory","instantaneous_ops_per_sec","operation","error","console","warn","message","pushRedisMetrics","gatewayPush","metricsLogValues","metricObjects","registry","getMetricsAsJSON","JSON","stringify","startPush","_startPush","catch","cleanup","quit","exit","on","module","exports"],"sources":["../src/metricsRedisClient.js"],"sourcesContent":["const { MetricsClient } = require('.')\n\n/**\n * RedisMetricsClient extends MetricsClient to collect\n * Redis metrics periodically and push them to Prometheus Pushgateway.\n *\n * @extends MetricsClient\n */\nclass RedisMetricsClient extends MetricsClient {\n /**\n * @param {Object} options\n * @param {any} options.redisClient - Redis client instance (required)\n * @param {string} [options.appName] - Application name (from MetricsClient)\n * @param {string} [options.dynoId] - Dyno/instance ID (from MetricsClient)\n * @param {string} [options.processType] - Process type (from MetricsClient)\n * @param {boolean} [options.enabled] - Enable metrics collection (from MetricsClient)\n * @param {boolean} [options.logValues] - Log metrics values (from MetricsClient)\n * @param {string} [options.pushgatewayUrl] - PushGateway URL (from MetricsClient)\n * @param {string} [options.pushgatewaySecret] - PushGateway secret token (from MetricsClient)\n * @param {number} [options.intervalSec] - Interval in seconds for pushing metrics (from MetricsClient)\n * @param {boolean} [options.removeOldMetrics] - Remove old metrics by service (from MetricsClient)\n * @param {boolean} [options.scripDefaultMetrics] - Skip default metrics creation (from MetricsClient)\n * @param {function} [options.startupValidation] - Function to validate startup (from MetricsClient)\n */\n constructor({ redisClient, ...metricsConfig } = {}) {\n const intervalSec =\n metricsConfig.intervalSec ||\n parseInt(process.env.METRICS_QUEUE_INTERVAL_SEC || '', 10) ||\n 5\n\n super({\n ...metricsConfig,\n scripDefaultMetrics: true,\n processType: metricsConfig.processType || 'redis-metrics',\n intervalSec,\n })\n\n /** Redis client used for metrics */\n this.redisClient = redisClient\n\n /** Gauge for Redis client connections */\n this.redisConnectionsGauge = this.createGauge({\n name: 'app_redis_connections_count',\n help: 'Number of Redis client connections',\n labelNames: this.withDefaultLabels(['connection_type']),\n })\n\n /** Gauge for Redis memory usage */\n this.redisMemoryGauge = this.createGauge({\n name: 'app_redis_memory_bytes',\n help: 'Redis memory usage in bytes',\n labelNames: this.withDefaultLabels(['memory_type']),\n })\n\n /** Gauge for Redis operation stats */\n this.redisStatsGauge = this.createGauge({\n name: 'app_redis_stats_total',\n help: 'Redis operation statistics',\n labelNames: this.withDefaultLabels(['operation']),\n })\n\n this._setCleanupHandlers()\n }\n\n /**\n * Collect basic Redis INFO metrics: clients, memory, stats\n * @returns {Promise<void>}\n */\n collectRedisMetrics = async () => {\n try {\n const getRedisInfo = section =>\n new Promise((resolve, reject) => {\n this.redisClient.info(section, (err, result) => {\n if (err) reject(err)\n else resolve(result)\n })\n })\n\n const [clientsInfo, memoryInfo, statsInfo] = await Promise.all([\n getRedisInfo('clients'),\n getRedisInfo('memory'),\n getRedisInfo('stats'),\n ])\n\n const labels = this.getDefaultLabels()\n\n const parseRedisInfo = infoStr =>\n Object.fromEntries(\n infoStr\n .split('\\r\\n')\n .filter(line => line && !line.startsWith('#'))\n .map(line => line.split(':', 2))\n .filter(parts => parts.length === 2 && parts[0] && parts[1])\n )\n\n const clients = parseRedisInfo(clientsInfo)\n const memory = parseRedisInfo(memoryInfo)\n const stats = parseRedisInfo(statsInfo)\n\n if (clients.connected_clients) {\n this.redisConnectionsGauge.set(\n { ...labels, connection_type: 'connected' },\n parseInt(clients.connected_clients, 10) || 0\n )\n }\n\n if (memory.used_memory) {\n this.redisMemoryGauge.set(\n { ...labels, memory_type: 'used' },\n parseInt(memory.used_memory, 10) || 0\n )\n }\n if (memory.maxmemory) {\n this.redisMemoryGauge.set(\n { ...labels, memory_type: 'max' },\n parseInt(memory.maxmemory, 10) || 0\n )\n }\n\n if (stats.instantaneous_ops_per_sec) {\n this.redisStatsGauge.set(\n { ...labels, operation: 'ops_per_sec' },\n parseInt(stats.instantaneous_ops_per_sec, 10) || 0\n )\n }\n } catch (error) {\n console.warn(\n `[redis-metrics] Failed to collect Redis metrics:`,\n error.message\n )\n }\n }\n\n /**\n * Collect metrics for all Redis and push to Prometheus Pushgateway\n * @returns {Promise<void>}\n */\n pushRedisMetrics = async () => {\n try {\n await this.collectRedisMetrics()\n await this.gatewayPush()\n\n if (this.metricsLogValues) {\n const metricObjects = await this.registry.getMetricsAsJSON()\n console.info(\n `[redis-metrics] Collected metrics for Redis`,\n JSON.stringify(metricObjects, null, 2)\n )\n }\n } catch (error) {\n console.error(\n `[redis-metrics] Failed to collect Redis metrics: ${error.message}`\n )\n throw error\n }\n }\n\n /**\n * Start periodic collection.\n * @param {number} [intervalSec=this.intervalSec] - Interval in seconds\n */\n startPush = (intervalSec = this.intervalSec) => {\n this._startPush(intervalSec, () => {\n this.pushRedisMetrics().catch(err => {\n console.error(`[redis-metrics] Failed to push Redis metrics:`, err)\n })\n })\n }\n\n /**\n * Cleanup Redis client\n */\n cleanup = async () => {\n try {\n this.redisClient?.quit()\n } catch (err) {\n console.error('[redis-metrics] Error closing Redis client:', err)\n }\n process.exit(0)\n }\n\n _setCleanupHandlers = () => {\n process.on('SIGINT', this.cleanup)\n process.on('SIGTERM', this.cleanup)\n }\n}\n\nmodule.exports = { RedisMetricsClient }\n"],"mappings":"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","ignoreList":[]}
@@ -4,5 +4,17 @@
4
4
  * @param {string} [defaultAppName='undefined-app'] - Fallback app name if METRICS_APP_NAME is not set.
5
5
  * @returns {(client: import('redis').RedisClient, name: string) => void}
6
6
  */
7
- export function createSetClientName(defaultAppName?: string | undefined): (client: import('redis').RedisClient, name: string) => void;
7
+ export function createSetClientNameForRedisV3(defaultAppName?: string | undefined): (client: import('redis').RedisClient, name: string) => void;
8
+ /**
9
+ * Creates a function to set Redis client name for node-redis v4
10
+ * @param {string} defaultAppName
11
+ * @returns {(client: ReturnType<createClient>, name: string) => void}
12
+ */
13
+ export function createSetClientNameForRedisV4(defaultAppName?: string): (client: ReturnType<createClient>, name: string) => void;
14
+ /**
15
+ * Creates a function to set Redis client name for ioredis
16
+ * @param {string} defaultAppName
17
+ * @returns {(client: Redis, name: string) => void}
18
+ */
19
+ export function createSetClientNameForIoredis(defaultAppName?: string): (client: Redis, name: string) => void;
8
20
  //# sourceMappingURL=redisUtils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"redisUtils.d.ts","sourceRoot":"","sources":["../src/redisUtils.js"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,mFAFsB,OAAO,OAAO,EAAE,WAAW,QAAQ,MAAM,KAAK,IAAI,CAuBvE"}
1
+ {"version":3,"file":"redisUtils.d.ts","sourceRoot":"","sources":["../src/redisUtils.js"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,6FAFsB,OAAO,OAAO,EAAE,WAAW,QAAQ,MAAM,KAAK,IAAI,CAuBvE;AA2BD;;;;GAIG;AACH,+DAHW,MAAM,YACK,wBAAwB,QAAQ,MAAM,KAAK,IAAI,CAsBpE;AAlDD;;;;GAIG;AACH,+DAHW,MAAM,yBACkB,MAAM,KAAK,IAAI,CAoBjD"}
package/lib/redisUtils.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * @param {string} [defaultAppName='undefined-app'] - Fallback app name if METRICS_APP_NAME is not set.
7
7
  * @returns {(client: import('redis').RedisClient, name: string) => void}
8
8
  */
9
- const createSetClientName = (defaultAppName = 'undefined-app') => {
9
+ const createSetClientNameForRedisV3 = (defaultAppName = 'undefined-app') => {
10
10
  return (client, name) => {
11
11
  const appName = process.env.METRICS_APP_NAME || defaultAppName;
12
12
  const dyno = process.env.BUILD_DYNO_PROCESS_TYPE || 'undefined-dyno';
@@ -23,7 +23,52 @@ const createSetClientName = (defaultAppName = 'undefined-app') => {
23
23
  }
24
24
  };
25
25
  };
26
+
27
+ /**
28
+ * Creates a function to set Redis client name for ioredis
29
+ * @param {string} defaultAppName
30
+ * @returns {(client: Redis, name: string) => void}
31
+ */
32
+ const createSetClientNameForIoredis = (defaultAppName = 'undefined-app') => {
33
+ return (client, name) => {
34
+ const appName = process.env.METRICS_APP_NAME || defaultAppName;
35
+ const dyno = process.env.BUILD_DYNO_PROCESS_TYPE || 'undefined-dyno';
36
+ if (process.env.NODE_ENV !== 'test') {
37
+ client.once('connect', () => {
38
+ client.call('CLIENT', ['SETNAME', `${appName}:${dyno}:${name}`]).then(() => {
39
+ console.log(`Connected to Redis for pid:${process.pid} (${name})`);
40
+ }).catch(err => {
41
+ console.error(`Failed to set client name for ${name}:`, err);
42
+ });
43
+ });
44
+ }
45
+ };
46
+ };
47
+
48
+ /**
49
+ * Creates a function to set Redis client name for node-redis v4
50
+ * @param {string} defaultAppName
51
+ * @returns {(client: ReturnType<createClient>, name: string) => void}
52
+ */
53
+ const createSetClientNameForRedisV4 = (defaultAppName = 'undefined-app') => {
54
+ return (client, name) => {
55
+ const appName = process.env.METRICS_APP_NAME || defaultAppName;
56
+ const dyno = process.env.BUILD_DYNO_PROCESS_TYPE || 'undefined-dyno';
57
+ if (process.env.NODE_ENV !== 'test') {
58
+ client.on('ready', async () => {
59
+ try {
60
+ await client.sendCommand(['CLIENT', 'SETNAME', `${appName}:${dyno}:${name}`]);
61
+ console.log(`Connected to Redis for pid:${process.pid} (${name})`);
62
+ } catch (err) {
63
+ console.error(`Failed to set client name for ${name}:`, err);
64
+ }
65
+ });
66
+ }
67
+ };
68
+ };
26
69
  module.exports = {
27
- createSetClientName
70
+ createSetClientNameForRedisV3,
71
+ createSetClientNameForRedisV4,
72
+ createSetClientNameForIoredis
28
73
  };
29
74
  //# sourceMappingURL=redisUtils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"redisUtils.js","names":["createSetClientName","defaultAppName","client","name","appName","process","env","METRICS_APP_NAME","dyno","BUILD_DYNO_PROCESS_TYPE","NODE_ENV","on","send_command","err","console","error","log","pid","module","exports"],"sources":["../src/redisUtils.js"],"sourcesContent":["/**\n * Creates a configured setClientName function.\n *\n * @param {string} [defaultAppName='undefined-app'] - Fallback app name if METRICS_APP_NAME is not set.\n * @returns {(client: import('redis').RedisClient, name: string) => void}\n */\nconst createSetClientName = (defaultAppName = 'undefined-app') => {\n return (client, name) => {\n const appName = process.env.METRICS_APP_NAME || defaultAppName\n const dyno = process.env.BUILD_DYNO_PROCESS_TYPE || 'undefined-dyno'\n\n if (process.env.NODE_ENV !== 'test') {\n client.on('connect', () => {\n client.send_command(\n 'CLIENT',\n ['SETNAME', `${appName}:${dyno}:${name}`],\n err => {\n if (err) {\n console.error(`Failed to set client name for ${name}:`, err)\n } else {\n console.log(`Connected to Redis for pid:${process.pid} (${name})`)\n }\n }\n )\n })\n }\n }\n}\n\nmodule.exports = { createSetClientName }\n"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA,MAAMA,mBAAmB,GAAGA,CAACC,cAAc,GAAG,eAAe,KAAK;EAChE,OAAO,CAACC,MAAM,EAAEC,IAAI,KAAK;IACvB,MAAMC,OAAO,GAAGC,OAAO,CAACC,GAAG,CAACC,gBAAgB,IAAIN,cAAc;IAC9D,MAAMO,IAAI,GAAGH,OAAO,CAACC,GAAG,CAACG,uBAAuB,IAAI,gBAAgB;IAEpE,IAAIJ,OAAO,CAACC,GAAG,CAACI,QAAQ,KAAK,MAAM,EAAE;MACnCR,MAAM,CAACS,EAAE,CAAC,SAAS,EAAE,MAAM;QACzBT,MAAM,CAACU,YAAY,CACjB,QAAQ,EACR,CAAC,SAAS,EAAE,GAAGR,OAAO,IAAII,IAAI,IAAIL,IAAI,EAAE,CAAC,EACzCU,GAAG,IAAI;UACL,IAAIA,GAAG,EAAE;YACPC,OAAO,CAACC,KAAK,CAAC,iCAAiCZ,IAAI,GAAG,EAAEU,GAAG,CAAC;UAC9D,CAAC,MAAM;YACLC,OAAO,CAACE,GAAG,CAAC,8BAA8BX,OAAO,CAACY,GAAG,KAAKd,IAAI,GAAG,CAAC;UACpE;QACF,CACF,CAAC;MACH,CAAC,CAAC;IACJ;EACF,CAAC;AACH,CAAC;AAEDe,MAAM,CAACC,OAAO,GAAG;EAAEnB;AAAoB,CAAC","ignoreList":[]}
1
+ {"version":3,"file":"redisUtils.js","names":["createSetClientNameForRedisV3","defaultAppName","client","name","appName","process","env","METRICS_APP_NAME","dyno","BUILD_DYNO_PROCESS_TYPE","NODE_ENV","on","send_command","err","console","error","log","pid","createSetClientNameForIoredis","once","call","then","catch","createSetClientNameForRedisV4","sendCommand","module","exports"],"sources":["../src/redisUtils.js"],"sourcesContent":["/**\n * Creates a configured setClientName function.\n *\n * @param {string} [defaultAppName='undefined-app'] - Fallback app name if METRICS_APP_NAME is not set.\n * @returns {(client: import('redis').RedisClient, name: string) => void}\n */\nconst createSetClientNameForRedisV3 = (defaultAppName = 'undefined-app') => {\n return (client, name) => {\n const appName = process.env.METRICS_APP_NAME || defaultAppName\n const dyno = process.env.BUILD_DYNO_PROCESS_TYPE || 'undefined-dyno'\n\n if (process.env.NODE_ENV !== 'test') {\n client.on('connect', () => {\n client.send_command(\n 'CLIENT',\n ['SETNAME', `${appName}:${dyno}:${name}`],\n err => {\n if (err) {\n console.error(`Failed to set client name for ${name}:`, err)\n } else {\n console.log(`Connected to Redis for pid:${process.pid} (${name})`)\n }\n }\n )\n })\n }\n }\n}\n\n/**\n * Creates a function to set Redis client name for ioredis\n * @param {string} defaultAppName\n * @returns {(client: Redis, name: string) => void}\n */\nconst createSetClientNameForIoredis = (defaultAppName = 'undefined-app') => {\n return (client, name) => {\n const appName = process.env.METRICS_APP_NAME || defaultAppName\n const dyno = process.env.BUILD_DYNO_PROCESS_TYPE || 'undefined-dyno'\n\n if (process.env.NODE_ENV !== 'test') {\n client.once('connect', () => {\n client\n .call('CLIENT', ['SETNAME', `${appName}:${dyno}:${name}`])\n .then(() => {\n console.log(`Connected to Redis for pid:${process.pid} (${name})`)\n })\n .catch(err => {\n console.error(`Failed to set client name for ${name}:`, err)\n })\n })\n }\n }\n}\n\n/**\n * Creates a function to set Redis client name for node-redis v4\n * @param {string} defaultAppName\n * @returns {(client: ReturnType<createClient>, name: string) => void}\n */\nconst createSetClientNameForRedisV4 = (defaultAppName = 'undefined-app') => {\n return (client, name) => {\n const appName = process.env.METRICS_APP_NAME || defaultAppName\n const dyno = process.env.BUILD_DYNO_PROCESS_TYPE || 'undefined-dyno'\n\n if (process.env.NODE_ENV !== 'test') {\n client.on('ready', async () => {\n try {\n await client.sendCommand([\n 'CLIENT',\n 'SETNAME',\n `${appName}:${dyno}:${name}`,\n ])\n console.log(`Connected to Redis for pid:${process.pid} (${name})`)\n } catch (err) {\n console.error(`Failed to set client name for ${name}:`, err)\n }\n })\n }\n }\n}\n\nmodule.exports = {\n createSetClientNameForRedisV3,\n createSetClientNameForRedisV4,\n createSetClientNameForIoredis,\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.1.56",
3
+ "version": "0.1.58",
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",
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './metricsClient'
2
2
  export * from './metricsRedisClient'
3
+ export * from './metricsQueueRedisClient'
3
4
  export * from './redisUtils'
@@ -0,0 +1,222 @@
1
+ const Queue = require('bee-queue')
2
+ const { RedisMetricsClient } = require('.')
3
+
4
+ /**
5
+ * QueueRedisMetricsClient extends MetricsClient to collect
6
+ * Redis and Bee Queue metrics periodically and push them to Prometheus Pushgateway.
7
+ *
8
+ * @extends RedisMetricsClient
9
+ */
10
+ class QueueRedisMetricsClient extends RedisMetricsClient {
11
+ /**
12
+ * @param {Object} options
13
+ * @param {any} options.redisClient - Redis client instance (required)
14
+ * @param {string} [options.appName] - Application name (from MetricsClient)
15
+ * @param {string} [options.dynoId] - Dyno/instance ID (from MetricsClient)
16
+ * @param {string} [options.processType] - Process type (from MetricsClient)
17
+ * @param {boolean} [options.enabled] - Enable metrics collection (from MetricsClient)
18
+ * @param {boolean} [options.logValues] - Log metrics values (from MetricsClient)
19
+ * @param {string} [options.pushgatewayUrl] - PushGateway URL (from MetricsClient)
20
+ * @param {string} [options.pushgatewaySecret] - PushGateway secret token (from MetricsClient)
21
+ * @param {number} [options.intervalSec] - Interval in seconds for pushing metrics (from MetricsClient)
22
+ * @param {boolean} [options.removeOldMetrics] - Remove old metrics by service (from MetricsClient)
23
+ * @param {boolean} [options.scripDefaultMetrics] - Skip default metrics creation (from MetricsClient)
24
+ * @param {function} [options.startupValidation] - Function to validate startup (from MetricsClient)
25
+ */
26
+ constructor({ redisClient, metricsConfig = {} } = {}) {
27
+ const getConfiguredQueueNames = () => {
28
+ if (!process.env.METRICS_APP_REDIS_BQ) {
29
+ throw new Error(
30
+ 'No queues configured for monitoring. Set METRICS_APP_REDIS_BQ with comma-separated queue names'
31
+ )
32
+ }
33
+
34
+ const allQueues = process.env.METRICS_APP_REDIS_BQ.split(',')
35
+ .map(q => q.trim())
36
+ .filter(Boolean)
37
+
38
+ if (allQueues.length === 0) {
39
+ throw new Error(
40
+ 'METRICS_APP_REDIS_BQ is empty or contains only whitespace. ' +
41
+ 'Example: METRICS_APP_REDIS_BQ="adalo-compile,adalo-migrations"'
42
+ )
43
+ }
44
+
45
+ return [...new Set(allQueues)]
46
+ }
47
+
48
+ const startupValidation = () => {
49
+ try {
50
+ const queueNames = getConfiguredQueueNames()
51
+ console.info(
52
+ `[queue-metrics] Queue & Redis metrics collection starting for ${queueNames.length} queues`
53
+ )
54
+ return true
55
+ } catch (error) {
56
+ console.error(`[queue-metrics] ❌ Cannot start: ${error.message}`)
57
+ console.error(
58
+ `[queue-metrics] 💡 Example: METRICS_APP_REDIS_BQ="adalo-compile,adalo-migrations"`
59
+ )
60
+ console.error(
61
+ `[queue-metrics] 💡 Optional: METRICS_QUEUE_INTERVAL_SEC="10"`
62
+ )
63
+ console.error(`[queue-metrics] Skipping queue metrics collection`)
64
+ return false
65
+ }
66
+ }
67
+
68
+ super({
69
+ ...metricsConfig,
70
+ redisClient,
71
+ startupValidation,
72
+ })
73
+
74
+ this.getConfiguredQueueNames = getConfiguredQueueNames
75
+ this.startupValidation = startupValidation
76
+
77
+ /** Cache for queue objects to avoid multiple connections */
78
+ this.queueCache = new Map()
79
+
80
+ /** Gauge for queue jobs by status */
81
+ this.queueJobsGauge = this.createGauge({
82
+ name: 'app_queue_jobs_count',
83
+ help: 'Number of app jobs in the queue by status',
84
+ labelNames: this.withDefaultLabels(['queue_name', 'status']),
85
+ })
86
+
87
+ this._setCleanupHandlers()
88
+ }
89
+
90
+ /**
91
+ * Collect metrics for a single Bee Queue and set gauges
92
+ * @param {string} queueName - Name of the queue
93
+ * @returns {Promise<void>}
94
+ */
95
+ collectSingleQueueMetrics = async queueName => {
96
+ try {
97
+ if (!this.queueCache.has(queueName)) {
98
+ this.queueCache.set(
99
+ queueName,
100
+ new Queue(queueName, {
101
+ redis: this.redisClient,
102
+ isWorker: false,
103
+ getEvents: false,
104
+ sendEvents: false,
105
+ })
106
+ )
107
+ }
108
+
109
+ const queue = this.queueCache.get(queueName)
110
+ const health = await queue.checkHealth()
111
+
112
+ const labels = {
113
+ ...this.getDefaultLabels(),
114
+ queue_name: queueName,
115
+ }
116
+
117
+ this.queueJobsGauge.set(
118
+ { ...labels, status: 'waiting' },
119
+ health.waiting || 0
120
+ )
121
+ this.queueJobsGauge.set(
122
+ { ...labels, status: 'active' },
123
+ health.active || 0
124
+ )
125
+ this.queueJobsGauge.set(
126
+ { ...labels, status: 'succeeded' },
127
+ health.succeeded || 0
128
+ )
129
+ this.queueJobsGauge.set(
130
+ { ...labels, status: 'failed' },
131
+ health.failed || 0
132
+ )
133
+ this.queueJobsGauge.set(
134
+ { ...labels, status: 'delayed' },
135
+ health.delayed || 0
136
+ )
137
+ } catch (error) {
138
+ console.warn(
139
+ `[queue-metrics] Failed to collect metrics for queue ${queueName}:`,
140
+ error.message
141
+ )
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Collect metrics for all queues and Redis, then push to Pushgateway
147
+ * @returns {Promise<void>}
148
+ */
149
+ collectQueueMetrics = async () => {
150
+ try {
151
+ const queueNames = this.getConfiguredQueueNames()
152
+
153
+ await this.collectRedisMetrics()
154
+ await Promise.allSettled(
155
+ queueNames.map(queueName => this.collectSingleQueueMetrics(queueName))
156
+ )
157
+
158
+ await this.gatewayPush()
159
+
160
+ if (this.metricsLogValues) {
161
+ const metricObjects = await this.registry.getMetricsAsJSON()
162
+ console.info(
163
+ `[queue-metrics] Collected metrics for ${queueNames.length} queues:`,
164
+ JSON.stringify(metricObjects, null, 2)
165
+ )
166
+ }
167
+ } catch (error) {
168
+ if (
169
+ error.message?.includes('No queues configured') ||
170
+ error.message?.includes('METRICS_APP_REDIS_BQ')
171
+ ) {
172
+ console.error(
173
+ `[queue-metrics] ❌ Configuration error: ${error.message}`
174
+ )
175
+ console.error(
176
+ `[queue-metrics] 💡 Example config: METRICS_APP_REDIS_BQ="adalo-compile,adalo-migrations"`
177
+ )
178
+ } else {
179
+ console.error(
180
+ `[queue-metrics] Failed to collect queue metrics: ${error.message}`
181
+ )
182
+ }
183
+ throw error
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Start periodic collection.
189
+ * @param {number} [intervalSec=this.intervalSec] - Interval in seconds
190
+ */
191
+ startPush = (intervalSec = this.intervalSec) => {
192
+ this._startPush(intervalSec, () => {
193
+ this.collectQueueMetrics().catch(err => {
194
+ console.error(
195
+ `[queue-metrics] Failed to collect queue & Redis metrics:`,
196
+ err
197
+ )
198
+ })
199
+ })
200
+ }
201
+
202
+ _setCleanupHandlers = () => {
203
+ process.on('SIGINT', this.cleanup)
204
+ process.on('SIGTERM', this.cleanup)
205
+ }
206
+
207
+ /**
208
+ * Cleanup queues and Redis client
209
+ */
210
+ cleanup = async () => {
211
+ for (const [queueName, queue] of this.queueCache) {
212
+ try {
213
+ await queue.close()
214
+ } catch (err) {
215
+ console.error(`[queue-metrics] Error closing queue ${queueName}:`, err)
216
+ }
217
+ }
218
+ process.exit(0)
219
+ }
220
+ }
221
+
222
+ module.exports = { QueueRedisMetricsClient }