我对监控工具的看法:从爬虫崩了到JS埋点乱飞的血泪史
成都的初夏,窗外银杏叶刚绿,耳机里放着 Lo-fi Hip Hop,我左手咖啡右手键盘,正准备用 Rust 写个玩具项目放松一下——结果 Slack 突然疯狂弹窗:“线上爬虫服务挂了!”
这已经是我这周第三次被这种告警惊醒。说实话,作为一个重度 Cursor 用户(是的,我现在写代码基本靠它补全、重构、甚至 debug,已经离不开这个 AI 搭子了),我对“被动响应式运维”早就忍无可忍。每次都是用户投诉了、数据断了、老板问了,我们才手忙脚乱去查日志。监控不是锦上添花,而是保命符——这话是我被双11凌晨三点叫起来修 bug 之后悟出来的。
所以今天这篇,不讲高大上的 SRE 理论,就聊聊我在实际项目里踩过的坑、选错的工具、以及最后怎么用一套“土法炼钢 + 现代化埋点”的组合拳,把监控这件事从“救火”变成“防火”。
起因:那个崩掉的 JavaScript 爬虫
事情得从去年年底说起。我们团队接了个需求:抓取某电商平台上商品价格和评论数据,用于竞品分析。产品经理拍胸脯说:“很简单,就是个爬虫嘛!”——但凡他说“简单”,我就知道要加班。
技术方案定了:用 Puppeteer 控制 Headless Chrome,模拟真实用户行为(因为目标站有反爬,直接 fetch 肯定被 ban)。前端同事还顺手加了段 JS 埋点,记录页面加载时间、元素渲染状态等,方便我们判断是不是被检测到了。
// 前端埋点示例(简化版)
window.performanceData = {
loadTime: performance.timing.loadEventEnd - performance.timing.navigationStart,
domReady: performance.timing.domContentLoadedEventEnd - performance.timing.navigationStart,
hasAntiBot: !!document.querySelector('#captcha-modal')
};
听起来很美好?现实是:上线三天后,爬虫成功率从 95% 掉到 30%。更糟的是,我们完全不知道问题出在哪——是网络抖动?IP 被封?还是对方改了 DOM 结构导致 selector 失效?
日志里只有一堆 TimeoutError 和 Element not found,根本没法复现。测试同学甩锅给运维:“你们服务器 IP 质量太差!” 运维反呛:“你 JS 写得跟屎一样,连错误都没 catch!” 我夹在中间,真的想砸电脑。
那一刻我意识到:没有可观测性(Observability)的系统,就像蒙着眼开 F1 赛车。
初期尝试:用 Prometheus + Grafana 撑场面
作为科班出身的程序员,第一反应当然是上 Prometheus。毕竟“云原生三件套”嘛,K8s 都上了,不搞个指标监控说不过去。
我们在爬虫服务里加了几个基础指标:
// Rust 服务端(用 actix-web + prometheus)
use prometheus::{register_int_counter, IntCounter};
lazy_static! {
pub static ref CRAWL_SUCCESS_TOTAL: IntCounter =
register_int_counter!("crawl_success_total", "Total successful crawls").unwrap();
pub static ref CRAWL_FAILURE_TOTAL: IntCounter =
register_int_counter!("crawl_failure_total", "Total failed crawls").unwrap();
}
// 在 handler 里
if result.is_ok() {
CRAWL_SUCCESS_TOTAL.inc();
} else {
CRAWL_FAILURE_TOTAL.inc();
}
配合 Grafana 画了个看板,看着曲线图还挺唬人。可问题来了:这些指标太粗粒度了。我知道失败率高,但不知道是因为 JS 执行超时、还是网络请求 403、还是页面结构变了。
更尴尬的是,前端 JS 埋点的数据根本没传回来。为啥?因为我们只监控了后端服务,忘了前端也是整个链路的一环!
这时候 Cursor 发挥了作用。我对着它吐槽:“我想要一个能同时监控后端指标和前端埋点的方案”,它直接给我生成了一段建议:
“考虑用 OpenTelemetry 统一采集 traces、metrics 和 logs,前端用 web-vitals 库上报性能数据,后端用 OTLP 协议发送。”
虽然当时觉得 OTel 有点重,但确实指了条明路。
转折点:把 JS 埋点接入监控体系
我们决定做两件事:
- 让前端 JS 埋点能主动上报
- 后端接收并关联这些数据
首先改造前端代码,在爬虫任务结束时,把 performanceData 发给我们的监控 API:
// 爬虫任务完成后
fetch('/api/telemetry', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
taskId: window.TASK_ID,
timestamp: Date.now(),
metrics: window.performanceData,
userAgent: navigator.userAgent
})
});
然后在 Rust 后端加个 endpoint 接收:
#[derive(Deserialize)]
struct TelemetryPayload {
task_id: String,
timestamp: u64,
metrics: serde_json::Value,
}
async fn handle_telemetry(payload: web::Json<TelemetryPayload>) -> impl Responder {
// 存入 ClickHouse(后面会说为什么选它)
telemetry_db.insert(&payload).await.unwrap();
HttpResponse::Ok().json("ok")
}
关键来了:如何把前端埋点和后端日志关联起来?
我们给每个爬虫任务生成一个全局唯一的 trace_id,前后端都带上它。这样在排查时,输入一个 trace_id,就能看到完整的调用链:从任务发起 → JS 执行 → 页面加载 → 数据提取 → 结果存储。
这个设计灵感其实来自我之前被领导逼着学的分布式追踪课程——没想到真用上了。
监控工具选型:为什么没选 ELK?
说到数据存储,很多人第一反应是 ELK(Elasticsearch + Logstash + Kibana)。但我们没选,原因很现实:
- 成本太高:ES 吃内存太狠,我们小团队预算有限
- 写入延迟:日志要等几秒才能搜到,线上事故等不起
- 复杂度高:Logstash 配置写得我想哭
后来我们试了 ClickHouse —— 俄罗斯人写的列式数据库,专为 OLAP 设计。插入百万条日志只要几秒,查询更是快到飞起。
比如查“最近一小时所有包含 anti-bot 的任务”:
SELECT task_id, user_agent, metrics
FROM telemetry
WHERE
timestamp > now() - INTERVAL 1 HOUR
AND metrics.hasAntiBot = 1
LIMIT 100;
执行时间:0.03 秒。运维小哥第一次看到结果时惊了:“这比查 Redis 还快?”
我们还用 Grafana 直连 ClickHouse,画了个自定义看板,展示:
- 每小时爬虫成功率
- 平均页面加载时间分布
- Anti-bot 触发率趋势
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 任务成功率 | 30% | 88% |
| 平均故障定位时间 | 2小时 | 15分钟 |
| 团队夜间报警次数 | 5+/周 | 0 |
数据不会骗人。自从有了这套监控,产品经理再也没敢说“就是个爬虫嘛”。
血泪教训:别忽视业务语义
但也不是一帆风顺。有一次,我们发现成功率突然暴跌,但所有技术指标都正常:CPU 不高、网络延迟低、JS 埋点也没报 anti-bot。
查了整整一天,最后发现是业务逻辑 bug:目标网站把“缺货”状态从 div.out-of-stock 改成了 span.sold-out,而我们的爬虫还在找旧 class,结果返回空数据,但程序认为“成功抓取了”(只是内容为空)。
这暴露了一个致命问题:监控不能只看“是否完成”,还要看“是否正确完成”。
于是我们加了“业务健康度”指标:
// 检查抓取结果是否合理
if extracted_data.price <= 0 || extracted_data.title.is_empty() {
BUSINESS_INVALID_RESULT.inc(); // 新增指标
log::warn!("Invalid data for task {}: {:?}", task_id, extracted_data);
}
同时,在 JS 埋点里也加了内容校验:
// 检查关键元素是否存在
window.performanceData.hasPrice = !!document.querySelector('.price');
window.performanceData.hasTitle = !!document.querySelector('h1.product-title');
现在,我们的告警规则不只是“失败率 > 10%”,而是:
- 技术失败率 > 5%
- 或 业务无效率 > 8%
- 或 页面关键元素缺失率突增
这才是真正的“可观测性”——不仅知道系统坏了,还知道为什么坏、坏在哪一层。
关于 JavaScript 的特殊挑战
说到 JS,不得不提它的动态特性给监控带来的麻烦。比如:
- 异步回调地狱:错误堆栈难追踪
- 第三方脚本污染:广告、统计代码可能拖慢页面
- 浏览器兼容性:不同版本执行速度差异大
我们用两个技巧缓解:
- 统一错误上报:
window.addEventListener('error', (e) => {
fetch('/api/js-error', {
method: 'POST',
body: JSON.stringify({
message: e.message,
filename: e.filename,
lineno: e.lineno,
stack: e.error?.stack
})
});
});
- 用 Performance Observer 监控长任务:
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 100) { // 超过100ms算卡顿
reportLongTask(entry);
}
}
});
observer.observe({ entryTypes: ['longtask'] });
这些数据汇入 ClickHouse 后,我们甚至能分析出:“使用 Chrome 112 的用户,长任务发生率比 111 高 3 倍”——直接推动我们升级了 Puppeteer 的 Chromium 版本。
最终架构:轻量但闭环
现在的监控体系长这样:
[爬虫任务]
│
├── (Rust 服务) → 指标 → Prometheus → Alertmanager
│
├── (Headless Chrome) → JS 埋点 → /api/telemetry → ClickHouse
│
└── (错误日志) → Loki → Grafana Logs
所有数据在 Grafana 里聚合,一个面板搞定:
- 上方:Prometheus 指标(QPS、错误率)
- 中间:ClickHouse 查询(业务有效性、JS 性能)
- 下方:Loki 日志(具体错误堆栈)
最关键的是,告警能直达责任人。比如:
- 如果是 JS 执行超时 → @前端同学
- 如果是 IP 被封 → @运维同学
- 如果是解析逻辑错误 → @我(谁让我是爬虫 owner)
上周五晚上,系统自动告警:“anti-bot 触发率突增至 40%”。我还没来得及看,运维已经换了一批代理 IP,前端也调整了点击节奏。整个过程无人值守,10 分钟恢复。我继续听我的 Lo-fi,喝我的冰美式——这才是程序员该有的生活节奏,尤其是在成都。
写在最后:监控的本质是“信任”
回过头看,我对监控工具的看法其实很简单:
- 不要为了监控而监控:每个指标都要有明确的行动指引(比如“如果 X>Y,就做 Z”)
- 打通前后端:现代应用是端到端的,监控也该如此
- 业务指标 > 技术指标:用户不在乎你的 CPU,只在乎能不能买到商品
- 工具越简单越好:我们没上全套 OpenTelemetry,但用 ClickHouse + 自定义埋点解决了 90% 问题
有人说:“监控是成本中心。” 但我觉得,好的监控是利润中心——它减少故障损失、提升迭代信心、甚至帮产品决策(比如“用户在第3步流失最多,优化那里!”)。
至于我?现在写代码基本靠 Cursor,但它再聪明,也替代不了我对系统状态的掌控感。而这份掌控感,来自每一行埋点、每一个指标、每一次提前预警。
哦对了,最近在用 Rust 重写监控 agent,性能提升 3 倍,内存占用只有 Go 版的 1/5。如果你也在折腾监控,欢迎交流——不过先让我把这个 borrow checker 错误搞定再说 😅

评论 0