@adalo/metrics 0.1.150 → 0.1.151
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-health.md +14 -21
- package/README.md +4 -4
- package/lib/health/databaseChecker.d.ts +1 -3
- package/lib/health/databaseChecker.d.ts.map +1 -1
- package/lib/health/databaseChecker.js +27 -33
- package/lib/health/databaseChecker.js.map +1 -1
- package/lib/health/healthCheckClient.d.ts +8 -1
- package/lib/health/healthCheckClient.d.ts.map +1 -1
- package/lib/health/healthCheckClient.js +5 -3
- package/lib/health/healthCheckClient.js.map +1 -1
- package/package.json +1 -1
- package/src/health/databaseChecker.js +27 -29
- package/src/health/healthCheckClient.js +5 -3
package/README-health.md
CHANGED
|
@@ -137,18 +137,14 @@ app.get('/hc', healthCheckClient.healthHandler())
|
|
|
137
137
|
### Healthy Response
|
|
138
138
|
```json
|
|
139
139
|
{
|
|
140
|
-
"status": "
|
|
141
|
-
"
|
|
142
|
-
"
|
|
143
|
-
"
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
"status": "healthy",
|
|
149
|
-
"latencyMs": 1
|
|
150
|
-
}
|
|
151
|
-
}
|
|
140
|
+
"status": "ok",
|
|
141
|
+
"lastCheckAt": 1738143477926,
|
|
142
|
+
"resources": {
|
|
143
|
+
"DATABASE_URL": { "status": "ok", "connectMs": 9.2 },
|
|
144
|
+
"REDIS_URL": { "status": "ok" }
|
|
145
|
+
},
|
|
146
|
+
"isStale": false,
|
|
147
|
+
"config": { "checkIntervalMs": 30000, "staleThresholdMs": 180000, "checkTimeoutMs": 15000 }
|
|
152
148
|
}
|
|
153
149
|
```
|
|
154
150
|
|
|
@@ -170,16 +166,13 @@ app.get('/hc', healthCheckClient.healthHandler())
|
|
|
170
166
|
### Unhealthy Response (Database Issue)
|
|
171
167
|
```json
|
|
172
168
|
{
|
|
173
|
-
"status": "
|
|
174
|
-
"
|
|
175
|
-
"
|
|
176
|
-
"
|
|
177
|
-
"status": "unhealthy",
|
|
178
|
-
"error": "connection timeout",
|
|
179
|
-
"latencyMs": 5000
|
|
180
|
-
}
|
|
169
|
+
"status": "error",
|
|
170
|
+
"lastCheckAt": 1738143477926,
|
|
171
|
+
"resources": {
|
|
172
|
+
"DATABASE_URL": { "status": "error", "error": "connection timeout", "connectMs": 5000 }
|
|
181
173
|
},
|
|
182
|
-
"
|
|
174
|
+
"isStale": false,
|
|
175
|
+
"config": { "checkIntervalMs": 30000, "staleThresholdMs": 180000, "checkTimeoutMs": 15000 }
|
|
183
176
|
}
|
|
184
177
|
```
|
|
185
178
|
|
package/README.md
CHANGED
|
@@ -81,12 +81,12 @@ Response format (BetterStack compatible):
|
|
|
81
81
|
"database": {
|
|
82
82
|
"status": "healthy",
|
|
83
83
|
"clusters": {
|
|
84
|
-
"main": { "status": "healthy", "
|
|
85
|
-
"cluster_1": { "status": "healthy", "
|
|
86
|
-
"cluster_2": { "status": "healthy", "
|
|
84
|
+
"main": { "status": "healthy", "connectMs": 5.2 },
|
|
85
|
+
"cluster_1": { "status": "healthy", "connectMs": 8.1 },
|
|
86
|
+
"cluster_2": { "status": "healthy", "connectMs": 6.4 }
|
|
87
87
|
}
|
|
88
88
|
},
|
|
89
|
-
"redis": { "status": "healthy"
|
|
89
|
+
"redis": { "status": "healthy" }
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
```
|
|
@@ -28,12 +28,10 @@ export function createDatabasePool(env: string, url: string, connectionTimeoutMs
|
|
|
28
28
|
/**
|
|
29
29
|
* @param {any} pool
|
|
30
30
|
* @param {string} type
|
|
31
|
-
* @returns {Promise<{ connectMs: number
|
|
31
|
+
* @returns {Promise<{ connectMs: number }>}
|
|
32
32
|
*/
|
|
33
33
|
export function runHealthCheck(pool: any, type: string): Promise<{
|
|
34
34
|
connectMs: number;
|
|
35
|
-
queryMs: number;
|
|
36
|
-
totalMs: number;
|
|
37
35
|
}>;
|
|
38
36
|
/**
|
|
39
37
|
* @param {any} pool
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"databaseChecker.d.ts","sourceRoot":"","sources":["../../src/health/databaseChecker.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"databaseChecker.d.ts","sourceRoot":"","sources":["../../src/health/databaseChecker.js"],"names":[],"mappings":"AAqBA;;;GAGG;AACH,qCAHW,MAAM,GACJ,MAAM,CAYlB;AAED;;;;GAIG;AACH,wCAJW,MAAM,8BAEJ;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAiBnG;AAED;;;;;GAKG;AACH,wCALW,MAAM,OACN,MAAM,uBACN,MAAM;UACI,GAAG;UAAQ,MAAM;EA8BrC;AAED;;;;GAIG;AACH,qCAJW,GAAG,QACH,MAAM;eACiB,MAAM;GA8DvC;AAED;;;GAGG;AACH,gCAHW,GAAG,GACD,QAAQ,IAAI,CAAC,CAQzB;AAzKD,0CAAmC;AACnC,oCAA6B"}
|
|
@@ -8,10 +8,18 @@ const DB_TYPE_POSTGRES = 'postgres';
|
|
|
8
8
|
const DB_TYPE_MYSQL = 'mysql';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
+
* @returns {bigint}
|
|
12
|
+
*/
|
|
13
|
+
function nowNs() {
|
|
14
|
+
return process.hrtime.bigint();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {bigint} deltaNs
|
|
11
19
|
* @returns {number}
|
|
12
20
|
*/
|
|
13
|
-
function
|
|
14
|
-
return Number(
|
|
21
|
+
function nsToMs(deltaNs) {
|
|
22
|
+
return Number(deltaNs) / 1_000_000;
|
|
15
23
|
}
|
|
16
24
|
|
|
17
25
|
/**
|
|
@@ -95,76 +103,62 @@ function createDatabasePool(env, url, connectionTimeoutMs) {
|
|
|
95
103
|
/**
|
|
96
104
|
* @param {any} pool
|
|
97
105
|
* @param {string} type
|
|
98
|
-
* @returns {Promise<{ connectMs: number
|
|
106
|
+
* @returns {Promise<{ connectMs: number }>}
|
|
99
107
|
*/
|
|
100
108
|
async function runHealthCheck(pool, type) {
|
|
101
109
|
if (type === DB_TYPE_MYSQL) {
|
|
102
|
-
|
|
103
|
-
let connectMs = 0;
|
|
104
|
-
let queryMs = 0;
|
|
110
|
+
let connectMs = 0.0;
|
|
105
111
|
|
|
106
112
|
/** @type {any} */
|
|
107
113
|
let conn;
|
|
108
114
|
try {
|
|
109
|
-
const startConnect =
|
|
115
|
+
const startConnect = nowNs();
|
|
110
116
|
conn = await pool.getConnection();
|
|
111
|
-
connectMs =
|
|
112
|
-
const startQuery = nowMs();
|
|
117
|
+
connectMs = nsToMs(nowNs() - startConnect);
|
|
113
118
|
await conn.query('SELECT 1');
|
|
114
|
-
queryMs = nowMs() - startQuery;
|
|
115
119
|
return {
|
|
116
|
-
connectMs
|
|
117
|
-
queryMs,
|
|
118
|
-
totalMs: nowMs() - startTotal
|
|
120
|
+
connectMs
|
|
119
121
|
};
|
|
120
122
|
} catch (err) {
|
|
121
|
-
const totalMs = nowMs() - startTotal;
|
|
122
123
|
err.healthCheckTimings = {
|
|
123
|
-
connectMs
|
|
124
|
-
queryMs,
|
|
125
|
-
totalMs
|
|
124
|
+
connectMs
|
|
126
125
|
};
|
|
127
126
|
throw err;
|
|
128
127
|
} finally {
|
|
129
128
|
if (conn) {
|
|
130
129
|
try {
|
|
131
|
-
|
|
130
|
+
// Destroy the connection so next check measures a real connect.
|
|
131
|
+
// mysql2 pooled connections support destroy() to remove it from the pool.
|
|
132
|
+
if (typeof conn.destroy === 'function') conn.destroy();else if (typeof conn.end === 'function') await conn.end();else if (typeof conn.release === 'function') conn.release();
|
|
132
133
|
} catch {
|
|
133
134
|
// ignore
|
|
134
135
|
}
|
|
135
136
|
}
|
|
136
137
|
}
|
|
137
138
|
}
|
|
138
|
-
|
|
139
|
-
let connectMs = 0;
|
|
140
|
-
let queryMs = 0;
|
|
139
|
+
let connectMs = 0.0;
|
|
141
140
|
|
|
142
141
|
/** @type {import('pg').PoolClient | null} */
|
|
143
142
|
let client = null;
|
|
144
143
|
try {
|
|
145
|
-
const startConnect =
|
|
144
|
+
const startConnect = nowNs();
|
|
146
145
|
client = await pool.connect();
|
|
147
|
-
connectMs =
|
|
148
|
-
const startQuery = nowMs();
|
|
146
|
+
connectMs = nsToMs(nowNs() - startConnect);
|
|
149
147
|
await client.query('SELECT 1');
|
|
150
|
-
queryMs = nowMs() - startQuery;
|
|
151
148
|
return {
|
|
152
|
-
connectMs
|
|
153
|
-
queryMs,
|
|
154
|
-
totalMs: nowMs() - startTotal
|
|
149
|
+
connectMs
|
|
155
150
|
};
|
|
156
151
|
} catch (err) {
|
|
157
|
-
const totalMs = nowMs() - startTotal;
|
|
158
152
|
err.healthCheckTimings = {
|
|
159
|
-
connectMs
|
|
160
|
-
queryMs,
|
|
161
|
-
totalMs
|
|
153
|
+
connectMs
|
|
162
154
|
};
|
|
163
155
|
throw err;
|
|
164
156
|
} finally {
|
|
165
157
|
if (client) {
|
|
166
158
|
try {
|
|
167
|
-
client.
|
|
159
|
+
// Force the pool to drop the client so next check measures a real connect.
|
|
160
|
+
// In node-postgres, passing a truthy value removes the client from the pool.
|
|
161
|
+
client.release(true);
|
|
168
162
|
} catch {
|
|
169
163
|
// ignore
|
|
170
164
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"databaseChecker.js","names":["Pool","require","mysql","DB_TYPE_POSTGRES","DB_TYPE_MYSQL","
|
|
1
|
+
{"version":3,"file":"databaseChecker.js","names":["Pool","require","mysql","DB_TYPE_POSTGRES","DB_TYPE_MYSQL","nowNs","process","hrtime","bigint","nsToMs","deltaNs","Number","getDatabaseType","url","lower","toLowerCase","startsWith","parseConnectionUrl","type","parsed","URL","defaultPort","defaultDb","host","hostname","port","parseInt","String","user","username","password","database","pathname","replace","createDatabasePool","env","connectionTimeoutMs","config","Error","pool","createPool","connectionLimit","connectTimeout","waitForConnections","connectionString","max","idleTimeoutMillis","connectionTimeoutMillis","runHealthCheck","connectMs","conn","startConnect","getConnection","query","err","healthCheckTimings","destroy","end","release","client","connect","closePool","module","exports"],"sources":["../../src/health/databaseChecker.js"],"sourcesContent":["const { Pool } = require('pg')\nconst mysql = require('mysql2/promise')\n\nconst DB_TYPE_POSTGRES = 'postgres'\nconst DB_TYPE_MYSQL = 'mysql'\n\n/**\n * @returns {bigint}\n */\nfunction nowNs() {\n return process.hrtime.bigint()\n}\n\n/**\n * @param {bigint} deltaNs\n * @returns {number}\n */\nfunction nsToMs(deltaNs) {\n return Number(deltaNs) / 1_000_000\n}\n\n/**\n * @param {string} url\n * @returns {string} postgres | mysql\n */\nfunction getDatabaseType(url) {\n if (!url || typeof url !== 'string') return DB_TYPE_POSTGRES\n const lower = url.toLowerCase()\n if (lower.startsWith('mysql://') || lower.startsWith('mysql2://')) {\n return DB_TYPE_MYSQL\n }\n if (lower.startsWith('mariadb://')) {\n return DB_TYPE_MYSQL\n }\n return DB_TYPE_POSTGRES\n}\n\n/**\n * @param {string} url\n * @param {string} [type]\n * @returns {{ host: string, port: number, user: string, password: string, database: string } | null}\n */\nfunction parseConnectionUrl(url, type) {\n try {\n const parsed = new URL(url)\n const defaultPort = type === DB_TYPE_MYSQL ? 3306 : 5432\n const defaultDb = type === DB_TYPE_MYSQL ? 'mysql' : 'postgres'\n return {\n host: parsed.hostname || 'localhost',\n port: parseInt(parsed.port || String(defaultPort), 10),\n user: parsed.username || '',\n password: parsed.password || '',\n database: (parsed.pathname || '/').replace(/^\\//, '') || defaultDb,\n }\n } catch {\n return null\n }\n}\n\n/**\n * @param {string} env\n * @param {string} url\n * @param {number} connectionTimeoutMs\n * @returns {{ pool: any, type: string }}\n */\nfunction createDatabasePool(env, url, connectionTimeoutMs) {\n const type = getDatabaseType(url)\n\n if (type === DB_TYPE_MYSQL) {\n const config = parseConnectionUrl(url, DB_TYPE_MYSQL)\n if (!config) {\n throw new Error(`Invalid MySQL URL for ${env}`)\n }\n const pool = mysql.createPool({\n host: config.host,\n port: config.port,\n user: config.user,\n password: config.password,\n database: config.database,\n connectionLimit: 1,\n connectTimeout: connectionTimeoutMs,\n waitForConnections: false,\n })\n return { pool, type: DB_TYPE_MYSQL }\n }\n\n const pool = new Pool({\n connectionString: url,\n max: 1,\n idleTimeoutMillis: 30000,\n connectionTimeoutMillis: connectionTimeoutMs,\n })\n return { pool, type: DB_TYPE_POSTGRES }\n}\n\n/**\n * @param {any} pool\n * @param {string} type\n * @returns {Promise<{ connectMs: number }>}\n */\nasync function runHealthCheck(pool, type) {\n if (type === DB_TYPE_MYSQL) {\n let connectMs = 0.0\n\n /** @type {any} */\n let conn\n try {\n const startConnect = nowNs()\n conn = await pool.getConnection()\n connectMs = nsToMs(nowNs() - startConnect)\n\n await conn.query('SELECT 1')\n return { connectMs }\n } catch (err) {\n err.healthCheckTimings = {\n connectMs,\n }\n throw err\n } finally {\n if (conn) {\n try {\n // Destroy the connection so next check measures a real connect.\n // mysql2 pooled connections support destroy() to remove it from the pool.\n if (typeof conn.destroy === 'function') conn.destroy()\n else if (typeof conn.end === 'function') await conn.end()\n else if (typeof conn.release === 'function') conn.release()\n } catch {\n // ignore\n }\n }\n }\n }\n\n let connectMs = 0.0\n\n /** @type {import('pg').PoolClient | null} */\n let client = null\n try {\n const startConnect = nowNs()\n client = await pool.connect()\n connectMs = nsToMs(nowNs() - startConnect)\n\n await client.query('SELECT 1')\n return { connectMs }\n } catch (err) {\n err.healthCheckTimings = {\n connectMs,\n }\n throw err\n } finally {\n if (client) {\n try {\n // Force the pool to drop the client so next check measures a real connect.\n // In node-postgres, passing a truthy value removes the client from the pool.\n client.release(true)\n } catch {\n // ignore\n }\n }\n }\n}\n\n/**\n * @param {any} pool\n * @returns {Promise<void>}\n */\nasync function closePool(pool) {\n try {\n await pool.end()\n } catch {\n // ignore\n }\n}\n\nmodule.exports = {\n getDatabaseType,\n parseConnectionUrl,\n createDatabasePool,\n runHealthCheck,\n closePool,\n DB_TYPE_POSTGRES,\n DB_TYPE_MYSQL,\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -53,7 +53,14 @@ export class HealthCheckClient {
|
|
|
53
53
|
pool: any;
|
|
54
54
|
type: string;
|
|
55
55
|
} | undefined;
|
|
56
|
-
_checkDatabase(resource: any): Promise<
|
|
56
|
+
_checkDatabase(resource: any): Promise<{
|
|
57
|
+
status: string;
|
|
58
|
+
connectMs: number;
|
|
59
|
+
} | {
|
|
60
|
+
connectMs?: any;
|
|
61
|
+
status: string;
|
|
62
|
+
error: string;
|
|
63
|
+
}>;
|
|
57
64
|
_checkRedis(resource: any): Promise<{
|
|
58
65
|
status: string;
|
|
59
66
|
error?: undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"healthCheckClient.d.ts","sourceRoot":"","sources":["../../src/health/healthCheckClient.js"],"names":[],"mappings":"6BA6Ea;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,GAAG,CAAA;CAAE;AAD1E;;GAEG;AACH;IACE;;;;;;OAMG;IACH;QALqC,SAAS,EAAnC,cAAc,EAAE;QACC,MAAM;QACN,OAAO;QACP,QAAQ;OA2BnC;IAxBC;;;;;;;;;;;;MAAmE;IACnE,gBACgE;IAEhE,mBAAmD;IAEnD,qBAA2B;IAC3B,uDAAuD;IACvD;cAD+B,GAAG;cAAQ,MAAM;OACjB;IAE/B,+BAA+B;IAC/B,YADW,cAAc,EAAE,CACc;IAIvC,qCAAuD;IAGzD,yBAKE;IAGJ,4BAEC;IAED,+BAGC;IAED;cA5BiC,GAAG;cAAQ,MAAM;kBAsCjD;IAED
|
|
1
|
+
{"version":3,"file":"healthCheckClient.d.ts","sourceRoot":"","sources":["../../src/health/healthCheckClient.js"],"names":[],"mappings":"6BA6Ea;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,GAAG,CAAA;CAAE;AAD1E;;GAEG;AACH;IACE;;;;;;OAMG;IACH;QALqC,SAAS,EAAnC,cAAc,EAAE;QACC,MAAM;QACN,OAAO;QACP,QAAQ;OA2BnC;IAxBC;;;;;;;;;;;;MAAmE;IACnE,gBACgE;IAEhE,mBAAmD;IAEnD,qBAA2B;IAC3B,uDAAuD;IACvD;cAD+B,GAAG;cAAQ,MAAM;OACjB;IAE/B,+BAA+B;IAC/B,YADW,cAAc,EAAE,CACc;IAIvC,qCAAuD;IAGzD,yBAKE;IAGJ,4BAEC;IAED,+BAGC;IAED;cA5BiC,GAAG;cAAQ,MAAM;kBAsCjD;IAED;;;;;;;OAoCC;IAED;;;;;;OA4BC;IAED;;;;;;;;;;;;;;;;;;OA8BC;IAED,kDAeC;IAED,mCAgBC;IAED,gCAiBC;IAED;;;;;;;;;;;;;;;;;;OAIC;IAED,mBAEC;IAED,uDAiCC;IAED,yBASC;CACF;AAhUD,2HAA2H;AAC3H,oCADW;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,qBAAqB,EAAE,MAAM,CAAA;CAAE,CAOtH"}
|
|
@@ -144,19 +144,21 @@ class HealthCheckClient {
|
|
|
144
144
|
return {
|
|
145
145
|
status: 'error',
|
|
146
146
|
error: `DB connect latency ${timings.connectMs}ms exceeds ${maxConnect}ms`,
|
|
147
|
-
|
|
147
|
+
connectMs: timings.connectMs
|
|
148
148
|
};
|
|
149
149
|
}
|
|
150
150
|
return {
|
|
151
151
|
status: 'ok',
|
|
152
|
-
|
|
152
|
+
connectMs: timings.connectMs
|
|
153
153
|
};
|
|
154
154
|
} catch (err) {
|
|
155
155
|
const timings = err?.healthCheckTimings;
|
|
156
156
|
return {
|
|
157
157
|
status: 'error',
|
|
158
158
|
error: maskSensitiveData(err.message),
|
|
159
|
-
...(timings && typeof timings === 'object' ?
|
|
159
|
+
...(timings && typeof timings === 'object' ? {
|
|
160
|
+
connectMs: timings.connectMs
|
|
161
|
+
} : {})
|
|
160
162
|
};
|
|
161
163
|
}
|
|
162
164
|
}
|
|
@@ -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","healthHandler","req","res","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 ...timings,\n }\n }\n\n return { status: 'ok', ...timings }\n } catch (err) {\n const timings = err?.healthCheckTimings\n return {\n status: 'error',\n error: maskSensitiveData(err.message),\n ...(timings && typeof timings === 'object' ? timings : {}),\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 healthHandler() {\n return async (req, res) => {\n try {\n const result = await this.getCachedResult()\n\n if (!result) {\n res.status(503).json({\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 return\n }\n\n const statusCode = result.status === 'ok' ? 200 : 503\n res.status(statusCode).json(result)\n } catch (err) {\n console.error(`${this.prefixLogs} Health check failed:`, err)\n res.status(503).json({\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\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","healthHandler","req","res","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 healthHandler() {\n return async (req, res) => {\n try {\n const result = await this.getCachedResult()\n\n if (!result) {\n res.status(503).json({\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 return\n }\n\n const statusCode = result.status === 'ok' ? 200 : 503\n res.status(statusCode).json(result)\n } catch (err) {\n console.error(`${this.prefixLogs} Health check failed:`, err)\n res.status(503).json({\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\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":[]}
|
package/package.json
CHANGED
|
@@ -5,10 +5,18 @@ const DB_TYPE_POSTGRES = 'postgres'
|
|
|
5
5
|
const DB_TYPE_MYSQL = 'mysql'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
+
* @returns {bigint}
|
|
9
|
+
*/
|
|
10
|
+
function nowNs() {
|
|
11
|
+
return process.hrtime.bigint()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {bigint} deltaNs
|
|
8
16
|
* @returns {number}
|
|
9
17
|
*/
|
|
10
|
-
function
|
|
11
|
-
return Number(
|
|
18
|
+
function nsToMs(deltaNs) {
|
|
19
|
+
return Number(deltaNs) / 1_000_000
|
|
12
20
|
}
|
|
13
21
|
|
|
14
22
|
/**
|
|
@@ -88,38 +96,34 @@ function createDatabasePool(env, url, connectionTimeoutMs) {
|
|
|
88
96
|
/**
|
|
89
97
|
* @param {any} pool
|
|
90
98
|
* @param {string} type
|
|
91
|
-
* @returns {Promise<{ connectMs: number
|
|
99
|
+
* @returns {Promise<{ connectMs: number }>}
|
|
92
100
|
*/
|
|
93
101
|
async function runHealthCheck(pool, type) {
|
|
94
102
|
if (type === DB_TYPE_MYSQL) {
|
|
95
|
-
|
|
96
|
-
let connectMs = 0
|
|
97
|
-
let queryMs = 0
|
|
103
|
+
let connectMs = 0.0
|
|
98
104
|
|
|
99
105
|
/** @type {any} */
|
|
100
106
|
let conn
|
|
101
107
|
try {
|
|
102
|
-
const startConnect =
|
|
108
|
+
const startConnect = nowNs()
|
|
103
109
|
conn = await pool.getConnection()
|
|
104
|
-
connectMs =
|
|
110
|
+
connectMs = nsToMs(nowNs() - startConnect)
|
|
105
111
|
|
|
106
|
-
const startQuery = nowMs()
|
|
107
112
|
await conn.query('SELECT 1')
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return { connectMs, queryMs, totalMs: nowMs() - startTotal }
|
|
113
|
+
return { connectMs }
|
|
111
114
|
} catch (err) {
|
|
112
|
-
const totalMs = nowMs() - startTotal
|
|
113
115
|
err.healthCheckTimings = {
|
|
114
116
|
connectMs,
|
|
115
|
-
queryMs,
|
|
116
|
-
totalMs,
|
|
117
117
|
}
|
|
118
118
|
throw err
|
|
119
119
|
} finally {
|
|
120
120
|
if (conn) {
|
|
121
121
|
try {
|
|
122
|
-
|
|
122
|
+
// Destroy the connection so next check measures a real connect.
|
|
123
|
+
// mysql2 pooled connections support destroy() to remove it from the pool.
|
|
124
|
+
if (typeof conn.destroy === 'function') conn.destroy()
|
|
125
|
+
else if (typeof conn.end === 'function') await conn.end()
|
|
126
|
+
else if (typeof conn.release === 'function') conn.release()
|
|
123
127
|
} catch {
|
|
124
128
|
// ignore
|
|
125
129
|
}
|
|
@@ -127,34 +131,28 @@ async function runHealthCheck(pool, type) {
|
|
|
127
131
|
}
|
|
128
132
|
}
|
|
129
133
|
|
|
130
|
-
|
|
131
|
-
let connectMs = 0
|
|
132
|
-
let queryMs = 0
|
|
134
|
+
let connectMs = 0.0
|
|
133
135
|
|
|
134
136
|
/** @type {import('pg').PoolClient | null} */
|
|
135
137
|
let client = null
|
|
136
138
|
try {
|
|
137
|
-
const startConnect =
|
|
139
|
+
const startConnect = nowNs()
|
|
138
140
|
client = await pool.connect()
|
|
139
|
-
connectMs =
|
|
141
|
+
connectMs = nsToMs(nowNs() - startConnect)
|
|
140
142
|
|
|
141
|
-
const startQuery = nowMs()
|
|
142
143
|
await client.query('SELECT 1')
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return { connectMs, queryMs, totalMs: nowMs() - startTotal }
|
|
144
|
+
return { connectMs }
|
|
146
145
|
} catch (err) {
|
|
147
|
-
const totalMs = nowMs() - startTotal
|
|
148
146
|
err.healthCheckTimings = {
|
|
149
147
|
connectMs,
|
|
150
|
-
queryMs,
|
|
151
|
-
totalMs,
|
|
152
148
|
}
|
|
153
149
|
throw err
|
|
154
150
|
} finally {
|
|
155
151
|
if (client) {
|
|
156
152
|
try {
|
|
157
|
-
client.
|
|
153
|
+
// Force the pool to drop the client so next check measures a real connect.
|
|
154
|
+
// In node-postgres, passing a truthy value removes the client from the pool.
|
|
155
|
+
client.release(true)
|
|
158
156
|
} catch {
|
|
159
157
|
// ignore
|
|
160
158
|
}
|
|
@@ -154,17 +154,19 @@ class HealthCheckClient {
|
|
|
154
154
|
return {
|
|
155
155
|
status: 'error',
|
|
156
156
|
error: `DB connect latency ${timings.connectMs}ms exceeds ${maxConnect}ms`,
|
|
157
|
-
|
|
157
|
+
connectMs: timings.connectMs,
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
return { status: 'ok',
|
|
161
|
+
return { status: 'ok', connectMs: timings.connectMs }
|
|
162
162
|
} catch (err) {
|
|
163
163
|
const timings = err?.healthCheckTimings
|
|
164
164
|
return {
|
|
165
165
|
status: 'error',
|
|
166
166
|
error: maskSensitiveData(err.message),
|
|
167
|
-
...(timings && typeof timings === 'object'
|
|
167
|
+
...(timings && typeof timings === 'object'
|
|
168
|
+
? { connectMs: timings.connectMs }
|
|
169
|
+
: {}),
|
|
168
170
|
}
|
|
169
171
|
}
|
|
170
172
|
}
|