feat(telemetry): rollups, alerts, scheduler, health checks, admin server fns
- pure rollup accumulators with tests; daily upserts keyed date/metric/dim - alert checks with db-backed cooldown and push to admin players - in-process scheduler started from health probes - health endpoint checks pb + supertokens, stale-while-revalidate, always 200 - sse connection counter moved to emitter - telemetry feature server fns: events/errors/groups/rollups/alerts/status
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
export interface HealthChecks {
|
||||
pocketbase: "ok" | "down";
|
||||
supertokens: "ok" | "down";
|
||||
}
|
||||
|
||||
export interface HealthSnapshot {
|
||||
status: "ok" | "degraded";
|
||||
checks: HealthChecks;
|
||||
checkedAt: string;
|
||||
}
|
||||
|
||||
const CHECK_TTL_MS = 10_000;
|
||||
const CHECK_TIMEOUT_MS = 2_000;
|
||||
|
||||
let cached: HealthSnapshot | null = null;
|
||||
let refreshing: Promise<HealthSnapshot> | null = null;
|
||||
|
||||
const ping = async (url: string): Promise<boolean> => {
|
||||
try {
|
||||
const response = await fetch(url, { signal: AbortSignal.timeout(CHECK_TIMEOUT_MS) });
|
||||
return response.ok;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const runChecks = async (): Promise<HealthSnapshot> => {
|
||||
const [pocketbase, supertokens] = await Promise.all([
|
||||
ping(`${process.env.POCKETBASE_URL}/api/health`),
|
||||
ping(`${process.env.SUPERTOKENS_URI}/hello`),
|
||||
]);
|
||||
|
||||
cached = {
|
||||
status: pocketbase && supertokens ? "ok" : "degraded",
|
||||
checks: {
|
||||
pocketbase: pocketbase ? "ok" : "down",
|
||||
supertokens: supertokens ? "ok" : "down",
|
||||
},
|
||||
checkedAt: new Date().toISOString(),
|
||||
};
|
||||
return cached;
|
||||
};
|
||||
|
||||
// Stale-while-revalidate: k8s probes get the cached snapshot instantly while a
|
||||
// refresh runs in the background; only the very first request awaits a check.
|
||||
export const getHealthSnapshot = async (): Promise<HealthSnapshot> => {
|
||||
if (cached && Date.now() - Date.parse(cached.checkedAt) < CHECK_TTL_MS) {
|
||||
return cached;
|
||||
}
|
||||
if (!refreshing) {
|
||||
refreshing = runChecks().finally(() => {
|
||||
refreshing = null;
|
||||
});
|
||||
}
|
||||
return cached ?? refreshing;
|
||||
};
|
||||
Reference in New Issue
Block a user