独立开发者没有 SRE 团队,但线上东西挂了一晚上不知道,是很伤的事——读者流失、评论丢一条是一条、渡劫code 用户会话全断。我的目标很明确:零预算搭一套「知道它挂了、知道它慢了、知道谁在搞事」的监控体系。整套跑了一年,月成本 0 元,全在 Cloudflare 免费额度和 Telegram 免费 Bot 里。这篇把每一层拆开讲,包括那个让告警自己「活着」的心跳设计。
一、第一层:Cloudflare 自带 Analytics
Cloudflare 后台白送的能力,很多人压根没看过:
- Workers Analytics:Dashboard 里每个 Worker 的请求数、错误数、CPU 时间分布(P50/P99)、按 Colo(PoP)拆分。还能通过 GraphQL Analytics API 编程拉取,写进自己的周报。
- Web Analytics(RUM):博客前端注入一段 beacon 脚本,白拿 PV/UV/来源/设备/国家分布。不跨站跟踪、隐私友好,读者不用弹 Cookie 同意。
- 免费额度内的保留:Workers Analytics 保留期有限(够看趋势,不够考古),长期数据定期用 API 导出到 R2 存证。
我们的日常数字:博客日均 PV 2400、UV 900;Workers 全家桶日均请求 3.1 万;P99 CPU 4.2ms(免费上限 10ms,还有余量但值得盯);错误率长期 <0.1%,超过 0.5% 就人工介入。Analytics 的定位是看趋势和容量,它不是告警源——数据有几分钟延迟,拿它报警等于马后炮。
二、第二层:Cron 状态页
架构一句话:一个 Cron Worker 每 5 分钟探测所有端点,结果写 KV,静态状态页读 KV 渲染。
探测清单(8 个端点):
- 博客主站首页(CF Pages 端)
- GitHub Pages 端、Gitee Pages 端(三端都要活,见 三端同步架构记)
- 评论 API
/health - 渡劫code 主站
- DO 房间 ping(带 token 的探针请求,同时测 DO 冷启动耗时)
- D1 备份结果检查(前一天备份文件是否出现在 R2,见 Queues + Cron 实战)
结果写两个 KV key:status:latest 存当前各端点状态 JSON;status:history 存环形数组,5 分钟一个点、保留 30 天共 8640 条,序列化后约 600KB——KV 单 value 上限 25MiB,毫无压力。状态页是纯静态 HTML,JS fetch /status-data.json(一个 Pages Function 读 KV 返回,带 60 秒 Cache-Control),渲染 90 天 Uptime 热力图,长得像 statuspage.io 但不要钱。
三个坑:
- KV 最终一致性:刚写入立刻读,跨 PoP 可能读到旧值。状态页允许 60 秒缓存无所谓,但告警判断绝不能依赖「读 KV」——直接在 Worker 内存里判断本次探测结果,KV 只是给页面看的。
- 自监控悖论:探测 Worker 和状态页自己挂了怎么办?我们的解法是 Telegram 告警走心跳模式:每 6 小时发一条「还活着」静默消息,超过 6 小时没收到心跳,说明监控链本身出问题了。Cloudflare 全球故障的那天,你收到的不是一堆误报,而是「没有消息」——这恰是设计意图。
- 探测超时要分级:普通端点 5 秒超时,DO ping 放宽到 10 秒(冷启动 + 跨区)。第一版全部 5 秒,DO 探针天天误报「超时」,其实是正常冷启动。
成本核算:8 端点 × 每天 288 次 = 2304 次请求/天,全走 Workers 免费额度(10 万/天),占比 2.3%。KV 写每天 288 次,免费额度 1000 次/天,占 29%——这是唯一需要留意的指标,端点清单别再涨了。
三、第三层:告警机器人
Telegram Bot,@BotFather 两分钟建好,代码二十行:
async function alert(env: Env, text: string) {
await fetch(`https://api.telegram.org/bot${env.TG_TOKEN}/sendMessage`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
chat_id: env.TG_CHAT_ID,
text,
parse_mode: 'HTML',
disable_web_page_preview: true,
}),
});
}
告警策略比代码重要,四条军规:
- 连续 2 次失败(10 分钟)才发——单次网络抖动不打扰,半夜的告警必须值钱
- 恢复也要通知:带上「持续了 X 分钟」,否则你不知道故障是真恢复还是探测挂了
- 聚合发送:同一轮探测多个端点失败,合成一条消息。CF 大面积故障时 8 个端点同时挂,8 条告警是噪音,1 条「CF 全局故障,8/8 端点不可达」才是信息
- 每晚 23 点汇总:当天异常条数,无异常也发。连续「一切正常」的汇总建立信任,真告警时你才不会当成垃圾推送忽略
限额完全不是问题:Telegram 30 条/秒,个人监控一天最多几十条。国内网络环境访问 api.telegram.org 不稳的读者,平替是企业微信机器人 webhook(一条 URL 搞定)或 Server酱(推微信),接口形态一模一样,改个 fetch 地址而已。
真实案例:上个月 03:17,Telegram 弹出「api.520691.xyz 连续 2 次 502,耗时 1.8s」——一次 D1 主库短暂写锁导致评论接口 502。从告警到定位 10 分钟,早高峰前恢复。这条告警值回全部票价。
四、日志层:tail 的艺术
wrangler tail:实时日志流,调试神器,但有采样和限流,不适合 7×24 常开。我们的用法:告警触发后开 10 分钟看现场,看完就关。- 结构化日志:全部
console.log(JSON.stringify({ level, msg, requestId, ms })),tail 里直接 jq 过滤。纯文本日志在边缘环境等于没日志。 - 慢查询打点:D1 查询超 200ms 记 warn 级日志,细节在 D1 优化那篇——监控的另一半是「慢」,而不只是「挂」。
- Logpush:全量日志外推到 R2/S3 是付费能力,免费版 tail + Analytics 对个人项目够用,别为不需要的持久化花钱。
五、业务指标:比 HTTP 状态码更重要的事
「站点 200」不等于「业务健康」。我们额外盯四个业务信号:
- Queues DLQ 深度:非零即告警(通知在静默死亡)
- DO 在线房间数:突然掉零说明 WebSocket 通道出问题,比用户反馈快得多
- D1 备份成功标记:备份失败是最高优先级——数据是独立开发者的最后底线
- R2 用量水位:超过免费额度 80% 时预警,留时间清理而不是等账单
小结
监控的性价比公式:覆盖关键路径 × 及时触达 ÷ 维护成本。三层各司其职——Analytics 看趋势、Cron 状态页看当下、Bot 负责叫你。全套 0 元,维护成本就是那 300 行 Worker 代码,写它的那个周末之后,我再没为「挂了不知道」失眠过。独立开发者最划算的投资,就是让机器替你守夜。