Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/openstatus. ๐ซ Status page with uptime monitoring & API monitoring as code ๐ซ openstatus.dev
bun drizzle-orm monitoring monitoring-as-code nextjs observability on-call open-source shadcn-ui status-page statuspage synthetic-monitoring tinybird turso uptime uptime-checker uptime-monitor
Something went wrong. Try again.
5.9 kB ยท 202 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203import { getLogger } from "@logtape/logtape";import { monitorRegions } from "@openstatus/db/src/schema/constants";import { monitorStatusSchema } from "@openstatus/db/src/schema/monitors/validation";import { Hono } from "hono";import { z } from "zod";
import { env } from "../env";import type { Env } from "../index";import { checkerAudit } from "../utils/audit-log";import { triggerNotifications } from "./alerting";import { enqueueOutbox } from "./outbox";import { updateStatusPrivate } from "./private-location";import { EVENT_TYPE, applyStatusTransition, isStaleCheck } from "./transition";
export const checkerRoute = new Hono<Env>();
checkerRoute.post("/updateStatusPrivate", updateStatusPrivate);
const payloadSchema = z.object({ monitorId: z.string(), message: z.string().optional(), statusCode: z.number().optional(), region: z.enum(monitorRegions), cronTimestamp: z.number(), status: monitorStatusSchema, latency: z.number().optional(),});
type Payload = z.infer<typeof payloadSchema>;
const logger = getLogger(["workflow"]);
async function publishStatusAudit(payload: Payload): Promise<void> { const { monitorId, region, statusCode, cronTimestamp, latency } = payload; const id = `monitor:${monitorId}`; const targets = [{ id: monitorId, type: "monitor" as const }]; const metadata = { region, statusCode: statusCode ?? -1, cronTimestamp, latency, };
// Best-effort, like publishIncidentAudit: the transition batch has already // committed, and throwing here would make Cloud Tasks retry a transition that // has landed. The retry short-circuits on the unchanged region status, so the // notification this request still owes would never be sent. try { switch (payload.status) { case "active": await checkerAudit.publishAuditLog({ id, action: "monitor.recovered", targets, metadata, }); break; case "degraded": await checkerAudit.publishAuditLog({ id, action: "monitor.degraded", targets, metadata, }); break; case "error": await checkerAudit.publishAuditLog({ id, action: "monitor.failed", targets, metadata: { ...metadata, message: payload.message }, }); break; } } catch (error) { logger.warn("Failed to publish status audit log", { monitor_id: payload.monitorId, error_message: error instanceof Error ? error.message : String(error), }); }}
checkerRoute.post("/updateStatus", async (c) => { const config = env(); const auth = c.req.header("Authorization"); if (auth !== `Basic ${config.CRON_SECRET}`) { logger.error("Unauthorized"); return c.text("Unauthorized", 401); }
const event = c.get("event"); const result = payloadSchema.safeParse(await c.req.json());
if (!result.success) { return c.text("Unprocessable Entity", 422); }
const { monitorId, message, region, statusCode, cronTimestamp, status, latency, } = result.data; const monitorIdNumber = Number(monitorId);
logger.info("Updating monitor status", { monitor_id: monitorId, region, status, status_code: statusCode, cron_timestamp: cronTimestamp, latency_ms: latency, });
const statusUpdate: Record<string, unknown> = { status, message, region, status_code: statusCode, cron_timestamp: cronTimestamp, latency_ms: latency, monitorId: monitorIdNumber, }; if (event) event.status_update = statusUpdate;
if (isStaleCheck(cronTimestamp, config.STALE_CHECK_MS)) { statusUpdate.stale = true; return c.json({ success: true }, 200); }
const transition = await applyStatusTransition({ monitorId: monitorIdNumber, region, status, cronTimestamp, statusCode, message, latency, deadlineSeconds: Math.floor(config.OUTBOX_DEADLINE_MS / 1000), rolloutPct: config.OUTBOX_ROLLOUT_PCT, });
if (transition.kind === "unchanged") { statusUpdate.fast_path_skipped = true; return c.json({ success: true }, 200); }
if (transition.kind === "monitor-missing") { statusUpdate.monitor_missing = true; return c.json({ success: true }, 200); }
statusUpdate.affectedRegionsCount = transition.affectedRegions.length; statusUpdate.quorum_count = transition.quorumCount; statusUpdate.region_count = transition.regionCount; statusUpdate.transition_applied = transition.transitioned; statusUpdate.outbox_rows = transition.outboxRows.length;
await publishStatusAudit(result.data);
if (!transition.transitioned) { return c.text("Ok", 200); }
logger.info("Monitor status changed", { monitor_id: monitorIdNumber, status, });
let triggeredNotifications: { notificationId: number; provider: string }[] = [];
// Ownership is whatever the batch actually wrote, not a second copy of the // rollout formula: `pending` means the drainer owns it, `settled` with an // `inline` outcome means the inline sender does. if (transition.outboxRows.some((row) => row.deliveryStatus === "pending")) { enqueueOutbox(transition.outboxRows.map((row) => row.id)); triggeredNotifications = transition.outboxRows.map((row) => ({ notificationId: row.notificationId, provider: row.provider, })); } else if (transition.outboxRows.length > 0) { triggeredNotifications = await triggerNotifications({ monitorId, statusCode, message, notifType: EVENT_TYPE[status], cronTimestamp, regions: transition.affectedRegions, latency, incidentId: transition.incidentId ?? undefined, }); }
statusUpdate.notificationTriggered = triggeredNotifications.length > 0; statusUpdate.notifications = triggeredNotifications;
return c.text("Ok", 200);});