From 8119d31a36938fedb098a96a2dd96c5814fd3b9b Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:25:00 +0000 Subject: [PATCH 1/2] feat: add health endpoint Checks DB connection & lock status. --- apps/server/src/database/queries/meta.ts | 52 ++++++++++++++++++++++++ apps/server/src/routes/index.ts | 18 ++++++++ 2 files changed, 70 insertions(+) diff --git a/apps/server/src/database/queries/meta.ts b/apps/server/src/database/queries/meta.ts index ef68245..d309b14 100644 --- a/apps/server/src/database/queries/meta.ts +++ b/apps/server/src/database/queries/meta.ts @@ -35,3 +35,55 @@ export async function setFeatureCompatibilityVersion(version: string) { const admin = mongoose.connection.db.admin(); await admin.command({ setFeatureCompatibilityVersion: version }); } + +export async function getDatabaseHealth(): Promise<{ + status: "UP" | "DOWN" | "READONLY"; + details?: { + version: string; + uptime: number; + writable: boolean; + }; + message?: string; +}> { + if (!mongoose.connection.readyState || !mongoose.connection.db) { + return { status: "DOWN", message: "Database not connected" }; + } + + try { + const mongoInfos = await getMongoInfos(); + + // Fetch server status to get uptime + const admin = mongoose.connection.db.admin(); + const serverStatus = await admin.command({ serverStatus: 1 }); + + // Check for fsyncLock using db.currentOp() + const currentOp = await admin.command({ currentOp: 1 }); + const isWriteLocked = currentOp.inprog.some((op: { desc: string, active: boolean }) => op.desc === 'fsyncLockWorker' && op.active); + + if (isWriteLocked) { + return { + status: "READONLY", + details: { + version: mongoInfos.version, + uptime: serverStatus.uptime || 0, + writable: false, + }, + message: "Database is read-only (fsyncLock enabled)", + }; + } + + // If no lock is detected, check if the DB is generally writable + return { + status: "UP", + details: { + version: mongoInfos.version, + uptime: serverStatus.uptime || 0, + writable: true, + }, + }; + + } catch (error) { + console.error("Error querying database health:", error); + return { status: "DOWN", message: "Error querying database health" }; + } +} diff --git a/apps/server/src/routes/index.ts b/apps/server/src/routes/index.ts index 5b580ad..64218e0 100644 --- a/apps/server/src/routes/index.ts +++ b/apps/server/src/routes/index.ts @@ -18,6 +18,7 @@ import { setUserPublicToken, storeInUser, } from "../database"; +import { getDatabaseHealth } from "../database/queries/meta"; import { logger } from "../tools/logger"; import { LoggedRequest, @@ -36,6 +37,23 @@ router.get("/", (_, res) => { res.status(200).send("Hello !"); }); +router.get("/health", async (_, res) => { + try { + const dbHealth = await getDatabaseHealth(); + + if (dbHealth.status === "DOWN") { + res.status(503).send({status: "DB connection error", db_health: dbHealth}); + } else if (dbHealth.status === "READONLY") { + res.status(503).send({status: "warning", db_health: dbHealth}); + } else { + res.status(200).send({status: "ok", db_health: dbHealth}); + } + } catch (e) { + logger.error(e); + res.status(500).send({ status: "error", message: "Unexpected error" }); + } +}); + router.post("/logout", async (_, res) => { res.clearCookie("token"); res.status(200).end(); From 87a1c8750e5758e48a2bba2ed2a4458f0e28703a Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:27:10 +0000 Subject: [PATCH 2/2] fix: simplify health status error message --- apps/server/src/routes/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/routes/index.ts b/apps/server/src/routes/index.ts index 64218e0..9452d83 100644 --- a/apps/server/src/routes/index.ts +++ b/apps/server/src/routes/index.ts @@ -42,7 +42,7 @@ router.get("/health", async (_, res) => { const dbHealth = await getDatabaseHealth(); if (dbHealth.status === "DOWN") { - res.status(503).send({status: "DB connection error", db_health: dbHealth}); + res.status(503).send({status: "error", db_health: dbHealth}); } else if (dbHealth.status === "READONLY") { res.status(503).send({status: "warning", db_health: dbHealth}); } else {