我对监控工具的看法:从爬虫崩了到JS埋点乱飞的血泪史

极客达人
2025-12-15 07:49
阅读 789

成都的初夏,窗外银杏叶刚绿,耳机里放着 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 失效?

日志里只有一堆 TimeoutErrorElement 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 埋点接入监控体系

我们决定做两件事:

  1. 让前端 JS 埋点能主动上报
  2. 后端接收并关联这些数据

首先改造前端代码,在爬虫任务结束时,把 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,不得不提它的动态特性给监控带来的麻烦。比如:

  • 异步回调地狱:错误堆栈难追踪
  • 第三方脚本污染:广告、统计代码可能拖慢页面
  • 浏览器兼容性:不同版本执行速度差异大

我们用两个技巧缓解:

  1. 统一错误上报
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
    })
  });
});
  1. 用 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

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝