@adalo/metrics 0.1.122 → 0.1.124
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/lib/health/healthCheckCache.d.ts +57 -0
- package/lib/health/healthCheckCache.d.ts.map +1 -0
- package/lib/health/healthCheckCache.js +200 -0
- package/lib/health/healthCheckCache.js.map +1 -0
- package/lib/{healthCheckClient.d.ts → health/healthCheckClient.d.ts} +59 -15
- package/lib/health/healthCheckClient.d.ts.map +1 -0
- package/lib/{healthCheckClient.js → health/healthCheckClient.js} +232 -49
- package/lib/health/healthCheckClient.js.map +1 -0
- package/lib/health/healthCheckUtils.d.ts +54 -0
- package/lib/health/healthCheckUtils.d.ts.map +1 -0
- package/lib/health/healthCheckUtils.js +142 -0
- package/lib/health/healthCheckUtils.js.map +1 -0
- package/lib/health/healthCheckWorker.d.ts +2 -0
- package/lib/health/healthCheckWorker.d.ts.map +1 -0
- package/lib/health/healthCheckWorker.js +64 -0
- package/lib/health/healthCheckWorker.js.map +1 -0
- package/lib/index.d.ts +8 -6
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +28 -6
- package/lib/index.js.map +1 -1
- package/lib/metrics/baseMetricsClient.d.ts.map +1 -0
- package/lib/metrics/baseMetricsClient.js.map +1 -0
- package/lib/metrics/metricsClient.d.ts.map +1 -0
- package/lib/metrics/metricsClient.js.map +1 -0
- package/lib/metrics/metricsDatabaseClient.d.ts.map +1 -0
- package/lib/metrics/metricsDatabaseClient.js.map +1 -0
- package/lib/metrics/metricsQueueRedisClient.d.ts.map +1 -0
- package/lib/{metricsQueueRedisClient.js → metrics/metricsQueueRedisClient.js} +2 -2
- package/lib/metrics/metricsQueueRedisClient.js.map +1 -0
- package/lib/metrics/metricsRedisClient.d.ts.map +1 -0
- package/lib/{metricsRedisClient.js → metrics/metricsRedisClient.js} +1 -1
- package/lib/metrics/metricsRedisClient.js.map +1 -0
- package/package.json +1 -1
- package/src/health/healthCheckCache.js +237 -0
- package/src/{healthCheckClient.js → health/healthCheckClient.js} +226 -49
- package/src/health/healthCheckUtils.js +143 -0
- package/src/health/healthCheckWorker.js +61 -0
- package/src/index.ts +8 -6
- package/src/{metricsQueueRedisClient.js → metrics/metricsQueueRedisClient.js} +2 -2
- package/src/{metricsRedisClient.js → metrics/metricsRedisClient.js} +1 -1
- package/lib/baseMetricsClient.d.ts.map +0 -1
- package/lib/baseMetricsClient.js.map +0 -1
- package/lib/healthCheckClient.d.ts.map +0 -1
- package/lib/healthCheckClient.js.map +0 -1
- package/lib/metricsClient.d.ts.map +0 -1
- package/lib/metricsClient.js.map +0 -1
- package/lib/metricsDatabaseClient.d.ts.map +0 -1
- package/lib/metricsDatabaseClient.js.map +0 -1
- package/lib/metricsQueueRedisClient.d.ts.map +0 -1
- package/lib/metricsQueueRedisClient.js.map +0 -1
- package/lib/metricsRedisClient.d.ts.map +0 -1
- package/lib/metricsRedisClient.js.map +0 -1
- /package/lib/{baseMetricsClient.d.ts → metrics/baseMetricsClient.d.ts} +0 -0
- /package/lib/{baseMetricsClient.js → metrics/baseMetricsClient.js} +0 -0
- /package/lib/{metricsClient.d.ts → metrics/metricsClient.d.ts} +0 -0
- /package/lib/{metricsClient.js → metrics/metricsClient.js} +0 -0
- /package/lib/{metricsDatabaseClient.d.ts → metrics/metricsDatabaseClient.d.ts} +0 -0
- /package/lib/{metricsDatabaseClient.js → metrics/metricsDatabaseClient.js} +0 -0
- /package/lib/{metricsQueueRedisClient.d.ts → metrics/metricsQueueRedisClient.d.ts} +0 -0
- /package/lib/{metricsRedisClient.d.ts → metrics/metricsRedisClient.d.ts} +0 -0
- /package/src/{baseMetricsClient.js → metrics/baseMetricsClient.js} +0 -0
- /package/src/{metricsClient.js → metrics/metricsClient.js} +0 -0
- /package/src/{metricsDatabaseClient.js → metrics/metricsDatabaseClient.js} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metricsRedisClient.js","names":["BaseMetricsClient","require","getRedisClientType","REDIS_V4","IOREDIS","REDIS_V3","redisConnectionStableFields","redisConnectionFields","RedisMetricsClient","constructor","redisClient","metricsConfig","intervalSec","parseInt","process","env","METRICS_QUEUE_INTERVAL_SEC","processType","redisClientType","redisConnectionsGauge","createGauge","name","help","labelNames","withDefaultLabels","redisConnectionsMemoryGauge","redisMemoryGauge","redisStatsGauge","_setCleanupHandlers","getRedisConnections","Error","Promise","resolve","reject","send_command","err","result","message","sendCommand","client","getRedisInfo","section","info","parseRedisConnections","clientsStr","split","filter","line","trim","map","parts","forEach","p","k","v","includes","collectRedisMetrics","memoryInfoStr","statsInfoStr","connectionsInfoStr","all","labels","getDefaultLabels","connections","logValues","console","log","length","grouped","conn","flags","totMem","cmd","key","JSON","stringify","count","memory","Object","values","valueLabels","set","parseRedisInfo","infoStr","fromEntries","startsWith","stats","used_memory","memory_type","maxmemory","instantaneous_ops_per_sec","operation","error","warn","pushRedisMetrics","gatewayPush","clearAllCounters","metricsLogValues","metricObjects","registry","getMetricsAsJSON","startPush","_startPush","catch","cleanup","quit","disconnect","exit","on","module","exports"],"sources":["../../src/metrics/metricsRedisClient.js"],"sourcesContent":["const { BaseMetricsClient } = require('./baseMetricsClient')\nconst {\n getRedisClientType,\n REDIS_V4,\n IOREDIS,\n REDIS_V3,\n} = require('../redisUtils')\n\nconst redisConnectionStableFields = ['name', 'flags', 'cmd']\nconst redisConnectionFields = ['name', 'flags', 'tot-mem', 'cmd']\n\n/**\n * RedisMetricsClient extends BaseMetricsClient to collect\n * Redis metrics periodically and push them to Prometheus Pushgateway.\n *\n * @extends BaseMetricsClient\n */\nclass RedisMetricsClient extends BaseMetricsClient {\n /**\n * @param {Object} options\n * @param {any} options.redisClient - Redis client instance (required)\n * @param {string} [options.appName] - Application name (from BaseMetricsClient)\n * @param {string} [options.dynoId] - Dyno/instance ID (from BaseMetricsClient)\n * @param {string} [options.processType] - Process type (from BaseMetricsClient)\n * @param {boolean} [options.enabled] - Enable metrics collection (from BaseMetricsClient)\n * @param {boolean} [options.logValues] - Log metrics values (from BaseMetricsClient)\n * @param {string} [options.pushgatewayUrl] - PushGateway URL (from BaseMetricsClient)\n * @param {string} [options.pushgatewaySecret] - PushGateway secret token (from BaseMetricsClient)\n * @param {number} [options.intervalSec] - Interval in seconds for pushing metrics (from BaseMetricsClient)\n * @param {boolean} [options.removeOldMetrics] - Remove old metrics by service (from BaseMetricsClient)\n * @param {function} [options.startupValidation] - Function to validate startup (from BaseMetricsClient)\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 processType: metricsConfig.processType || 'queue-metrics',\n intervalSec,\n })\n\n /** Redis client used for metrics */\n this.redisClient = redisClient\n this.redisClientType = getRedisClientType(redisClient)\n\n /** Counter for Redis client connections */\n this.redisConnectionsGauge = this.createGauge({\n name: 'app_redis_connections_count',\n help: 'Redis client connections',\n labelNames: this.withDefaultLabels(redisConnectionStableFields),\n })\n\n this.redisConnectionsMemoryGauge = this.createGauge({\n name: 'app_redis_connections_memory_usage_count',\n help: 'Redis client connections',\n labelNames: this.withDefaultLabels(redisConnectionStableFields),\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 getRedisConnections = async () => {\n if (!this.redisClient) throw new Error('Redis client not provided')\n\n // node-redis v3 (uses callback)\n if (this.redisClientType === REDIS_V3) {\n return new Promise((resolve, reject) => {\n this.redisClient.send_command('CLIENT', ['LIST'], (err, result) => {\n if (err) {\n reject(new Error(`Failed to get CLIENT LIST: ${err.message}`))\n } else resolve(result)\n })\n })\n }\n\n // node-redis v4\n if (this.redisClientType === REDIS_V4) {\n try {\n return this.redisClient.sendCommand(['CLIENT', 'LIST'])\n } catch (err) {\n throw new Error(`Failed to get CLIENT LIST: ${err.message}`)\n }\n }\n\n if (this.redisClientType === IOREDIS) {\n try {\n return this.redisClient.client('LIST')\n } catch (err) {\n throw new Error(`Failed to get CLIENT LIST: ${err.message}`)\n }\n }\n\n throw new Error('Unsupported Redis client type')\n }\n\n getRedisInfo = async section => {\n if (!this.redisClient) throw new Error('Redis client not provided')\n\n // node-redis v3 (uses callback)\n if (this.redisClientType === REDIS_V3) {\n return new Promise((resolve, reject) => {\n this.redisClient.info(section, (err, result) => {\n if (err) reject(err)\n else resolve(result)\n })\n })\n }\n\n // node-redis v4 or ioredis (info returns Promise)\n if (this.redisClientType === REDIS_V4 || this.redisClientType === IOREDIS) {\n try {\n return this.redisClient.info(section)\n } catch (err) {\n throw new Error(`Failed to get Redis INFO: ${err.message}`)\n }\n }\n\n throw new Error('Unsupported Redis client type')\n }\n\n parseRedisConnections = clientsStr => {\n return clientsStr\n .split('\\n')\n .filter(line => line.trim() !== '')\n .map(line => {\n const parts = line.split(' ')\n const client = {}\n parts.forEach(p => {\n const [k, v] = p.split('=')\n if (redisConnectionFields.includes(k)) {\n client[k] = v\n }\n })\n return client\n })\n }\n\n /**\n * Collect basic Redis INFO metrics: clients, memory, stats\n * @returns {Promise<void>}\n */\n collectRedisMetrics = async () => {\n try {\n const [memoryInfoStr, statsInfoStr, connectionsInfoStr] =\n await Promise.all([\n this.getRedisInfo('memory'),\n this.getRedisInfo('stats'),\n this.getRedisConnections(),\n ])\n\n const labels = this.getDefaultLabels()\n\n const connections = this.parseRedisConnections(connectionsInfoStr)\n\n if (this.logValues) {\n console.log('Redis connections: ', connectionsInfoStr)\n console.log('Redis connections count: ', connections.length)\n }\n\n const grouped = {}\n connections.forEach(conn => {\n const { name, flags, 'tot-mem': totMem, cmd } = conn\n\n // build grouping key (includes default gauge labels later)\n const key = JSON.stringify({ name, flags, cmd })\n\n if (!grouped[key]) {\n grouped[key] = {\n labels: { name, flags, cmd },\n count: 0,\n memory: 0,\n }\n }\n\n grouped[key].count += 1\n grouped[key].memory += parseInt(totMem, 10)\n })\n\n Object.values(grouped).forEach(\n ({ labels: valueLabels, count, memory }) => {\n this.redisConnectionsGauge.set({ ...labels, ...valueLabels }, count)\n this.redisConnectionsMemoryGauge.set(\n { ...labels, ...valueLabels },\n memory\n )\n }\n )\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 memory = parseRedisInfo(memoryInfoStr)\n const stats = parseRedisInfo(statsInfoStr)\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 `[queue-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 this.clearAllCounters()\n\n if (this.metricsLogValues) {\n const metricObjects = await this.registry.getMetricsAsJSON()\n console.info(\n `[queue-metrics] Collected metrics for Redis`,\n JSON.stringify(metricObjects, null, 2)\n )\n }\n } catch (error) {\n console.error(\n `[queue-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(`[queue-metrics] Failed to push Redis metrics:`, err)\n })\n })\n }\n\n /**\n * Cleanup Redis client and exit process.\n * @returns {Promise<void>}\n */\n cleanup = async () => {\n try {\n if (!this.redisClient) return\n\n if (\n this.redisClientType === REDIS_V3 ||\n this.redisClientType === REDIS_V4\n ) {\n await this.redisClient.quit()\n } else if (this.redisClientType === IOREDIS) {\n await this.redisClient.disconnect()\n }\n } catch (err) {\n console.error('[queue-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":[]}
|
package/package.json
CHANGED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
const {
|
|
2
|
+
getRedisClientType,
|
|
3
|
+
REDIS_V4,
|
|
4
|
+
IOREDIS,
|
|
5
|
+
REDIS_V3,
|
|
6
|
+
} = require('../redisUtils')
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* HealthCheckCache provides a shared cache layer for health check results.
|
|
10
|
+
* It uses Redis if available for cross-process sharing, with graceful fallback
|
|
11
|
+
* to in-memory cache if Redis is not configured or unavailable.
|
|
12
|
+
*/
|
|
13
|
+
class HealthCheckCache {
|
|
14
|
+
/**
|
|
15
|
+
* @param {Object} options
|
|
16
|
+
* @param {any} [options.redisClient] - Redis client instance (optional)
|
|
17
|
+
* @param {string} [options.appName] - Application name for cache key
|
|
18
|
+
* @param {number} [options.cacheTtlMs=60000] - Cache TTL in milliseconds
|
|
19
|
+
*/
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
this.redisClient = options.redisClient || null
|
|
22
|
+
this.appName = options.appName || process.env.BUILD_APP_NAME || 'unknown-app'
|
|
23
|
+
this.cacheTtlMs = options.cacheTtlMs ?? 60 * 1000
|
|
24
|
+
this.cacheKey = `healthcheck:${this.appName}`
|
|
25
|
+
|
|
26
|
+
/** In-memory fallback cache */
|
|
27
|
+
this._memoryCache = null
|
|
28
|
+
this._memoryCacheTimestamp = null
|
|
29
|
+
|
|
30
|
+
if (this.redisClient) {
|
|
31
|
+
this._redisClientType = getRedisClientType(this.redisClient)
|
|
32
|
+
this._redisAvailable = true
|
|
33
|
+
} else {
|
|
34
|
+
this._redisAvailable = false
|
|
35
|
+
console.warn(
|
|
36
|
+
`[HealthCheckCache] Redis not configured for ${this.appName}, using in-memory cache only (not shared across processes)`
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Checks if Redis is available and working.
|
|
43
|
+
* @returns {Promise<boolean>}
|
|
44
|
+
* @private
|
|
45
|
+
*/
|
|
46
|
+
async _checkRedisAvailable() {
|
|
47
|
+
if (!this.redisClient || !this._redisAvailable) {
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
let pong
|
|
53
|
+
if (this._redisClientType === REDIS_V3) {
|
|
54
|
+
pong = await new Promise((resolve, reject) => {
|
|
55
|
+
this.redisClient.ping((err, result) => {
|
|
56
|
+
if (err) reject(err)
|
|
57
|
+
else resolve(result)
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
} else if (
|
|
61
|
+
this._redisClientType === REDIS_V4 ||
|
|
62
|
+
this._redisClientType === IOREDIS
|
|
63
|
+
) {
|
|
64
|
+
pong = await this.redisClient.ping()
|
|
65
|
+
} else {
|
|
66
|
+
return false
|
|
67
|
+
}
|
|
68
|
+
return pong === 'PONG'
|
|
69
|
+
} catch (err) {
|
|
70
|
+
// Redis not available
|
|
71
|
+
if (this._redisAvailable) {
|
|
72
|
+
console.warn(
|
|
73
|
+
`[HealthCheckCache] Redis became unavailable: ${err.message}`
|
|
74
|
+
)
|
|
75
|
+
this._redisAvailable = false
|
|
76
|
+
}
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Gets cached health check result from Redis (if available) or in-memory cache.
|
|
83
|
+
* Throws error if Redis is configured but read fails (so caller can return proper error format).
|
|
84
|
+
* @returns {Promise<Object | null>} Cached result or null
|
|
85
|
+
* @throws {Error} If Redis is configured but read fails
|
|
86
|
+
*/
|
|
87
|
+
async get() {
|
|
88
|
+
// If Redis is configured, we MUST read from it (don't fall back to memory)
|
|
89
|
+
if (this.redisClient) {
|
|
90
|
+
try {
|
|
91
|
+
let cachedStr
|
|
92
|
+
if (this._redisClientType === REDIS_V3) {
|
|
93
|
+
cachedStr = await new Promise((resolve, reject) => {
|
|
94
|
+
this.redisClient.get(this.cacheKey, (err, result) => {
|
|
95
|
+
if (err) reject(err)
|
|
96
|
+
else resolve(result)
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
} else if (
|
|
100
|
+
this._redisClientType === REDIS_V4 ||
|
|
101
|
+
this._redisClientType === IOREDIS
|
|
102
|
+
) {
|
|
103
|
+
cachedStr = await this.redisClient.get(this.cacheKey)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (cachedStr) {
|
|
107
|
+
try {
|
|
108
|
+
const cached = JSON.parse(cachedStr)
|
|
109
|
+
if (cached.result && cached.timestamp) {
|
|
110
|
+
const age = Date.now() - cached.timestamp
|
|
111
|
+
if (age < this.cacheTtlMs) {
|
|
112
|
+
// Also update in-memory cache as backup
|
|
113
|
+
this._memoryCache = cached.result
|
|
114
|
+
this._memoryCacheTimestamp = cached.timestamp
|
|
115
|
+
return cached.result
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
} catch (parseErr) {
|
|
119
|
+
console.warn(
|
|
120
|
+
`[HealthCheckCache] Failed to parse Redis cache:`,
|
|
121
|
+
parseErr.message
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// No cache in Redis - return null (worker may not have run yet)
|
|
126
|
+
return null
|
|
127
|
+
} catch (redisErr) {
|
|
128
|
+
// Redis read failed - throw error so caller can return proper error format
|
|
129
|
+
this._redisAvailable = false
|
|
130
|
+
throw new Error(`Redis cache read failed: ${redisErr.message}`)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// No Redis configured - fall back to in-memory cache
|
|
135
|
+
if (this._memoryCache && this._memoryCacheTimestamp) {
|
|
136
|
+
const age = Date.now() - this._memoryCacheTimestamp
|
|
137
|
+
if (age < this.cacheTtlMs) {
|
|
138
|
+
return this._memoryCache
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return null
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Sets cached health check result in Redis (if available) and in-memory.
|
|
147
|
+
* @param {Object} result - Health check result to cache
|
|
148
|
+
* @returns {Promise<void>}
|
|
149
|
+
*/
|
|
150
|
+
async set(result) {
|
|
151
|
+
const cacheData = {
|
|
152
|
+
result,
|
|
153
|
+
timestamp: Date.now(),
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Update in-memory cache
|
|
157
|
+
this._memoryCache = result
|
|
158
|
+
this._memoryCacheTimestamp = cacheData.timestamp
|
|
159
|
+
|
|
160
|
+
// Try to update Redis if available
|
|
161
|
+
if (await this._checkRedisAvailable()) {
|
|
162
|
+
try {
|
|
163
|
+
const cacheStr = JSON.stringify(cacheData)
|
|
164
|
+
const ttlSeconds = Math.ceil(this.cacheTtlMs / 1000) + 10
|
|
165
|
+
|
|
166
|
+
if (this._redisClientType === REDIS_V3) {
|
|
167
|
+
await new Promise((resolve, reject) => {
|
|
168
|
+
this.redisClient.setex(
|
|
169
|
+
this.cacheKey,
|
|
170
|
+
ttlSeconds,
|
|
171
|
+
cacheStr,
|
|
172
|
+
(err) => {
|
|
173
|
+
if (err) reject(err)
|
|
174
|
+
else resolve()
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
})
|
|
178
|
+
} else if (
|
|
179
|
+
this._redisClientType === REDIS_V4 ||
|
|
180
|
+
this._redisClientType === IOREDIS
|
|
181
|
+
) {
|
|
182
|
+
await this.redisClient.setex(this.cacheKey, ttlSeconds, cacheStr)
|
|
183
|
+
}
|
|
184
|
+
} catch (redisErr) {
|
|
185
|
+
// Redis write failed, but in-memory cache is updated
|
|
186
|
+
console.warn(
|
|
187
|
+
`[HealthCheckCache] Redis write failed (in-memory cache updated):`,
|
|
188
|
+
redisErr.message
|
|
189
|
+
)
|
|
190
|
+
this._redisAvailable = false
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Clears the cache (both Redis and in-memory).
|
|
197
|
+
* @returns {Promise<void>}
|
|
198
|
+
*/
|
|
199
|
+
async clear() {
|
|
200
|
+
this._memoryCache = null
|
|
201
|
+
this._memoryCacheTimestamp = null
|
|
202
|
+
|
|
203
|
+
if (await this._checkRedisAvailable()) {
|
|
204
|
+
try {
|
|
205
|
+
if (this._redisClientType === REDIS_V3) {
|
|
206
|
+
await new Promise((resolve, reject) => {
|
|
207
|
+
this.redisClient.del(this.cacheKey, (err) => {
|
|
208
|
+
if (err) reject(err)
|
|
209
|
+
else resolve()
|
|
210
|
+
})
|
|
211
|
+
})
|
|
212
|
+
} else if (
|
|
213
|
+
this._redisClientType === REDIS_V4 ||
|
|
214
|
+
this._redisClientType === IOREDIS
|
|
215
|
+
) {
|
|
216
|
+
await this.redisClient.del(this.cacheKey)
|
|
217
|
+
}
|
|
218
|
+
} catch (redisErr) {
|
|
219
|
+
console.warn(
|
|
220
|
+
`[HealthCheckCache] Redis clear failed:`,
|
|
221
|
+
redisErr.message
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Checks if Redis is configured and available.
|
|
229
|
+
* @returns {boolean}
|
|
230
|
+
*/
|
|
231
|
+
isRedisAvailable() {
|
|
232
|
+
return this._redisAvailable && this.redisClient !== null
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
module.exports = { HealthCheckCache }
|
|
237
|
+
|