前言
想象你开了一家无人超市。你坐在办公室,突然收到投诉:货架倒了、扫码枪坏了、冷柜不制冷了。你跑过去一看,一切正常。然后你走了,问题又出现。你需要一套远程监控系统:实时看到每个货架的状态、每笔交易的成功率、每个摄像头的画面。这就是前端监控的活。
前端监控就是给网站装“黑匣子”:记录错误、性能、用户行为,然后上报到服务器,让你能在后台看到一切。今天我们就来搭一套简易但够用的前端监控系统。
一、监控什么?四个核心维度
- 错误监控:JS报错、Promise拒绝、资源加载失败、API接口报错。
- 性能监控:页面加载时间、首次渲染、DOM ready、接口响应时间。
- 用户行为:点击、路由跳转、页面停留时长、用户操作路径。
- 业务数据:比如“支付成功率”、“注册完成率”。
二、错误监控:抓尽所有“漏网之鱼”
1. JS运行时错误
用window.onerror捕获同步错误和部分异步错误。
window.onerror = function(message, source, lineno, colno, error) {
const report = {
type: 'jsError',
message,
stack: error?.stack,
file: source,
line: lineno,
col: colno,
userAgent: navigator.userAgent,
url: location.href
};
sendReport(report); // 上报到服务器
return true; // 阻止默认控制台输出
};
2. Promise 未捕获的拒绝
用unhandledrejection捕获未处理的Promise reject。
window.addEventListener('unhandledrejection', (event) => {
const report = {
type: 'promiseError',
reason: event.reason,
stack: event.reason?.stack
};
sendReport(report);
});
3. 资源加载失败(图片、script、link)
window.addEventListener('error', (event) => {
if (event.target !== window) {
const report = {
type: 'resourceError',
tagName: event.target.tagName,
src: event.target.src || event.target.href
};
sendReport(report);
}
}, true); // 捕获阶段
4. Vue/React 组件错误
Vue:Vue.config.errorHandler
React:componentDidCatch 或 ErrorBoundary
// Vue
Vue.config.errorHandler = (err, vm, info) => {
sendReport({ type: 'vueError', err, info });
};
// React
class ErrorBoundary extends React.Component {
componentDidCatch(error, errorInfo) {
sendReport({ type: 'reactError', error, errorInfo });
}
render() { return this.props.children; }
}
5. 接口请求错误(axios/fetch)
拦截axios响应或重写fetch。
// axios 拦截器
axios.interceptors.response.use(
response => response,
error => {
sendReport({
type: 'apiError',
url: error.config.url,
status: error.response?.status,
message: error.message
});
return Promise.reject(error);
}
);
三、性能监控:谁拖慢了页面?
1. Navigation Timing API
window.addEventListener('load', () => {
const timing = performance.timing;
const report = {
type: 'performance',
dns: timing.domainLookupEnd - timing.domainLookupStart,
tcp: timing.connectEnd - timing.connectStart,
ttfb: timing.responseStart - timing.requestStart,
domReady: timing.domContentLoadedEventEnd - timing.navigationStart,
load: timing.loadEventEnd - timing.navigationStart
};
sendReport(report);
});
2. 首屏时间(自定义)
可以用MutationObserver检测页面关键元素出现的时间。
3. 使用 Web Vitals(Core Web Vitals)
Google推荐的三个指标:LCP(最大内容绘制)、FID(首次输入延迟)、CLS(累积布局偏移)。可以用web-vitals库。
import {getLCP, getFID, getCLS} from 'web-vitals';
getLCP(metric => sendReport({type: 'webVital', name: 'LCP', value: metric.value}));
// 同理 FID, CLS
四、用户行为监控:他到底点了哪里?
记录关键操作:页面访问、点击、路由变化、表单提交。
1. 路由变化
SPA里监听popstate和pushState(需要重写)。
const originalPushState = history.pushState;
history.pushState = function(...args) {
originalPushState.apply(this, args);
sendReport({ type: 'routeChange', url: location.href, method: 'pushState' });
};
window.addEventListener('popstate', () => {
sendReport({ type: 'routeChange', url: location.href, method: 'popstate' });
});
2. 点击事件
全局监听click,上报目标元素的关键信息(避免上报敏感信息)。
document.addEventListener('click', (e) => {
const target = e.target;
const report = {
type: 'click',
tag: target.tagName,
text: target.innerText?.slice(0, 50),
className: target.className,
id: target.id
};
sendReport(report);
});
注意防抖和采样(比如只上报10%的点击),避免上报量过大。
五、上报方式:怎么把数据传回来?
- 使用
navigator.sendBeacon:页面关闭时也能保证发送,不阻塞跳转。 - 图片打点(GET):
new Image().src = '/log?data=...',简单,跨域友好。 - POST请求:数据量大时用
fetch,注意跨域和失败重试。
function sendReport(data) {
const url = 'https://your-monitor-server.com/report';
if (navigator.sendBeacon) {
navigator.sendBeacon(url, JSON.stringify(data));
} else {
fetch(url, {
method: 'POST',
body: JSON.stringify(data),
keepalive: true
}).catch(e => console.warn('上报失败', e));
}
}
六、采样与去重:别把服务器打满
- 采样:只上报一部分用户(比如10%),用随机数或用户ID哈希。
- 去重:同一错误连续触发多次,可以合并上报(比如5秒内只报一次)。
const errorCache = new Map();
function dedupeReport(errorKey, data, delay = 5000) {
if (errorCache.has(errorKey)) return;
errorCache.set(errorKey, true);
sendReport(data);
setTimeout(() => errorCache.delete(errorKey), delay);
}
七、推荐工具:不用自己造轮子
- Sentry:最流行的错误监控,支持全框架,提供SourceMap上传、错误聚合、报警。
- Fundebug:国内,支持微信小程序。
- 阿里ARMS、腾讯RUM:企业级。
- 自建:可以用Elasticsearch + Kibana,或直接买第三方服务。
八、实战:最小化监控SDK
class Monitor {
constructor(serverUrl) {
this.url = serverUrl;
this.init();
}
init() {
window.onerror = (msg, file, line, col, err) => this.send('js', { msg, file, line, col, stack: err?.stack });
window.addEventListener('unhandledrejection', e => this.send('promise', { reason: e.reason }));
// 其他监听...
}
send(type, data) {
const payload = { type, data, time: Date.now(), ua: navigator.userAgent, url: location.href };
navigator.sendBeacon(this.url, JSON.stringify(payload));
}
}
new Monitor('https://your-server.com/api/log');
九、总结:监控是你的“远程遥控器”
- 监控错误、性能、行为、业务数据。
- 用
onerror、unhandledrejection、资源错误捕获。 - 上报用
sendBeacon,注意采样去重。 - 生产环境推荐用Sentry等成熟方案。
有了监控,用户还没投诉,你已经知道Bug在哪,连夜修复。第二天用户起床,Bug已消失,还以为自己卡了。这就是前端监控的魔力。
如果你觉得今天的“天眼”够犀利,点个赞让更多人看到。明天我们将聊聊微前端——巨石应用拆解术,多个团队并行开发的终极方案。我们明天见!