@adalo/metrics 0.1.172 → 0.1.173

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.
@@ -31,7 +31,7 @@ const DEFAULT_HEALTH_CONFIG = {
31
31
  checkIntervalMs: 30_000,
32
32
  staleThresholdMs: 180_000,
33
33
  checkTimeoutMs: 15_000,
34
- maxDbConnectLatencyMs: readNumberEnv('HEALTH_DB_MAX_CONNECT_LATENCY_MS') ?? 1000
34
+ maxDbConnectLatencyMs: readNumberEnv('HEALTH_DB_MAX_CONNECT_LATENCY_MS') ?? 30_000
35
35
  };
36
36
  const SENSITIVE_PATTERNS = [{
37
37
  pattern: /(postgres(?:ql)?|mysql|mongodb|redis|amqp):\/\/([^:]+):([^@]+)@([^:/]+)(:\d+)?\/([^\s?]+)/gi,
@@ -1 +1 @@
1
- {"version":3,"file":"healthCheckClient.js","names":["createDatabasePool","runHealthCheck","closePool","require","getRedisClientType","REDIS_V4","IOREDIS","REDIS_V3","HealthCheckCache","readNumberEnv","name","raw","process","env","undefined","num","Number","isFinite","DEFAULT_HEALTH_CONFIG","checkIntervalMs","staleThresholdMs","checkTimeoutMs","maxDbConnectLatencyMs","SENSITIVE_PATTERNS","pattern","replacement","maskSensitiveData","text","masked","replace","HealthCheckClient","constructor","options","healthConfig","config","appName","BUILD_APP_NAME","prefixLogs","_refreshPromise","_databasePools","Map","_resources","resources","redisClient","_getRedisClientForCache","_redisClientType","_cache","cacheKey","_getEnv","resource","redisResource","find","r","client","_getPool","url","has","pool","type","set","get","_checkDatabase","status","error","timings","maxConnect","connectMs","err","healthCheckTimings","message","_checkRedis","pong","Promise","resolve","reject","ping","result","_performHealthCheckInternal","sortedResources","Object","keys","sort","reduce","acc","key","hasError","values","some","lastCheckAt","Date","now","isStale","_formatResult","cached","performHealthCheck","then","catch","getCachedResult","console","refreshCache","clearCache","_log503Failure","req","payload","requestId","headers","id","line","msg","request_id","resourceErrors","entries","filter","map","warn","JSON","stringify","healthHandler","res","body","json","statusCode","cleanup","clear","module","exports"],"sources":["../../src/health/healthCheckClient.js"],"sourcesContent":["const {\n createDatabasePool,\n runHealthCheck,\n closePool,\n} = require('./databaseChecker')\nconst {\n getRedisClientType,\n REDIS_V4,\n IOREDIS,\n REDIS_V3,\n} = require('../redisUtils')\nconst { HealthCheckCache } = require('./healthCheckCache')\n\n/**\n * @param {string} name\n * @returns {number | undefined}\n */\nfunction readNumberEnv(name) {\n const raw = process.env[name]\n if (raw == null || raw === '') return undefined\n const num = Number(raw)\n return Number.isFinite(num) ? num : undefined\n}\n\n/** @type {{ checkIntervalMs: number, staleThresholdMs: number, checkTimeoutMs: number, maxDbConnectLatencyMs: number }} */\nconst DEFAULT_HEALTH_CONFIG = {\n checkIntervalMs: 30_000,\n staleThresholdMs: 180_000,\n checkTimeoutMs: 15_000,\n maxDbConnectLatencyMs:\n readNumberEnv('HEALTH_DB_MAX_CONNECT_LATENCY_MS') ?? 1000,\n}\n\nconst SENSITIVE_PATTERNS = [\n {\n pattern:\n /(postgres(?:ql)?|mysql|mongodb|redis|amqp):\\/\\/([^:]+):([^@]+)@([^:/]+)(:\\d+)?\\/([^\\s?]+)/gi,\n replacement: '$1://***:***@***$5/***',\n },\n {\n pattern: /(\\w+):\\/\\/([^:]+):([^@]+)@([^\\s/]+)/gi,\n replacement: '$1://***:***@***',\n },\n {\n pattern:\n /(password|passwd|pwd|secret|token|api[_-]?key|auth[_-]?token)[\"\\s]*[:=][\"\\s]*([^\\s,}\"]+)/gi,\n replacement: '$1=***',\n },\n {\n pattern:\n /(database|table|schema|role|user|relation|column|index)\\s*[\"']([^\"']+)[\"']/gi,\n replacement: '$1 \"***\"',\n },\n {\n pattern: /\\b(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})(:\\d+)?\\b/g,\n replacement: '***$2',\n },\n {\n pattern: /\\b(host|hostname|server)[\"\\s]*[:=][\"\\s]*([^\\s,}\"]+)/gi,\n replacement: '$1=***',\n },\n]\n\n/**\n * @param {string} text\n * @returns {string}\n */\nfunction maskSensitiveData(text) {\n if (!text || typeof text !== 'string') return text\n let masked = text\n for (const { pattern, replacement } of SENSITIVE_PATTERNS) {\n masked = masked.replace(pattern, replacement)\n }\n return masked\n}\n\n/**\n * @typedef {{ env: string, url?: string } | { env: string, client?: any }} HealthResource\n */\nclass HealthCheckClient {\n /**\n * @param {Object} options\n * @param {HealthResource[]} options.resources - Must include Redis resource with client\n * @param {Object} [options.config]\n * @param {string} [options.appName] - For cache key: healthcheck:${appName}\n * @param {string} [options.cacheKey] - Redis key (overrides appName)\n */\n constructor(options = {}) {\n this.healthConfig = { ...DEFAULT_HEALTH_CONFIG, ...options.config }\n this.appName =\n options.appName || process.env.BUILD_APP_NAME || 'unknown-app'\n\n this.prefixLogs = `[${this.appName}] [HealthCheck]`\n\n this._refreshPromise = null\n /** @type {Map<string, { pool: any, type: string }>} */\n this._databasePools = new Map()\n\n /** @type {HealthResource[]} */\n this._resources = options.resources || []\n\n const redisClient = this._getRedisClientForCache()\n if (redisClient) {\n this._redisClientType = getRedisClientType(redisClient)\n }\n\n this._cache = new HealthCheckCache({\n redisClient: redisClient || null,\n cacheKey: options.cacheKey,\n appName: this.appName,\n staleThresholdMs: this.healthConfig.staleThresholdMs,\n })\n }\n\n _getEnv(resource) {\n return resource.env ?? resource.name\n }\n\n _getRedisClientForCache() {\n const redisResource = this._resources.find(r => 'client' in r && r.client)\n return redisResource?.client || null\n }\n\n _getPool(env, url) {\n if (!this._databasePools.has(env)) {\n const { pool, type } = createDatabasePool(\n env,\n url,\n this.healthConfig.checkTimeoutMs\n )\n this._databasePools.set(env, { pool, type })\n }\n return this._databasePools.get(env)\n }\n\n async _checkDatabase(resource) {\n const env = this._getEnv(resource)\n const url = 'url' in resource ? resource.url : process.env[env]\n if (!url) {\n return { status: 'error', error: `Env ${env} not set` }\n }\n\n try {\n const { pool, type } = this._getPool(env, url)\n const timings = await runHealthCheck(pool, type)\n\n const maxConnect = this.healthConfig.maxDbConnectLatencyMs\n if (\n typeof maxConnect === 'number' &&\n Number.isFinite(maxConnect) &&\n timings?.connectMs != null &&\n timings.connectMs > maxConnect\n ) {\n return {\n status: 'error',\n error: `DB connect latency ${timings.connectMs}ms exceeds ${maxConnect}ms`,\n connectMs: timings.connectMs,\n }\n }\n\n return { status: 'ok', connectMs: timings.connectMs }\n } catch (err) {\n const timings = err?.healthCheckTimings\n return {\n status: 'error',\n error: maskSensitiveData(err.message),\n ...(timings && typeof timings === 'object'\n ? { connectMs: timings.connectMs }\n : {}),\n }\n }\n }\n\n async _checkRedis(resource) {\n const { client } = resource\n if (!client) return { status: 'ok' }\n\n try {\n let pong\n if (this._redisClientType === REDIS_V3) {\n pong = await new Promise((resolve, reject) => {\n client.ping((err, result) => {\n if (err) reject(err)\n else resolve(result)\n })\n })\n } else if (\n this._redisClientType === REDIS_V4 ||\n this._redisClientType === IOREDIS\n ) {\n pong = await client.ping()\n } else {\n return { status: 'error', error: 'Unknown Redis client type' }\n }\n\n return pong === 'PONG'\n ? { status: 'ok' }\n : { status: 'error', error: `Unexpected: ${pong}` }\n } catch (err) {\n return { status: 'error', error: maskSensitiveData(err.message) }\n }\n }\n\n async _performHealthCheckInternal() {\n const resources = {}\n\n for (const resource of this._resources) {\n const env = this._getEnv(resource)\n\n if ('client' in resource && resource.client) {\n resources[env] = await this._checkRedis(resource)\n } else {\n resources[env] = await this._checkDatabase(resource)\n }\n }\n\n const sortedResources = Object.keys(resources)\n .sort()\n .reduce((acc, key) => {\n acc[key] = resources[key]\n return acc\n }, {})\n\n const hasError = Object.values(resources).some(r => r.status === 'error')\n const lastCheckAt = Date.now()\n\n return {\n status: hasError ? 'error' : 'ok',\n lastCheckAt,\n resources: sortedResources,\n isStale: false,\n config: this.healthConfig,\n }\n }\n\n _formatResult(result, cached = false) {\n const isStale =\n !result.lastCheckAt ||\n Date.now() - result.lastCheckAt > this.healthConfig.staleThresholdMs\n\n return {\n ...result,\n isStale,\n status: isStale ? 'stale' : result.status,\n ...(isStale && {\n error:\n 'Health check data is stale, health-check worker may not be running. Resource statuses are unknown.',\n }),\n ...(cached && { cached: true }),\n }\n }\n\n async performHealthCheck() {\n if (this._refreshPromise) {\n return this._refreshPromise\n }\n\n this._refreshPromise = this._performHealthCheckInternal()\n .then(result => {\n this._refreshPromise = null\n return this._formatResult(result)\n })\n .catch(err => {\n this._refreshPromise = null\n throw err\n })\n\n return this._refreshPromise\n }\n\n async getCachedResult() {\n try {\n const cached = await this._cache.get()\n if (cached) return this._formatResult(cached)\n return null\n } catch (err) {\n console.error(`${this.prefixLogs} Failed to read from cache:`, err)\n return {\n status: 'error',\n lastCheckAt: null,\n resources: {},\n isStale: true,\n error:\n 'Redis unavailable, unable to read health status of other resources',\n config: this.healthConfig,\n }\n }\n }\n\n async refreshCache() {\n const result = await this._performHealthCheckInternal()\n await this._cache.set(result)\n return result\n }\n\n clearCache() {\n this._refreshPromise = null\n }\n\n /**\n * Logs a structured line when health returns 503 so log drains / Loki can store\n * and query failed health data. Includes request_id when present for correlation\n * with router logs.\n * @param {object} req - Express request (for request_id header)\n * @param {object} payload - Safe summary of the 503 response body\n */\n _log503Failure(req, payload) {\n const requestId =\n (req && req.headers && req.headers['x-request-id']) ||\n (req && req.id) ||\n null\n const line = {\n msg: 'HealthCheck 503',\n appName: this.appName,\n request_id: requestId,\n error: payload.error,\n isStale: payload.isStale,\n status: payload.status,\n resourceErrors:\n payload.resources &&\n Object.entries(payload.resources)\n .filter(([, r]) => r && r.status === 'error')\n .map(([env, r]) => ({ env, error: r.error })),\n }\n console.warn(JSON.stringify(line))\n }\n\n healthHandler() {\n return async (req, res) => {\n try {\n const result = await this.getCachedResult()\n\n if (!result) {\n const body = {\n status: 'error',\n lastCheckAt: null,\n resources: {},\n isStale: true,\n error:\n 'No health check data yet, health-check worker may not be running',\n config: this.healthConfig,\n }\n this._log503Failure(req, body)\n res.status(503).json(body)\n return\n }\n\n const statusCode = result.status === 'ok' ? 200 : 503\n if (statusCode === 503) {\n this._log503Failure(req, result)\n }\n res.status(statusCode).json(result)\n } catch (err) {\n console.error(`${this.prefixLogs} Health check failed:`, err)\n const body = {\n status: 'error',\n lastCheckAt: null,\n resources: {},\n isStale: true,\n error:\n 'Redis unavailable, unable to read health status of other resources',\n config: this.healthConfig,\n }\n this._log503Failure(req, body)\n res.status(503).json(body)\n }\n }\n }\n\n async cleanup() {\n for (const [, { pool }] of this._databasePools) {\n try {\n await closePool(pool)\n } catch (err) {\n console.error(`${this.prefixLogs} Error closing database pool:`, err)\n }\n }\n this._databasePools.clear()\n }\n}\n\nmodule.exports = { HealthCheckClient, DEFAULT_HEALTH_CONFIG }\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"healthCheckClient.js","names":["createDatabasePool","runHealthCheck","closePool","require","getRedisClientType","REDIS_V4","IOREDIS","REDIS_V3","HealthCheckCache","readNumberEnv","name","raw","process","env","undefined","num","Number","isFinite","DEFAULT_HEALTH_CONFIG","checkIntervalMs","staleThresholdMs","checkTimeoutMs","maxDbConnectLatencyMs","SENSITIVE_PATTERNS","pattern","replacement","maskSensitiveData","text","masked","replace","HealthCheckClient","constructor","options","healthConfig","config","appName","BUILD_APP_NAME","prefixLogs","_refreshPromise","_databasePools","Map","_resources","resources","redisClient","_getRedisClientForCache","_redisClientType","_cache","cacheKey","_getEnv","resource","redisResource","find","r","client","_getPool","url","has","pool","type","set","get","_checkDatabase","status","error","timings","maxConnect","connectMs","err","healthCheckTimings","message","_checkRedis","pong","Promise","resolve","reject","ping","result","_performHealthCheckInternal","sortedResources","Object","keys","sort","reduce","acc","key","hasError","values","some","lastCheckAt","Date","now","isStale","_formatResult","cached","performHealthCheck","then","catch","getCachedResult","console","refreshCache","clearCache","_log503Failure","req","payload","requestId","headers","id","line","msg","request_id","resourceErrors","entries","filter","map","warn","JSON","stringify","healthHandler","res","body","json","statusCode","cleanup","clear","module","exports"],"sources":["../../src/health/healthCheckClient.js"],"sourcesContent":["const {\n createDatabasePool,\n runHealthCheck,\n closePool,\n} = require('./databaseChecker')\nconst {\n getRedisClientType,\n REDIS_V4,\n IOREDIS,\n REDIS_V3,\n} = require('../redisUtils')\nconst { HealthCheckCache } = require('./healthCheckCache')\n\n/**\n * @param {string} name\n * @returns {number | undefined}\n */\nfunction readNumberEnv(name) {\n const raw = process.env[name]\n if (raw == null || raw === '') return undefined\n const num = Number(raw)\n return Number.isFinite(num) ? num : undefined\n}\n\n/** @type {{ checkIntervalMs: number, staleThresholdMs: number, checkTimeoutMs: number, maxDbConnectLatencyMs: number }} */\nconst DEFAULT_HEALTH_CONFIG = {\n checkIntervalMs: 30_000,\n staleThresholdMs: 180_000,\n checkTimeoutMs: 15_000,\n maxDbConnectLatencyMs:\n readNumberEnv('HEALTH_DB_MAX_CONNECT_LATENCY_MS') ?? 30_000,\n}\n\nconst SENSITIVE_PATTERNS = [\n {\n pattern:\n /(postgres(?:ql)?|mysql|mongodb|redis|amqp):\\/\\/([^:]+):([^@]+)@([^:/]+)(:\\d+)?\\/([^\\s?]+)/gi,\n replacement: '$1://***:***@***$5/***',\n },\n {\n pattern: /(\\w+):\\/\\/([^:]+):([^@]+)@([^\\s/]+)/gi,\n replacement: '$1://***:***@***',\n },\n {\n pattern:\n /(password|passwd|pwd|secret|token|api[_-]?key|auth[_-]?token)[\"\\s]*[:=][\"\\s]*([^\\s,}\"]+)/gi,\n replacement: '$1=***',\n },\n {\n pattern:\n /(database|table|schema|role|user|relation|column|index)\\s*[\"']([^\"']+)[\"']/gi,\n replacement: '$1 \"***\"',\n },\n {\n pattern: /\\b(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})(:\\d+)?\\b/g,\n replacement: '***$2',\n },\n {\n pattern: /\\b(host|hostname|server)[\"\\s]*[:=][\"\\s]*([^\\s,}\"]+)/gi,\n replacement: '$1=***',\n },\n]\n\n/**\n * @param {string} text\n * @returns {string}\n */\nfunction maskSensitiveData(text) {\n if (!text || typeof text !== 'string') return text\n let masked = text\n for (const { pattern, replacement } of SENSITIVE_PATTERNS) {\n masked = masked.replace(pattern, replacement)\n }\n return masked\n}\n\n/**\n * @typedef {{ env: string, url?: string } | { env: string, client?: any }} HealthResource\n */\nclass HealthCheckClient {\n /**\n * @param {Object} options\n * @param {HealthResource[]} options.resources - Must include Redis resource with client\n * @param {Object} [options.config]\n * @param {string} [options.appName] - For cache key: healthcheck:${appName}\n * @param {string} [options.cacheKey] - Redis key (overrides appName)\n */\n constructor(options = {}) {\n this.healthConfig = { ...DEFAULT_HEALTH_CONFIG, ...options.config }\n this.appName =\n options.appName || process.env.BUILD_APP_NAME || 'unknown-app'\n\n this.prefixLogs = `[${this.appName}] [HealthCheck]`\n\n this._refreshPromise = null\n /** @type {Map<string, { pool: any, type: string }>} */\n this._databasePools = new Map()\n\n /** @type {HealthResource[]} */\n this._resources = options.resources || []\n\n const redisClient = this._getRedisClientForCache()\n if (redisClient) {\n this._redisClientType = getRedisClientType(redisClient)\n }\n\n this._cache = new HealthCheckCache({\n redisClient: redisClient || null,\n cacheKey: options.cacheKey,\n appName: this.appName,\n staleThresholdMs: this.healthConfig.staleThresholdMs,\n })\n }\n\n _getEnv(resource) {\n return resource.env ?? resource.name\n }\n\n _getRedisClientForCache() {\n const redisResource = this._resources.find(r => 'client' in r && r.client)\n return redisResource?.client || null\n }\n\n _getPool(env, url) {\n if (!this._databasePools.has(env)) {\n const { pool, type } = createDatabasePool(\n env,\n url,\n this.healthConfig.checkTimeoutMs\n )\n this._databasePools.set(env, { pool, type })\n }\n return this._databasePools.get(env)\n }\n\n async _checkDatabase(resource) {\n const env = this._getEnv(resource)\n const url = 'url' in resource ? resource.url : process.env[env]\n if (!url) {\n return { status: 'error', error: `Env ${env} not set` }\n }\n\n try {\n const { pool, type } = this._getPool(env, url)\n const timings = await runHealthCheck(pool, type)\n\n const maxConnect = this.healthConfig.maxDbConnectLatencyMs\n if (\n typeof maxConnect === 'number' &&\n Number.isFinite(maxConnect) &&\n timings?.connectMs != null &&\n timings.connectMs > maxConnect\n ) {\n return {\n status: 'error',\n error: `DB connect latency ${timings.connectMs}ms exceeds ${maxConnect}ms`,\n connectMs: timings.connectMs,\n }\n }\n\n return { status: 'ok', connectMs: timings.connectMs }\n } catch (err) {\n const timings = err?.healthCheckTimings\n return {\n status: 'error',\n error: maskSensitiveData(err.message),\n ...(timings && typeof timings === 'object'\n ? { connectMs: timings.connectMs }\n : {}),\n }\n }\n }\n\n async _checkRedis(resource) {\n const { client } = resource\n if (!client) return { status: 'ok' }\n\n try {\n let pong\n if (this._redisClientType === REDIS_V3) {\n pong = await new Promise((resolve, reject) => {\n client.ping((err, result) => {\n if (err) reject(err)\n else resolve(result)\n })\n })\n } else if (\n this._redisClientType === REDIS_V4 ||\n this._redisClientType === IOREDIS\n ) {\n pong = await client.ping()\n } else {\n return { status: 'error', error: 'Unknown Redis client type' }\n }\n\n return pong === 'PONG'\n ? { status: 'ok' }\n : { status: 'error', error: `Unexpected: ${pong}` }\n } catch (err) {\n return { status: 'error', error: maskSensitiveData(err.message) }\n }\n }\n\n async _performHealthCheckInternal() {\n const resources = {}\n\n for (const resource of this._resources) {\n const env = this._getEnv(resource)\n\n if ('client' in resource && resource.client) {\n resources[env] = await this._checkRedis(resource)\n } else {\n resources[env] = await this._checkDatabase(resource)\n }\n }\n\n const sortedResources = Object.keys(resources)\n .sort()\n .reduce((acc, key) => {\n acc[key] = resources[key]\n return acc\n }, {})\n\n const hasError = Object.values(resources).some(r => r.status === 'error')\n const lastCheckAt = Date.now()\n\n return {\n status: hasError ? 'error' : 'ok',\n lastCheckAt,\n resources: sortedResources,\n isStale: false,\n config: this.healthConfig,\n }\n }\n\n _formatResult(result, cached = false) {\n const isStale =\n !result.lastCheckAt ||\n Date.now() - result.lastCheckAt > this.healthConfig.staleThresholdMs\n\n return {\n ...result,\n isStale,\n status: isStale ? 'stale' : result.status,\n ...(isStale && {\n error:\n 'Health check data is stale, health-check worker may not be running. Resource statuses are unknown.',\n }),\n ...(cached && { cached: true }),\n }\n }\n\n async performHealthCheck() {\n if (this._refreshPromise) {\n return this._refreshPromise\n }\n\n this._refreshPromise = this._performHealthCheckInternal()\n .then(result => {\n this._refreshPromise = null\n return this._formatResult(result)\n })\n .catch(err => {\n this._refreshPromise = null\n throw err\n })\n\n return this._refreshPromise\n }\n\n async getCachedResult() {\n try {\n const cached = await this._cache.get()\n if (cached) return this._formatResult(cached)\n return null\n } catch (err) {\n console.error(`${this.prefixLogs} Failed to read from cache:`, err)\n return {\n status: 'error',\n lastCheckAt: null,\n resources: {},\n isStale: true,\n error:\n 'Redis unavailable, unable to read health status of other resources',\n config: this.healthConfig,\n }\n }\n }\n\n async refreshCache() {\n const result = await this._performHealthCheckInternal()\n await this._cache.set(result)\n return result\n }\n\n clearCache() {\n this._refreshPromise = null\n }\n\n /**\n * Logs a structured line when health returns 503 so log drains / Loki can store\n * and query failed health data. Includes request_id when present for correlation\n * with router logs.\n * @param {object} req - Express request (for request_id header)\n * @param {object} payload - Safe summary of the 503 response body\n */\n _log503Failure(req, payload) {\n const requestId =\n (req && req.headers && req.headers['x-request-id']) ||\n (req && req.id) ||\n null\n const line = {\n msg: 'HealthCheck 503',\n appName: this.appName,\n request_id: requestId,\n error: payload.error,\n isStale: payload.isStale,\n status: payload.status,\n resourceErrors:\n payload.resources &&\n Object.entries(payload.resources)\n .filter(([, r]) => r && r.status === 'error')\n .map(([env, r]) => ({ env, error: r.error })),\n }\n console.warn(JSON.stringify(line))\n }\n\n healthHandler() {\n return async (req, res) => {\n try {\n const result = await this.getCachedResult()\n\n if (!result) {\n const body = {\n status: 'error',\n lastCheckAt: null,\n resources: {},\n isStale: true,\n error:\n 'No health check data yet, health-check worker may not be running',\n config: this.healthConfig,\n }\n this._log503Failure(req, body)\n res.status(503).json(body)\n return\n }\n\n const statusCode = result.status === 'ok' ? 200 : 503\n if (statusCode === 503) {\n this._log503Failure(req, result)\n }\n res.status(statusCode).json(result)\n } catch (err) {\n console.error(`${this.prefixLogs} Health check failed:`, err)\n const body = {\n status: 'error',\n lastCheckAt: null,\n resources: {},\n isStale: true,\n error:\n 'Redis unavailable, unable to read health status of other resources',\n config: this.healthConfig,\n }\n this._log503Failure(req, body)\n res.status(503).json(body)\n }\n }\n }\n\n async cleanup() {\n for (const [, { pool }] of this._databasePools) {\n try {\n await closePool(pool)\n } catch (err) {\n console.error(`${this.prefixLogs} Error closing database pool:`, err)\n }\n }\n this._databasePools.clear()\n }\n}\n\nmodule.exports = { HealthCheckClient, DEFAULT_HEALTH_CONFIG }\n"],"mappings":"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","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"healthCheckWorker.d.ts","sourceRoot":"","sources":["../../src/health/healthCheckWorker.js"],"names":[],"mappings":"AAYA,2EA0DC"}
1
+ {"version":3,"file":"healthCheckWorker.d.ts","sourceRoot":"","sources":["../../src/health/healthCheckWorker.js"],"names":[],"mappings":"AA2BA,2EA4DC"}
@@ -19,6 +19,18 @@ const {
19
19
  const {
20
20
  DEFAULT_HEALTH_CONFIG
21
21
  } = require('./healthCheckClient');
22
+
23
+ /** Latency above this (ms) triggers a warning log on the worker. 2000 = 2 seconds. */
24
+ const LATENCY_WARNING_MS = 2000;
25
+ function logLatencyWarnings(prefixLogs, result) {
26
+ const resources = result?.resources || {};
27
+ for (const [env, r] of Object.entries(resources)) {
28
+ const connectMs = r?.connectMs;
29
+ if (typeof connectMs === 'number' && connectMs > LATENCY_WARNING_MS) {
30
+ console.warn(`${prefixLogs} DB connect latency high: ${env} connectMs=${connectMs}ms (threshold ${LATENCY_WARNING_MS}ms)`);
31
+ }
32
+ }
33
+ }
22
34
  function createHealthCheckWorker(options) {
23
35
  const {
24
36
  refreshIntervalMs = DEFAULT_HEALTH_CONFIG.checkIntervalMs,
@@ -34,7 +46,8 @@ function createHealthCheckWorker(options) {
34
46
  console.log(`${prefixLogs} Starting health check worker...`);
35
47
  console.log(`${prefixLogs} Refresh interval: ${refreshIntervalMs}ms`);
36
48
  try {
37
- await healthCheckClient.refreshCache();
49
+ const result = await healthCheckClient.refreshCache();
50
+ logLatencyWarnings(prefixLogs, result);
38
51
  if (logValues) {
39
52
  console.log(`${prefixLogs} Initial health check completed`);
40
53
  }
@@ -44,6 +57,7 @@ function createHealthCheckWorker(options) {
44
57
  const interval = setInterval(async () => {
45
58
  try {
46
59
  const result = await healthCheckClient.refreshCache();
60
+ logLatencyWarnings(prefixLogs, result);
47
61
  if (logValues) {
48
62
  console.log(`${prefixLogs} Health check refreshed at ${result.lastCheckAt}, status: ${result.status}`);
49
63
  }
@@ -1 +1 @@
1
- {"version":3,"file":"healthCheckWorker.js","names":["createHealthCheckWorkerClient","require","DEFAULT_HEALTH_CONFIG","createHealthCheckWorker","options","refreshIntervalMs","checkIntervalMs","workerClientOptions","appName","process","env","BUILD_APP_NAME","dynoId","HOSTNAME","processType","BUILD_DYNO_PROCESS_TYPE","logValues","HEALTH_LOG_VALUES","prefixLogs","healthCheckClient","runHealthCheckWorker","console","log","refreshCache","err","error","interval","setInterval","result","lastCheckAt","status","on","clearInterval","cleanup","finally","exit"],"sources":["../../src/health/healthCheckWorker.js"],"sourcesContent":["/**\n * @param {Object} options\n * @param {{ name: string } | { name: string, url?: string } | { name: string, client: any }}[] options.resources - Must include Redis resource with client\n * @param {string} [options.appName]\n * @param {number} [options.refreshIntervalMs]\n * @param {string} [options.databaseUrl]\n * @param {string} [options.databaseName]\n * @param {Object<string, string>} [options.additionalDatabaseUrls]\n */\nconst { createHealthCheckWorkerClient } = require('./healthCheckUtils')\nconst { DEFAULT_HEALTH_CONFIG } = require('./healthCheckClient')\n\nexport function createHealthCheckWorker(options) {\n const {\n refreshIntervalMs = DEFAULT_HEALTH_CONFIG.checkIntervalMs,\n ...workerClientOptions\n } = options\n\n const appName =\n workerClientOptions.appName || process.env.BUILD_APP_NAME || 'unknown-app'\n const dynoId = process.env.HOSTNAME || 'unknown-dyno'\n const processType =\n process.env.BUILD_DYNO_PROCESS_TYPE || 'health-check-worker'\n const logValues = process.env.HEALTH_LOG_VALUES === 'true'\n const prefixLogs = `[${processType}] [${appName}] [${dynoId}] [HealthCheck]`\n\n const healthCheckClient = createHealthCheckWorkerClient(workerClientOptions)\n\n return async function runHealthCheckWorker() {\n console.log(`${prefixLogs} Starting health check worker...`)\n console.log(`${prefixLogs} Refresh interval: ${refreshIntervalMs}ms`)\n\n try {\n await healthCheckClient.refreshCache()\n if (logValues) {\n console.log(`${prefixLogs} Initial health check completed`)\n }\n } catch (err) {\n console.error(`${prefixLogs} Initial health check failed:`, err)\n }\n\n const interval = setInterval(async () => {\n try {\n const result = await healthCheckClient.refreshCache()\n if (logValues) {\n console.log(\n `${prefixLogs} Health check refreshed at ${result.lastCheckAt}, status: ${result.status}`\n )\n }\n } catch (err) {\n console.error(`${prefixLogs} Health check refresh failed:`, err)\n }\n }, refreshIntervalMs)\n\n process.on('SIGTERM', () => {\n console.log(`${prefixLogs} Received SIGTERM, shutting down...`)\n clearInterval(interval)\n healthCheckClient.cleanup().finally(() => {\n process.exit(0)\n })\n })\n\n process.on('SIGINT', () => {\n console.log(`${prefixLogs} Received SIGINT, shutting down...`)\n clearInterval(interval)\n healthCheckClient.cleanup().finally(() => {\n process.exit(0)\n })\n })\n }\n}\n"],"mappings":";;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;EAAEA;AAA8B,CAAC,GAAGC,OAAO,CAAC,oBAAoB,CAAC;AACvE,MAAM;EAAEC;AAAsB,CAAC,GAAGD,OAAO,CAAC,qBAAqB,CAAC;AAEzD,SAASE,uBAAuBA,CAACC,OAAO,EAAE;EAC/C,MAAM;IACJC,iBAAiB,GAAGH,qBAAqB,CAACI,eAAe;IACzD,GAAGC;EACL,CAAC,GAAGH,OAAO;EAEX,MAAMI,OAAO,GACXD,mBAAmB,CAACC,OAAO,IAAIC,OAAO,CAACC,GAAG,CAACC,cAAc,IAAI,aAAa;EAC5E,MAAMC,MAAM,GAAGH,OAAO,CAACC,GAAG,CAACG,QAAQ,IAAI,cAAc;EACrD,MAAMC,WAAW,GACfL,OAAO,CAACC,GAAG,CAACK,uBAAuB,IAAI,qBAAqB;EAC9D,MAAMC,SAAS,GAAGP,OAAO,CAACC,GAAG,CAACO,iBAAiB,KAAK,MAAM;EAC1D,MAAMC,UAAU,GAAG,IAAIJ,WAAW,MAAMN,OAAO,MAAMI,MAAM,iBAAiB;EAE5E,MAAMO,iBAAiB,GAAGnB,6BAA6B,CAACO,mBAAmB,CAAC;EAE5E,OAAO,eAAea,oBAAoBA,CAAA,EAAG;IAC3CC,OAAO,CAACC,GAAG,CAAC,GAAGJ,UAAU,kCAAkC,CAAC;IAC5DG,OAAO,CAACC,GAAG,CAAC,GAAGJ,UAAU,sBAAsBb,iBAAiB,IAAI,CAAC;IAErE,IAAI;MACF,MAAMc,iBAAiB,CAACI,YAAY,CAAC,CAAC;MACtC,IAAIP,SAAS,EAAE;QACbK,OAAO,CAACC,GAAG,CAAC,GAAGJ,UAAU,iCAAiC,CAAC;MAC7D;IACF,CAAC,CAAC,OAAOM,GAAG,EAAE;MACZH,OAAO,CAACI,KAAK,CAAC,GAAGP,UAAU,+BAA+B,EAAEM,GAAG,CAAC;IAClE;IAEA,MAAME,QAAQ,GAAGC,WAAW,CAAC,YAAY;MACvC,IAAI;QACF,MAAMC,MAAM,GAAG,MAAMT,iBAAiB,CAACI,YAAY,CAAC,CAAC;QACrD,IAAIP,SAAS,EAAE;UACbK,OAAO,CAACC,GAAG,CACT,GAAGJ,UAAU,8BAA8BU,MAAM,CAACC,WAAW,aAAaD,MAAM,CAACE,MAAM,EACzF,CAAC;QACH;MACF,CAAC,CAAC,OAAON,GAAG,EAAE;QACZH,OAAO,CAACI,KAAK,CAAC,GAAGP,UAAU,+BAA+B,EAAEM,GAAG,CAAC;MAClE;IACF,CAAC,EAAEnB,iBAAiB,CAAC;IAErBI,OAAO,CAACsB,EAAE,CAAC,SAAS,EAAE,MAAM;MAC1BV,OAAO,CAACC,GAAG,CAAC,GAAGJ,UAAU,qCAAqC,CAAC;MAC/Dc,aAAa,CAACN,QAAQ,CAAC;MACvBP,iBAAiB,CAACc,OAAO,CAAC,CAAC,CAACC,OAAO,CAAC,MAAM;QACxCzB,OAAO,CAAC0B,IAAI,CAAC,CAAC,CAAC;MACjB,CAAC,CAAC;IACJ,CAAC,CAAC;IAEF1B,OAAO,CAACsB,EAAE,CAAC,QAAQ,EAAE,MAAM;MACzBV,OAAO,CAACC,GAAG,CAAC,GAAGJ,UAAU,oCAAoC,CAAC;MAC9Dc,aAAa,CAACN,QAAQ,CAAC;MACvBP,iBAAiB,CAACc,OAAO,CAAC,CAAC,CAACC,OAAO,CAAC,MAAM;QACxCzB,OAAO,CAAC0B,IAAI,CAAC,CAAC,CAAC;MACjB,CAAC,CAAC;IACJ,CAAC,CAAC;EACJ,CAAC;AACH","ignoreList":[]}
1
+ {"version":3,"file":"healthCheckWorker.js","names":["createHealthCheckWorkerClient","require","DEFAULT_HEALTH_CONFIG","LATENCY_WARNING_MS","logLatencyWarnings","prefixLogs","result","resources","env","r","Object","entries","connectMs","console","warn","createHealthCheckWorker","options","refreshIntervalMs","checkIntervalMs","workerClientOptions","appName","process","BUILD_APP_NAME","dynoId","HOSTNAME","processType","BUILD_DYNO_PROCESS_TYPE","logValues","HEALTH_LOG_VALUES","healthCheckClient","runHealthCheckWorker","log","refreshCache","err","error","interval","setInterval","lastCheckAt","status","on","clearInterval","cleanup","finally","exit"],"sources":["../../src/health/healthCheckWorker.js"],"sourcesContent":["/**\n * @param {Object} options\n * @param {{ name: string } | { name: string, url?: string } | { name: string, client: any }}[] options.resources - Must include Redis resource with client\n * @param {string} [options.appName]\n * @param {number} [options.refreshIntervalMs]\n * @param {string} [options.databaseUrl]\n * @param {string} [options.databaseName]\n * @param {Object<string, string>} [options.additionalDatabaseUrls]\n */\nconst { createHealthCheckWorkerClient } = require('./healthCheckUtils')\nconst { DEFAULT_HEALTH_CONFIG } = require('./healthCheckClient')\n\n/** Latency above this (ms) triggers a warning log on the worker. 2000 = 2 seconds. */\nconst LATENCY_WARNING_MS = 2000\n\nfunction logLatencyWarnings(prefixLogs, result) {\n const resources = result?.resources || {}\n for (const [env, r] of Object.entries(resources)) {\n const connectMs = r?.connectMs\n if (typeof connectMs === 'number' && connectMs > LATENCY_WARNING_MS) {\n console.warn(\n `${prefixLogs} DB connect latency high: ${env} connectMs=${connectMs}ms (threshold ${LATENCY_WARNING_MS}ms)`\n )\n }\n }\n}\n\nexport function createHealthCheckWorker(options) {\n const {\n refreshIntervalMs = DEFAULT_HEALTH_CONFIG.checkIntervalMs,\n ...workerClientOptions\n } = options\n\n const appName =\n workerClientOptions.appName || process.env.BUILD_APP_NAME || 'unknown-app'\n const dynoId = process.env.HOSTNAME || 'unknown-dyno'\n const processType =\n process.env.BUILD_DYNO_PROCESS_TYPE || 'health-check-worker'\n const logValues = process.env.HEALTH_LOG_VALUES === 'true'\n const prefixLogs = `[${processType}] [${appName}] [${dynoId}] [HealthCheck]`\n\n const healthCheckClient = createHealthCheckWorkerClient(workerClientOptions)\n\n return async function runHealthCheckWorker() {\n console.log(`${prefixLogs} Starting health check worker...`)\n console.log(`${prefixLogs} Refresh interval: ${refreshIntervalMs}ms`)\n\n try {\n const result = await healthCheckClient.refreshCache()\n logLatencyWarnings(prefixLogs, result)\n if (logValues) {\n console.log(`${prefixLogs} Initial health check completed`)\n }\n } catch (err) {\n console.error(`${prefixLogs} Initial health check failed:`, err)\n }\n\n const interval = setInterval(async () => {\n try {\n const result = await healthCheckClient.refreshCache()\n logLatencyWarnings(prefixLogs, result)\n if (logValues) {\n console.log(\n `${prefixLogs} Health check refreshed at ${result.lastCheckAt}, status: ${result.status}`\n )\n }\n } catch (err) {\n console.error(`${prefixLogs} Health check refresh failed:`, err)\n }\n }, refreshIntervalMs)\n\n process.on('SIGTERM', () => {\n console.log(`${prefixLogs} Received SIGTERM, shutting down...`)\n clearInterval(interval)\n healthCheckClient.cleanup().finally(() => {\n process.exit(0)\n })\n })\n\n process.on('SIGINT', () => {\n console.log(`${prefixLogs} Received SIGINT, shutting down...`)\n clearInterval(interval)\n healthCheckClient.cleanup().finally(() => {\n process.exit(0)\n })\n })\n }\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.172",
3
+ "version": "0.1.173",
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",
@@ -28,7 +28,7 @@ const DEFAULT_HEALTH_CONFIG = {
28
28
  staleThresholdMs: 180_000,
29
29
  checkTimeoutMs: 15_000,
30
30
  maxDbConnectLatencyMs:
31
- readNumberEnv('HEALTH_DB_MAX_CONNECT_LATENCY_MS') ?? 1000,
31
+ readNumberEnv('HEALTH_DB_MAX_CONNECT_LATENCY_MS') ?? 30_000,
32
32
  }
33
33
 
34
34
  const SENSITIVE_PATTERNS = [
@@ -10,6 +10,21 @@
10
10
  const { createHealthCheckWorkerClient } = require('./healthCheckUtils')
11
11
  const { DEFAULT_HEALTH_CONFIG } = require('./healthCheckClient')
12
12
 
13
+ /** Latency above this (ms) triggers a warning log on the worker. 2000 = 2 seconds. */
14
+ const LATENCY_WARNING_MS = 2000
15
+
16
+ function logLatencyWarnings(prefixLogs, result) {
17
+ const resources = result?.resources || {}
18
+ for (const [env, r] of Object.entries(resources)) {
19
+ const connectMs = r?.connectMs
20
+ if (typeof connectMs === 'number' && connectMs > LATENCY_WARNING_MS) {
21
+ console.warn(
22
+ `${prefixLogs} DB connect latency high: ${env} connectMs=${connectMs}ms (threshold ${LATENCY_WARNING_MS}ms)`
23
+ )
24
+ }
25
+ }
26
+ }
27
+
13
28
  export function createHealthCheckWorker(options) {
14
29
  const {
15
30
  refreshIntervalMs = DEFAULT_HEALTH_CONFIG.checkIntervalMs,
@@ -31,7 +46,8 @@ export function createHealthCheckWorker(options) {
31
46
  console.log(`${prefixLogs} Refresh interval: ${refreshIntervalMs}ms`)
32
47
 
33
48
  try {
34
- await healthCheckClient.refreshCache()
49
+ const result = await healthCheckClient.refreshCache()
50
+ logLatencyWarnings(prefixLogs, result)
35
51
  if (logValues) {
36
52
  console.log(`${prefixLogs} Initial health check completed`)
37
53
  }
@@ -42,6 +58,7 @@ export function createHealthCheckWorker(options) {
42
58
  const interval = setInterval(async () => {
43
59
  try {
44
60
  const result = await healthCheckClient.refreshCache()
61
+ logLatencyWarnings(prefixLogs, result)
45
62
  if (logValues) {
46
63
  console.log(
47
64
  `${prefixLogs} Health check refreshed at ${result.lastCheckAt}, status: ${result.status}`