我是怎么在家用前端监控工具救了线上项目的命
去年双11前两周,我正窝在出租屋的床上,一手握着泡面桶,一手在 VSCode 里疯狂敲代码。作为刚毕业三个月的大专生,能靠自学前端进到这家远程办公的小公司,说实话我自己都还有点不敢信。入职没多久就赶上大促,压力直接拉满——而就在这节骨眼上,用户反馈页面白屏、按钮点不动、订单提交失败的 bug 突然暴增。
更离谱的是,本地和测试环境一切正常,一上线就“薛定谔式崩溃”。我和后端兄弟对骂了三天,连运维都甩锅说“可能是 CDN 缓存问题”,直到我偶然在控制台瞥见一行报错:
Uncaught TypeError: Cannot read property 'map' of undefined
但问题是……这个错误压根没上报!我们项目里连个像样的错误监控都没有。
那一刻我真想砸了这台用了五年的 ThinkPad(虽然它已经快散架了)。也是从那天起,我下定决心:必须给项目加上前端监控工具。不然下次再出事,产品经理怕是要把我钉在需求墙上。
面试题里总考,工作中却没人提?
其实早在面试时,我就被问过:“你们项目怎么处理前端错误上报?”当时支支吾吾说了个 window.onerror,面试官笑了笑没再追问——我知道,他看出来我根本没实战经验。
现在回头看,很多初级前端(包括我)以为监控就是加个 Sentry 就完事了。但现实是:工具只是手段,关键是怎么用、怎么集成、怎么让数据真正有用。
我们公司用的是 Vue3 + Vite + TypeScript 技术栈,团队只有三个前端(包括我),老板对“可观测性”这种词嗤之以鼻,只关心“能不能少出 bug”。所以我的任务很明确:选一个轻量、易集成、成本低、能快速见效的监控方案。
工具选型:别被大厂方案吓到
一开始我想上 Sentry,毕竟名气大、文档全。但一看到要自己搭服务端(或者每月几百刀的 SaaS 费用),直接劝退。我们这种小团队,连专门的 DevOps 都没有,怎么可能维护一个监控后台?
于是我开始对比市面上的开源/免费方案:
| 工具 | 是否需要自建服务 | 免费额度 | 集成难度 | 自定义能力 |
|---|---|---|---|---|
| Sentry | 是(或付费 SaaS) | 有限 | 中等 | 强 |
| Fundebug | 否(SaaS) | 5k 次/月 | 低 | 中 |
| Badjs(腾讯开源) | 是 | 无限制 | 高 | 强 |
| 自研简易上报 | 否 | 0 成本 | 低 | 弱(初期) |
考虑到时间紧、人手少、预算为零,我决定先走“自研简易上报 + 第三方日志平台”的混合路线。核心思路是:先收集,再分析,最后告警。
我把目标拆解成三步:
- 捕获 JS 错误、资源加载失败、Promise reject
- 上报到后端(或第三方)
- 能查、能看、能告警
动手干:50 行代码搞定基础监控
别被“监控系统”这个词吓到。其实核心逻辑就那么几行。我在项目里新建了个 monitor.js,用 Vue 的插件形式注入:
// monitor.ts
const MONITOR_URL = 'https://log.mycompany.com/frontend-error';
interface ErrorLog {
message: string;
stack?: string;
url: string;
userAgent: string;
timestamp: number;
type: 'js' | 'resource' | 'promise';
}
function sendLog(log: ErrorLog) {
// 使用 navigator.sendBeacon 保证页面卸载时也能上报
if (navigator.sendBeacon) {
navigator.sendBeacon(MONITOR_URL, JSON.stringify(log));
} else {
// fallback: image 打点
const img = new Image();
img.src = `${MONITOR_URL}?data=${encodeURIComponent(JSON.stringify(log))}`;
}
}
export default {
install(app: any) {
// 1. 捕获全局 JS 错误
window.addEventListener('error', (e) => {
sendLog({
message: e.message,
stack: e.error?.stack || '',
url: location.href,
userAgent: navigator.userAgent,
timestamp: Date.now(),
type: 'js'
});
});
// 2. 捕获未处理的 Promise rejection
window.addEventListener('unhandledrejection', (e) => {
sendLog({
message: e.reason?.message || String(e.reason),
stack: e.reason?.stack || '',
url: location.href,
userAgent: navigator.userAgent,
timestamp: Date.now(),
type: 'promise'
});
e.preventDefault(); // 防止控制台红字(其实没用,但显得专业)
});
// 3. 捕获资源加载失败(图片、脚本等)
window.addEventListener('error', (e) => {
const target = e.target as HTMLElement;
if (target.tagName === 'IMG' || target.tagName === 'SCRIPT') {
sendLog({
message: `Resource load failed: ${target.src || target.getAttribute('href')}`,
url: location.href,
userAgent: navigator.userAgent,
timestamp: Date.now(),
type: 'resource',
stack: ''
});
}
}, true); // useCapture: true,确保在冒泡前捕获
}
};
然后在 main.ts 里注册:
import Monitor from './utils/monitor';
app.use(Monitor);
搞定!前后不到一小时。虽然简陋,但至少能把错误“吐”出来。
但光有上报不够,得让人看得懂
第一次上线后,我在后端写了个简单的 Node.js 接口接收日志,存到 MongoDB。结果第二天打开一看,全是这种:
message: "Cannot read property 'name' of undefined"
stack: "at eval (order.vue:45)"
这玩意儿谁看得懂?第 45 行?可我们的代码是打包压缩过的啊!
问题来了:生产环境的 sourcemap 不公开,怎么还原错误位置?
这时候我才意识到,真正的难点不是“上报”,而是“可读性”。
解决方案有两个:
- 在构建时生成 sourcemap,并上传到监控平台
- 在本地用 sourcemap 反解错误堆栈
我们选了后者——因为不想把 sourcemap 暴露给外网(安全风险)。于是我在本地写了个小脚本,用 source-map 库自动解析:
// decode-stack.js
const fs = require('fs');
const { SourceMapConsumer } = require('source-map');
async function decodeStack(stack, mapPath) {
const rawSourceMap = JSON.parse(fs.readFileSync(mapPath, 'utf8'));
const consumer = await new SourceMapConsumer(rawSourceMap);
return stack.split('\n').map(line => {
const match = line.match(/at (\S+) \((.+):(\d+):(\d+)\)/);
if (match) {
const [, func, file, line, col] = match;
const pos = consumer.originalPositionFor({
line: parseInt(line),
column: parseInt(col)
});
if (pos.source) {
return `at ${func} (${pos.source}:${pos.line}:${pos.column})`;
}
}
return line;
}).join('\n');
}
虽然每次都要手动跑脚本有点麻烦,但至少能定位到具体组件和行号了。后来我们甚至把这个脚本集成到了内部管理后台,输入错误 ID 就能自动反解——产品经理都惊了:“你这 bug 定位速度比我提需求还快?”
让监控“活”起来:从被动接收到主动预警
有一次凌晨三点,用户大量投诉支付失败。我睡得正香,手机突然震动——是企业微信机器人发来的告警:
【前端监控告警】
过去5分钟内,TypeError: Cannot read property 'status' of null错误超过 100 次
影响页面:/checkout
相关 commit:a1b2c3d
我一个鲤鱼打挺坐起来,打开电脑,十分钟后定位到是后端接口返回了 null 而不是 { status: ... }。临时加了个空值判断,热更新上线,搞定。
这个告警是怎么来的?其实就是后端加了个简单的聚合规则:
- 每分钟统计各错误类型的出现次数
- 如果某错误在 5 分钟内超过阈值(比如 50 次),就触发 webhook
- webhook 调用企业微信机器人 API 发消息
虽然糙,但救命。
动画交互爱好者的额外彩蛋
作为一个对前端动画和交互特别感兴趣的码农,我还偷偷加了个“用户体验监控”功能:记录用户点击无效区域的次数。
比如,某个按钮点了没反应(可能因为状态未加载完成),用户狂点十次。这种行为本身说明交互有问题。我在全局加了个 click 监听:
let lastClickTime = 0;
document.addEventListener('click', (e) => {
const now = Date.now();
if (now - lastClickTime < 300) {
// 300ms 内重复点击,可能是在“狂点”
sendLog({
message: 'Rapid click detected',
detail: { target: e.target.tagName, class: e.target.className },
type: 'ux',
// ...
});
}
lastClickTime = now;
});
后来发现,首页“立即抢购”按钮在活动开始前经常被狂点——于是产品加了个倒计时 loading 态,用户满意度直线上升。老板居然夸我“有产品思维”,笑死。
踩过的坑,都是未来的简历亮点
当然,过程没那么顺利。比如:
- 重复上报:同一个错误,用户刷新十次,上报十次。解决办法:加指纹去重(用错误信息 + 行号 + 页面 URL 做 hash)
- 性能影响:频繁上报拖慢页面。改用
sendBeacon+ 批量发送(每 30 秒发一次队列) - 隐私合规:不能随便传用户信息。我们把所有敏感字段(如 token、手机号)过滤掉,只保留技术上下文
最惨的一次是我把监控日志打到了生产数据库,结果半夜 DB CPU 100%,运维差点把我拉黑。从此以后,所有日志都走单独的日志服务,和业务库隔离。
现在回头看:监控不是奢侈品,是必需品
很多人觉得,小项目没必要搞监控。但我想说:越是小团队,越需要监控。因为你没有人力去一个个复现用户反馈的 bug。
现在的我,每次接手新项目,第一件事就是问:“有没有前端监控?”如果没有,我会花半天时间把基础监控搭起来——就当是给未来的自己买份保险。
上周五晚上,我又收到了一条告警,但这次只是个小 warning。我喝了口冰可乐,改了两行代码,提交,睡觉。那种“一切尽在掌握”的感觉,真的爽。
如果你也在自学前端,或者刚入行不久,别被“高大上”的架构吓住。从一个 window.onerror 开始,慢慢迭代,你也能搭建出属于自己的监控体系。
毕竟,能在线上救火的前端,才是真·高级前端(虽然我工资还没涨)。
后记:最近在准备跳槽,面试官又问监控相关的问题。这次我不仅能说出原理,还能掏出自己项目的监控截图和优化数据。他说:“你这经验,比很多三年经验的都扎实。”
——你看,当初那个被 bug 逼疯的大专生,终于也能笑着讲“事故”了。

评论 0