mirror of
https://github.com/MrUnknownDE/cloudflare-prometheus-exporter.git
synced 2026-04-27 18:13:44 +02:00
129 lines
3.7 KiB
TypeScript
129 lines
3.7 KiB
TypeScript
import { env } from "cloudflare:workers";
|
|
import { Hono } from "hono";
|
|
import { LandingPage } from "./components/LandingPage";
|
|
import { AccountMetricCoordinator } from "./durable-objects/AccountMetricCoordinator";
|
|
import { MetricCoordinator } from "./durable-objects/MetricCoordinator";
|
|
import { MetricExporter } from "./durable-objects/MetricExporter";
|
|
import { type AppConfig, parseConfig } from "./lib/config";
|
|
import { checkHealth, healthResponse } from "./lib/health";
|
|
import { configFromEnv, createLogger } from "./lib/logger";
|
|
import {
|
|
ConfigKeySchema,
|
|
getConfig,
|
|
getConfigKey,
|
|
getEnvDefaults,
|
|
resetAllConfig,
|
|
resetConfigKey,
|
|
setConfigKey,
|
|
} from "./lib/runtime-config";
|
|
|
|
export { MetricCoordinator, AccountMetricCoordinator, MetricExporter };
|
|
|
|
type Variables = { config: AppConfig };
|
|
|
|
const app = new Hono<{ Bindings: Env; Variables: Variables }>();
|
|
|
|
// Parse config middleware
|
|
app.use("*", async (c, next) => {
|
|
c.set("config", parseConfig(c.env));
|
|
await next();
|
|
});
|
|
|
|
// Disable guards
|
|
app.use("*", async (c, next) => {
|
|
const path = c.req.path;
|
|
if (c.var.config.disableUi && path === "/") {
|
|
return c.text("Not Found", 404);
|
|
}
|
|
if (c.var.config.disableConfigApi && path.startsWith("/config")) {
|
|
return c.text("Not Found", 404);
|
|
}
|
|
await next();
|
|
});
|
|
|
|
// Dynamic metrics path middleware (runs before routing)
|
|
app.get(env.METRICS_PATH, async (c) => {
|
|
const logger = createLogger("worker", configFromEnv(c.env)).withContext({
|
|
request_id: crypto.randomUUID(),
|
|
});
|
|
logger.info("Metrics request received");
|
|
|
|
try {
|
|
const coordinator = await MetricCoordinator.get(c.env);
|
|
const output = await coordinator.export();
|
|
logger.info("Metrics exported successfully");
|
|
return c.text(output, 200, {
|
|
"Content-Type": "text/plain; charset=utf-8",
|
|
});
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
logger.error("Failed to collect metrics", { error: message });
|
|
return c.text(`Error collecting metrics: ${message}`, 500);
|
|
}
|
|
});
|
|
|
|
// Routes
|
|
app.get("/", (c) => c.html(<LandingPage config={c.var.config} />));
|
|
|
|
app.get("/health", async (c) => {
|
|
const health = await checkHealth(c.env);
|
|
return healthResponse(health);
|
|
});
|
|
|
|
// Config API routes
|
|
app.get("/config", async (c) => {
|
|
const config = await getConfig(c.env);
|
|
return c.json(config);
|
|
});
|
|
|
|
app.get("/config/defaults", (c) => {
|
|
const defaults = getEnvDefaults(c.env);
|
|
return c.json(defaults);
|
|
});
|
|
|
|
app.get("/config/:key", async (c) => {
|
|
const keyResult = ConfigKeySchema.safeParse(c.req.param("key"));
|
|
if (!keyResult.success) {
|
|
return c.json({ error: "Invalid config key" }, 400);
|
|
}
|
|
const value = await getConfigKey(c.env, keyResult.data);
|
|
return c.json({ key: keyResult.data, value });
|
|
});
|
|
|
|
app.put("/config/:key", async (c) => {
|
|
const keyResult = ConfigKeySchema.safeParse(c.req.param("key"));
|
|
if (!keyResult.success) {
|
|
return c.json({ error: "Invalid config key" }, 400);
|
|
}
|
|
const body = await c.req.json<{ value: unknown }>().catch(() => null);
|
|
if (!body || !("value" in body)) {
|
|
return c.json({ error: "Request body must contain 'value'" }, 400);
|
|
}
|
|
const result = await setConfigKey(c.env, keyResult.data, body.value);
|
|
if (!result.success) {
|
|
return c.json(
|
|
{ error: "Invalid value", details: result.error.issues },
|
|
400,
|
|
);
|
|
}
|
|
return c.json(result.config);
|
|
});
|
|
|
|
app.delete("/config/:key", async (c) => {
|
|
const keyResult = ConfigKeySchema.safeParse(c.req.param("key"));
|
|
if (!keyResult.success) {
|
|
return c.json({ error: "Invalid config key" }, 400);
|
|
}
|
|
const config = await resetConfigKey(c.env, keyResult.data);
|
|
return c.json(config);
|
|
});
|
|
|
|
app.delete("/config", async (c) => {
|
|
const config = await resetAllConfig(c.env);
|
|
return c.json(config);
|
|
});
|
|
|
|
app.notFound((c) => c.text("Not Found", 404));
|
|
|
|
export default app;
|