diff --git a/CLAUDE.md b/CLAUDE.md index 1f83c4a..3669348 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,7 +34,6 @@ Cloudflare Workers with three entry points exported from `src/index.js`: | GET | `/` | inline | Health check | | POST | `/webhook/telegram` | `bot-commands.js` | grammY `webhookCallback("cloudflare-mod")` | | POST | `/webhook/status/:secret` | `statuspage-webhook.js` | Receives Statuspage webhooks (URL secret) | -| GET | `/metrics/:secret` | inline | Bot statistics (text or `?format=json`) | | GET | `/migrate/:secret` | inline | One-time KV migration (remove after use) | ### Data Flow @@ -52,7 +51,6 @@ Per-subscriber keys (no read-modify-write races): Special keys: - `last-status` — JSON snapshot of component statuses for cron comparison -- `metrics` — Counters for webhooks, messages, cron checks, commands `kv-store.js` handles key building/parsing with `kv.list({ prefix: "sub:" })` pagination. `threadId` can be `0` (General topic), so null checks use `!= null`. diff --git a/README.md b/README.md index 76112e3..f4f179c 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,6 @@ Hosted on [Cloudflare Workers](https://workers.cloudflare.com/) with KV for stor - **Supergroup topic support** — send `/start` in a specific topic and notifications go to that topic - **On-demand status check** — `/status` fetches live data from status.claude.com - **Automatic status monitoring** — cron checks every 5 minutes as a safety net -- **Metrics dashboard** — track webhooks, messages, cron checks via `/metrics` endpoint - **Self-healing** — automatically removes subscribers who block the bot ## Bot Commands diff --git a/src/bot-commands.js b/src/bot-commands.js index 141c960..ba0112c 100644 --- a/src/bot-commands.js +++ b/src/bot-commands.js @@ -8,8 +8,6 @@ import { } from "./kv-store.js"; import { fetchComponentByName, escapeHtml } from "./status-fetcher.js"; import { registerInfoCommands } from "./bot-info-commands.js"; -import { trackMetrics } from "./metrics.js"; - /** * Extract chatId and threadId from grammY context */ @@ -27,12 +25,6 @@ export async function handleTelegramWebhook(c) { const bot = new Bot(c.env.BOT_TOKEN); const kv = c.env.claude_status; - // Track command usage - bot.use(async (ctx, next) => { - await trackMetrics(kv, { commandsProcessed: 1 }); - await next(); - }); - bot.command("start", async (ctx) => { const { chatId, threadId } = getChatTarget(ctx); await addSubscriber(kv, chatId, threadId); diff --git a/src/cron-status-check.js b/src/cron-status-check.js index 88a3a72..8f6db71 100644 --- a/src/cron-status-check.js +++ b/src/cron-status-check.js @@ -1,7 +1,5 @@ import { fetchSummary, humanizeStatus, escapeHtml } from "./status-fetcher.js"; import { getSubscribersByType } from "./kv-store.js"; -import { trackMetrics } from "./metrics.js"; - const LAST_STATUS_KEY = "last-status"; /** @@ -59,11 +57,6 @@ export async function handleScheduled(env) { timestamp: new Date().toISOString(), })); - await trackMetrics(kv, { - cronChecks: 1, - lastCronAt: new Date().toISOString(), - }); - if (changes.length === 0) return; console.log(`Cron: ${changes.length} component change(s) detected`); @@ -80,6 +73,4 @@ export async function handleScheduled(env) { } console.log(`Cron: enqueued ${messages.length} messages for ${name} change`); } - - await trackMetrics(kv, { cronChangesDetected: changes.length }); } diff --git a/src/index.js b/src/index.js index 6097eb6..d60d305 100644 --- a/src/index.js +++ b/src/index.js @@ -4,8 +4,6 @@ import { handleStatuspageWebhook } from "./statuspage-webhook.js"; import { handleQueue } from "./queue-consumer.js"; import { handleScheduled } from "./cron-status-check.js"; import { migrateFromSingleKey } from "./kv-store.js"; -import { getMetrics, formatMetricsText } from "./metrics.js"; - const app = new Hono(); /** @@ -23,18 +21,6 @@ app.get("/", (c) => c.text("Claude Status Bot is running")); app.post("/webhook/telegram", (c) => handleTelegramWebhook(c)); app.post("/webhook/status/:secret", (c) => handleStatuspageWebhook(c)); -// Metrics endpoint — view bot statistics -app.get("/metrics/:secret", async (c) => { - const secret = c.req.param("secret"); - if (!await validateSecret(secret, c.env.WEBHOOK_SECRET)) { - return c.text("Unauthorized", 401); - } - const metrics = await getMetrics(c.env.claude_status); - const format = c.req.query("format"); - if (format === "json") return c.json(metrics); - return c.text(formatMetricsText(metrics)); -}); - // One-time migration route — remove after migration is confirmed app.get("/migrate/:secret", async (c) => { const secret = c.req.param("secret"); diff --git a/src/metrics.js b/src/metrics.js deleted file mode 100644 index ccbe9a4..0000000 --- a/src/metrics.js +++ /dev/null @@ -1,77 +0,0 @@ -const METRICS_KEY = "metrics"; - -const DEFAULT_METRICS = { - webhooksReceived: 0, - messagesEnqueued: 0, - messagesSent: 0, - messagesFailedPermanent: 0, - messagesRetried: 0, - subscribersRemoved: 0, - cronChecks: 0, - cronChangesDetected: 0, - commandsProcessed: 0, - lastWebhookAt: null, - lastCronAt: null, - startedAt: new Date().toISOString(), -}; - -/** - * Get current metrics from KV - */ -export async function getMetrics(kv) { - const data = await kv.get(METRICS_KEY, "json"); - return data || { ...DEFAULT_METRICS }; -} - -/** - * Increment one or more metric counters and optionally set timestamp fields - */ -export async function trackMetrics(kv, updates) { - const metrics = await getMetrics(kv); - for (const [key, value] of Object.entries(updates)) { - if (typeof value === "number") { - metrics[key] = (metrics[key] || 0) + value; - } else { - metrics[key] = value; - } - } - await kv.put(METRICS_KEY, JSON.stringify(metrics)); -} - -/** - * Format metrics as HTML for Telegram or plain text for API - */ -export function formatMetricsText(metrics) { - const uptime = metrics.startedAt - ? timeSince(new Date(metrics.startedAt)) - : "unknown"; - - return [ - `Webhooks received: ${metrics.webhooksReceived}`, - `Messages enqueued: ${metrics.messagesEnqueued}`, - `Messages sent: ${metrics.messagesSent}`, - `Messages failed: ${metrics.messagesFailedPermanent}`, - `Messages retried: ${metrics.messagesRetried}`, - `Subscribers auto-removed: ${metrics.subscribersRemoved}`, - `Cron checks: ${metrics.cronChecks}`, - `Cron changes detected: ${metrics.cronChangesDetected}`, - `Commands processed: ${metrics.commandsProcessed}`, - `Last webhook: ${metrics.lastWebhookAt || "never"}`, - `Last cron: ${metrics.lastCronAt || "never"}`, - `Tracking since: ${uptime}`, - ].join("\n"); -} - -/** - * Human-readable time duration since a given date - */ -function timeSince(date) { - const seconds = Math.floor((Date.now() - date.getTime()) / 1000); - if (seconds < 60) return `${seconds}s ago`; - const minutes = Math.floor(seconds / 60); - if (minutes < 60) return `${minutes}m ago`; - const hours = Math.floor(minutes / 60); - if (hours < 24) return `${hours}h ${minutes % 60}m ago`; - const days = Math.floor(hours / 24); - return `${days}d ${hours % 24}h ago`; -} diff --git a/src/queue-consumer.js b/src/queue-consumer.js index b8ff549..b0f2e61 100644 --- a/src/queue-consumer.js +++ b/src/queue-consumer.js @@ -1,7 +1,5 @@ import { removeSubscriber } from "./kv-store.js"; import { telegramUrl } from "./telegram-api.js"; -import { trackMetrics } from "./metrics.js"; - /** * Process a batch of queued messages, sending each to Telegram. * Handles rate limits (429 → retry), blocked bots (403/400 → remove subscriber). @@ -59,10 +57,7 @@ export async function handleQueue(batch, env) { } } - await trackMetrics(env.claude_status, { - messagesSent: sent, - messagesFailedPermanent: failed, - messagesRetried: retried, - subscribersRemoved: removed, - }); + if (sent || failed || retried || removed) { + console.log(`Queue batch: sent=${sent} failed=${failed} retried=${retried} removed=${removed}`); + } } diff --git a/src/statuspage-webhook.js b/src/statuspage-webhook.js index ede53b7..706652a 100644 --- a/src/statuspage-webhook.js +++ b/src/statuspage-webhook.js @@ -1,7 +1,5 @@ import { getSubscribersByType } from "./kv-store.js"; import { humanizeStatus, escapeHtml } from "./status-fetcher.js"; -import { trackMetrics } from "./metrics.js"; - /** * Timing-safe string comparison */ @@ -93,11 +91,5 @@ export async function handleStatuspageWebhook(c) { console.log(`Enqueued ${messages.length} messages for ${category}${componentName ? `:${componentName}` : ""}`); - await trackMetrics(c.env.claude_status, { - webhooksReceived: 1, - messagesEnqueued: messages.length, - lastWebhookAt: new Date().toISOString(), - }); - return c.text("OK", 200); }