摘要
随着前端项目变得越来越复杂,一点小小的异常都有可能导致页面白屏、功能不可用,甚至用户直接流失。为了尽早发现这些问题,我们需要在项目中构建一个前端错误监控系统,能自动捕获错误、记录上下文、上报后端,辅助我们快速定位问题,保障线上应用的稳定运行。
引言
虽然市面上已有如 Sentry、Fundebug 等成熟的监控平台,但对于部分项目(特别是中小项目)来说,要么成本太高,要么无法灵活定制。这时候,自己搭建一套“够用”的前端监控系统,就是非常实际的选择。
本文将手把手带你实现一个前端错误监控系统,包括常见错误捕获方法、信息收集、后端上报接口以及典型的实际使用场景。
前端错误监控系统的搭建思路
全局错误捕获方式
JavaScript 运行时错误
// 捕获运行时错误(语法错误、null 访问等)
window.onerror = function (message, source, lineno, colno, error) {
reportError({
type: 'js_error',
message, // 错误信息
source, // 出错文件
lineno, // 行号
colno, // 列号
stack: error?.stack // 错误堆栈
});
};
解释:当你访问未定义变量、或函数中抛出异常,这个函数会第一时间捕获它,并将错误信息打包给 reportError
函数。
静态资源加载错误(图片、CSS、JS)
window.addEventListener('error', function (e) {
// 过滤 JS 运行错误,保留资源加载错误
if (e.target && e.target !== window) {
reportError({
type: 'resource_error',
tagName: e.target.tagName, // 资源标签名(如 IMG / SCRIPT)
src: e.target.src || e.target.href // 资源地址
});
}
}, true);
解释:这个事件在 true
捕获阶段触发,可以监听资源文件加载失败,比如图片 404 或字体文件加载不到。
Promise 未处理异常
window.addEventListener('unhandledrejection', function (e) {
reportError({
type: 'promise_error',
reason: e.reason?.message || JSON.stringify(e.reason) // 兼容各种 Promise 报错内容
});
});
解释:这类错误通常出现在你 fetch()
或异步函数没有 .catch()
的时候,可能会被忽略,非常隐蔽。
错误信息统一上报接口
我们将捕获到的错误发送到服务器接口,一般推荐使用异步请求。
function reportError(errorInfo) {
try {
const payload = {
...errorInfo,
url: location.href,
userAgent: navigator.userAgent,
time: Date.now()
};
// 推荐使用 sendBeacon,不阻塞主线程
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/log/error', JSON.stringify(payload));
} else {
fetch('/api/log/error', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
}
} catch (e) {
console.warn('reportError 发送失败', e);
}
}
说明:
sendBeacon()
:适合在页面卸载时使用,保证日志能发出去。- 加了
try-catch
防止监控代码本身出错。
在框架中接入错误监控
Vue 应用错误捕获(Vue 3)
import { createApp } from 'vue';
import App from './App.vue';
const app = createApp(App);
// Vue 全局错误处理
app.config.errorHandler = (err, instance, info) => {
reportError({
type: 'vue_error',
message: err.message,
stack: err.stack,
info
});
};
app.mount('#app');
说明:配合 Vue 的生命周期,能捕获组件内部的渲染错误、生命周期钩子中的异常。
用户提示与异常处理
你可以根据错误类型,给用户展示提示信息,而不是直接页面崩溃。
function handleUserNotice(errorInfo) {
if (errorInfo.type === 'js_error') {
alert('页面出现了一些小问题,请刷新后重试');
}
}
还可以加入“自动恢复机制”或“点击重试按钮”。
典型使用场景举例
场景 1:用户打开页面就白屏,控制台报错
可能是初始化代码异常。我们可以在入口加上 try-catch:
try {
app.mount('#app');
} catch (e) {
reportError({
type: 'entry_error',
message: e.message,
stack: e.stack
});
}
场景 2:资源加载失败导致图标不显示
在加载资源时增加标识:
<link rel="stylesheet" href="cdn/font-awesome.css" data-monitor="true">
然后在错误监听中判断:
if (e.target.dataset.monitor === 'true') {
reportError({
type: 'resource_error',
src: e.target.href,
tag: e.target.tagName
});
}
场景 3:用户点击按钮后无响应
可能是某个异步请求失败但未 catch:
fetch('/api/data')
.then(res => res.json())
.then(data => {
processData(data);
});
// 忘记加 .catch
通过 unhandledrejection
我们可以自动捕获到:
window.addEventListener('unhandledrejection', function (e) {
reportError({
type: 'promise_error',
reason: e.reason
});
});
QA 问答环节
Q:这个监控系统会影响页面性能吗?
不会。错误上报是异步的,不会阻塞主线程。你还可以批量收集再统一发送,进一步降低影响。
Q:如果错误很多,会不会一直重复上报?
建议加入简单去重机制,例如根据 message + stack
做 hash,存在缓存中,5分钟内相同的不再重复上报。
Q:如何知道哪些页面报错多?
你可以在错误上报接口中增加字段,比如:
pagePath: location.pathname
后端聚合统计后,就能知道哪些页面最容易出问题。
总结
搭建前端错误监控系统并不难,只要掌握:
- 错误的捕获机制(
onerror
、error
事件、Promise 异常等) - 上下文数据收集
- 合理的后端上报方式
- 框架错误集成(Vue、React 等)
这样的监控系统,可以帮助你第一时间发现问题、快速定位、避免用户流失。