feat: add healthchecks (#1187)
This commit is contained in:
91
apps/nextjs/src/app/api/health/live/route.ts
Normal file
91
apps/nextjs/src/app/api/health/live/route.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { performance } from "perf_hooks";
|
||||
|
||||
import { db } from "@homarr/db";
|
||||
import { logger } from "@homarr/log";
|
||||
import { handshakeAsync } from "@homarr/redis";
|
||||
|
||||
export async function GET() {
|
||||
const timeBeforeHealthCheck = performance.now();
|
||||
const response = await executeAndAggregateAllHealthChecksAsync();
|
||||
logger.info(`Completed healthcheck after ${performance.now() - timeBeforeHealthCheck}ms`);
|
||||
|
||||
if (response.status === "healthy") {
|
||||
return new Response(JSON.stringify(response), {
|
||||
status: 200,
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify(response), {
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
|
||||
const executeAndAggregateAllHealthChecksAsync = async (): Promise<{
|
||||
healthChecks: Record<string, object>;
|
||||
status: "healthy" | "unhealthy";
|
||||
}> => {
|
||||
const healthChecks = [
|
||||
executeHealthCheckSafelyAsync("database", async () => {
|
||||
// sqlite driver does not support raw query execution. this is for a heartbeat check only - it doesn't matter if data is returned or not
|
||||
await db.query.serverSettings.findFirst();
|
||||
return {};
|
||||
}),
|
||||
executeHealthCheckSafelyAsync("redis", async () => {
|
||||
await handshakeAsync();
|
||||
return {};
|
||||
}),
|
||||
];
|
||||
|
||||
const healthCheckResults = await Promise.all(healthChecks);
|
||||
const anyUnhealthy = healthCheckResults.some((healthCheck) => healthCheck.status === "unhealthy");
|
||||
|
||||
const healthCheckValues = healthCheckResults.reduce(
|
||||
(acc, healthCheck) => {
|
||||
acc[healthCheck.name] = {
|
||||
status: healthCheck.status,
|
||||
...healthCheck.values,
|
||||
};
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, object>,
|
||||
);
|
||||
|
||||
return {
|
||||
status: anyUnhealthy ? "unhealthy" : "healthy",
|
||||
healthChecks: healthCheckValues,
|
||||
};
|
||||
};
|
||||
|
||||
const executeHealthCheckSafelyAsync = async (
|
||||
name: string,
|
||||
callback: () => Promise<object>,
|
||||
): Promise<HealthCheckResult> => {
|
||||
try {
|
||||
const currentTimeBeforeCallback = performance.now();
|
||||
const values = await callback();
|
||||
return {
|
||||
name,
|
||||
status: "healthy",
|
||||
values: {
|
||||
...values,
|
||||
latency: performance.now() - currentTimeBeforeCallback,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
logger.error(`Healthcheck '${name}' has failed: ${error}`);
|
||||
return {
|
||||
status: "unhealthy",
|
||||
values: {
|
||||
error,
|
||||
},
|
||||
name,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
interface HealthCheckResult {
|
||||
status: "healthy" | "unhealthy";
|
||||
name: string;
|
||||
values: object;
|
||||
}
|
||||
5
apps/nextjs/src/app/api/health/ready/route.ts
Normal file
5
apps/nextjs/src/app/api/health/ready/route.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export function GET() {
|
||||
return new Response(undefined, {
|
||||
status: 200,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user