mirror of
https://github.com/Xevion/bus-reminder.git
synced 2025-12-10 08:06:43 -06:00
Cronitor monitoring implementation
This commit is contained in:
76
src/monitor.ts
Normal file
76
src/monitor.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import {env} from "@/env/server.mjs";
|
||||||
|
import {z} from "zod";
|
||||||
|
|
||||||
|
const BASE_URL = `https://cronitor.link/p/${env.CRONITOR_ACCOUNT_ID}/${env.CRONITOR_JOB_ID}`;
|
||||||
|
const parameterSchema = z.object({
|
||||||
|
env: z.string().optional(),
|
||||||
|
host: z.string().optional(),
|
||||||
|
message: z.string().optional(),
|
||||||
|
metric: z.union([
|
||||||
|
z.object({count: z.number()}),
|
||||||
|
z.object({duration: z.number()}),
|
||||||
|
z.object({error_count: z.number()}),
|
||||||
|
]).optional(),
|
||||||
|
series: z.string().optional(),
|
||||||
|
state: z.enum(["run", "complete", "fail", "ok"]).optional(),
|
||||||
|
status_code: z.number().int().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
function buildURL(parameters: z.infer<typeof parameterSchema>) {
|
||||||
|
const url = new URL(BASE_URL);
|
||||||
|
|
||||||
|
// Definite string properties
|
||||||
|
const {env, host, message, series, state} = parameters;
|
||||||
|
for (const [key, value] of Object.entries({env, host, message, series, state}).filter(([, value]) => value != undefined))
|
||||||
|
url.searchParams.append(key, value!);
|
||||||
|
|
||||||
|
// Extract potentially undefined specified properties
|
||||||
|
if (parameters.metric != undefined) {
|
||||||
|
const {count, duration, error_count} = parameters.metric as {count?: number, duration?: number, error_count?: number};
|
||||||
|
if (count != undefined)
|
||||||
|
url.searchParams.append("metric", `count:${count}`);
|
||||||
|
else if (duration != undefined)
|
||||||
|
url.searchParams.append("metric", `duration:${duration}`);
|
||||||
|
else if (error_count != undefined)
|
||||||
|
url.searchParams.append("metric", `error_count:${error_count}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns The duration in seconds between the start and end dates
|
||||||
|
*/
|
||||||
|
function getDuration(start: Date, end: Date) {
|
||||||
|
return (end.getTime() - start.getTime()) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the given function while providing telemetry to Cronitor
|
||||||
|
* @param execute The function to execute
|
||||||
|
* @throws Any error thrown by the function will be rethrown. Cronitor will be notified of the failure.
|
||||||
|
*/
|
||||||
|
export default async function monitorAsync<T = any>(execute: () => Promise<T>): Promise<T> {
|
||||||
|
// Tell Cronitor that the job is running
|
||||||
|
const start = new Date();
|
||||||
|
await fetch(buildURL({state: "run"}).toString());
|
||||||
|
console.log("Cronitor: Job started")
|
||||||
|
|
||||||
|
// Execute the function, provide try/catch
|
||||||
|
let result: T | undefined = undefined;
|
||||||
|
try {
|
||||||
|
result = await execute();
|
||||||
|
} catch (error) {
|
||||||
|
const duration = getDuration(start, new Date());
|
||||||
|
await fetch(buildURL({state: "fail", message: error instanceof Error ? error.message : undefined, metric: {duration}}).toString());
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tell Cronitor that the job is complete (success)
|
||||||
|
console.log("Cronitor: Job completed")
|
||||||
|
const duration = getDuration(start, new Date());
|
||||||
|
await fetch(buildURL({state: "complete", metric: {duration}}).toString());
|
||||||
|
|
||||||
|
return result as T;
|
||||||
|
}
|
||||||
@@ -2,29 +2,39 @@
|
|||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { getDistance } from "@/location";
|
import { getDistance } from "@/location";
|
||||||
import { env } from "@/env/server.mjs";
|
import { env } from "@/env/server.mjs";
|
||||||
|
import monitorAsync from "@/monitor";
|
||||||
|
|
||||||
type ResponseData = {
|
type ResponseData = {
|
||||||
name: string;
|
diff: number;
|
||||||
|
inRange: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const center = { latitude: env.CENTER_LATITUDE, longitude: env.CENTER_LONGITUDE };
|
type StatusData = { status: string };
|
||||||
|
|
||||||
|
const center = {
|
||||||
|
latitude: env.CENTER_LATITUDE,
|
||||||
|
longitude: env.CENTER_LONGITUDE,
|
||||||
|
};
|
||||||
|
|
||||||
export default async function handler(
|
export default async function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse<ResponseData>
|
res: NextApiResponse<ResponseData & StatusData | StatusData>
|
||||||
) {
|
) {
|
||||||
|
|
||||||
if (req.query.key != env.API_KEY) {
|
if (req.query.key != env.API_KEY) {
|
||||||
// auth failed
|
// auth failed
|
||||||
res.status(401).json({ name: "Unauthorized" });
|
res.status(401).json({ status: "Unauthorized" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await monitorAsync(async function () {
|
||||||
const diff = await getDistance();
|
const diff = await getDistance();
|
||||||
|
// auth passed
|
||||||
// auth passed
|
res.setHeader(
|
||||||
res.setHeader('Cache-Control', `max-age=0, s-maxage=${env.EDGE_CACHE_TIME_SECONDS}, stale-while-revalidate`);
|
"Cache-Control",
|
||||||
// @ts-ignore
|
`max-age=0, s-maxage=${env.EDGE_CACHE_TIME_SECONDS}, stale-while-revalidate`
|
||||||
res.status(200).json({ diff, inRange: diff < env.MAX_DISTANCE });
|
);
|
||||||
|
res
|
||||||
|
.status(200)
|
||||||
|
.json({ diff, inRange: diff < env.MAX_DISTANCE, status: "Authorized" });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user