如何监控前端异常

一套完整的前端异常监控体系建设复杂度很高,涵盖异常捕获、数据收集、归类统计、分析告警、现场复现、源码定位,同时还要考量日志存储、上报量对服务端的压力、采样策略等工程问题。

本文聚焦浏览器端异常捕获与上报:先分清要盯哪些异常,再落到能用的捕获 API、上报通道和常见坑。

为什么要监控前端异常?

  • 提升用户体验:提前感知线上故障,避免大量用户遇到报错却没有反馈渠道。
  • 远程定位线上问题:不需要复现用户环境,通过上报日志还原故障现场。
  • 提前发现潜在缺陷:版本上线后快速捕捉隐性 bug,不等用户大量投诉。
  • 解决移动端复现难痛点:移动端机型、系统、WebView 环境繁杂,很多问题本地无法复现。
  • 完善工程体系:异常监控是前端工程化、质量保障体系中必不可少的一环。

JS 发生异常不会直接让 JS 引擎整体崩溃,只会终止当前执行任务,其余代码依旧可以运行;但网页崩溃属于渲染进程整体挂掉,JS 完全停止执行,需要单独方案监控。

需要监控哪些前端异常?

JS 代码异常

  • 语法错误 SyntaxError:代码书写语法非法,脚本直接解析失败。
  • 运行时异常
    • EvalError:规范里与 eval 相关的错误类;现行引擎里 eval 非法代码通常抛 SyntaxError,业务里很少再见到 EvalError
    • RangeError:数值超出合法范围
    • ReferenceError:引用未定义变量
    • TypeError:变量类型错误
    • URIError:URI 处理函数传参非法
    • Error:通用基础错误类

静态资源加载异常

imgscriptlinkaudiovideoiframe@font-face 等外链资源加载失败(404、跨域、网络中断)。

Promise 未捕获异常

Promise 内部 reject,但是没有写 .catch() 进行捕获处理。

网络请求异常

  • XMLHttpRequest 请求失败(超时、4xx、5xx)
  • fetch 请求异常(fetch 只会在网络层失败才 reject,HTTP 错误码如 404、500 不会抛出异常)

网页崩溃异常

浏览器渲染进程崩溃,页面白屏,所有 JS 停止执行,普通异常捕获 API 全部失效,需要特殊方案检测。

有哪些捕获前端异常的方式?

try-catch-finally

只能捕获同步运行时错误无法捕获语法错误、异步错误(定时器、回调、Promise)。适合对可疑业务代码块做局部保护。

try {
  throw new Error("业务代码执行出错");
} catch(e){
  // 捕获错误对象,做上报处理
  report(e);
} finally {
  console.log("无论是否报错都会执行");
}

window.onerror

全局监听 JS 运行时错误,包括部分语法错误。

限制:不能捕获资源加载异常、网络请求异常

注意:该监听代码必须优先于业务脚本执行;返回 true 可以阻止浏览器控制台打印未捕获错误。

window.onerror = function (msg, url, row, col, error) {
  console.log({ msg,  url,  row, col, error });
  return true; // 阻止控制台抛出
};

参数:

  • msg:错误信息;
  • url:报错脚本地址;
  • row:发生错误的行号;
  • col:发生错误的列号;
  • error:完整 Error 对象(包含堆栈 stack)。

window.addEventListener

资源的 error 事件不会向上冒泡,需要开启捕获阶段监听。

  • 可以捕获:JS 运行时错误 + 静态资源加载异常(script、img、link 加载失败)
  • 不能识别 HTTP 状态码,无法区分 404、500,需要结合事件的 srcElement 判断元素类型。

注意onerroraddEventListener 都会收到 JS 运行时错误,需要做判断,避免同一错误重复上报;只有 event.srcElement 是资源 DOM 元素时,才判定为资源加载错误。

window.addEventListener('error', function(event) {
  // 判断是资源加载异常
  if (event.srcElement && (
    event.srcElement instanceof HTMLScriptElement ||
    event.srcElement instanceof HTMLLinkElement ||
    event.srcElement instanceof HTMLImageElement
  )) {
    console.log("资源加载失败:", event.srcElement.src);
  }
}, true)

unhandledrejection(Promise 全局异常)

没有 catch 的 Promise 抛出异常不会触发 onerror,需要监听 unhandledrejection 事件。调用 e.preventDefault() 可以阻止控制台打印 Uncaught (in promise) 警告。

window.addEventListener("unhandledrejection", function(e){
  console.log("Promise未捕获异常", e.reason, e.promise);
  e.preventDefault();
})

Promise.catch

代码中 Promise 务必添加 .catch() 做本地捕获,避免异常抛到全局。

new Promise((resolve, reject) => {
  throw new Error("promise内部报错");
}).catch(e => {
  console.log("捕获promise异常", e);
})

拦截网络请求

原生 onerror 无法捕获接口返回 404、500 这类业务 HTTP 错误,需要劫持原生对象(重写 XMLHttpRequestfetch),拦截请求的失败、超时、abort 场景。

fetch 特性:HTTP 非 2xx 状态不会 reject,只有网络完全失败才会走 catch,因此需要手动判断 response.ok

// 劫持XMLHttpRequest
function hookXhr(report) {
  if (!window.XMLHttpRequest) return;
  const originSend = XMLHttpRequest.prototype.send;
  XMLHttpRequest.prototype.send = function() {
    const handle = (e) => {
      if(this.readyState === 4 && this.status >=400) {
        report({
          type: "xhr_error",
          status: this.status,
          url:this.responseURL
        })
      }
    }
    this.addEventListener('error', handle);
    this.addEventListener('load', handle);
    this.addEventListener('abort', handle);
    return originSend.apply(this, arguments);
  }
}
// 劫持fetch
function hookFetch(report) {
  if(!window.fetch) return;
  const originFetch = window.fetch;
  window.fetch = function(...args) {
    return originFetch.apply(this, args).then(res=>{
      if(!res.ok) {
        report({
          type: "fetch_error",
          url: args[0],
          status:res.status
        })
      }
      return res;
    }).catch(err=>{
      report({type:"fetch_network_error", err})
      throw err;
    })
  }
}

框架专属异常钩子

Vue 2 Vue.config.errorHandler:捕获 Vue 组件渲染、生命周期内抛出的异常。

Vue.config.errorHandler = (err, vm, info) => {
  console.error("Vue组件异常", err, vm, info);
}

Vue 3 app.config.errorHandler:挂到应用实例上,而不是全局 Vue.config

import { createApp } from "vue";
import App from "./App.vue";

const app = createApp(App);
app.config.errorHandler = (err, instance, info) => {
  console.error("Vue组件异常", err, instance, info);
}

componentDidCatch、Error Boundary:React 用 Error Boundary 捕获子组件渲染异常;类组件的 componentDidCatch 拿到错误信息。函数组件需要自行封装或使用支持 Error Boundary 的写法。

网页崩溃监控

网页崩溃时整个渲染进程停止,JS 全部停止执行,普通捕获 API 完全失效。

load + beforeunload + sessionStorage

原理:页面正常打开标记状态 pending;正常关闭触发 beforeunload 标记为 true;如果发生崩溃,页面直接销毁,不会执行 beforeunload。下次重新打开页面读取 sessionStorage,如果状态还是 pending,判定上一次发生崩溃。

缺陷:浏览器彻底关闭时 sessionStorage 会清空,会丢失崩溃标记;多标签页打开会造成误报。

window.addEventListener('load', function () {
  sessionStorage.setItem('good_exit', 'pending');
  setInterval(function () {
    sessionStorage.setItem('time_before_crash', new Date().toString());
  }, 1000);
});

window.addEventListener('beforeunload', function () {
  sessionStorage.setItem('good_exit', 'true');
});

// 页面再次加载后判断
if(sessionStorage.getItem('good_exit') && sessionStorage.getItem('good_exit') !== 'true') {
  // 上报崩溃日志
  console.log("检测到上一次页面发生崩溃", sessionStorage.getItem('time_before_crash'));
}

Service Worker 心跳检测(更可靠)

Service Worker 运行在独立线程,页面崩溃 SW 通常不受影响:

  • 页面每隔一段时间向 SW 发送心跳,携带唯一 sessionId;
  • 页面正常关闭发送 unload 消息,SW 清除该会话;
  • SW 定时遍历会话列表,如果超过阈值没有收到心跳,则判定页面发生崩溃。

限制:SW 需要 HTTPS 环境,必须同域名注册;部分老旧浏览器不兼容。

异常信息上报方式

上报需要注意:报错的时候本身网络可能很差,上报逻辑不能阻塞业务,尽量使用异步,避免产生新异常

Image 打点上报(最兼容)

利用图片请求,不会阻塞页面,跨域无限制;数据放在 URL query 参数,适合小体积日志。

function reportByImg(log) {
  const img = new Image();
  img.src = `/api/report?data=${encodeURIComponent(JSON.stringify(log))}`;
}

异步接口上报(fetch、XMLHttpRequest)

POST 发送完整错误对象、堆栈、上下文信息;使用 sendBeacon,页面卸载时也可以完成上报。

navigator.sendBeacon:页面 unload 阶段优先使用,浏览器会在后台排队发送,不会阻塞页面关闭。

function reportByBeacon(log) {
  const data = JSON.stringify(log);
  if (navigator.sendBeacon) {
    navigator.sendBeacon('/api/report', data);
  } else {
    fetch('/api/report', {method:'POST', body:data, keepalive:true})
  }
}

异常监控常见问题

Script error. 跨域脚本报错

当 JS 脚本放在 CDN 跨域域名,浏览器出于安全策略,会隐藏完整堆栈,只返回 Script error.

修复两个条件缺一不可:

  • <script src="cdn/xxx.js" crossorigin> 给 script 标签添加 crossorigin 属性
  • CDN 资源服务器返回 CORS 响应头 Access-Control-Allow-Origin

onerror、addEventListener('error') 有什么区别?

在 JavaScript 中,onerroraddEventListener('error') 都是用来捕获错误事件的,两者最核心的区别在于:捕获错误的能力不同

  • window.onerror:能捕获 JS 运行时错误,不能捕获静态资源加载错误(如图片 404、脚本加载失败)。

  • addEventListener('error'):能捕获资源加载错误,也能捕获 JS 运行时错误。

原因:资源加载错误不冒泡,到不了 window,所以 window.onerror 收不到;而 addEventListener 既可以监听 window,也可以监听具体元素,开启捕获阶段,所以可以捕获这两种错误。

推荐:绝大部分项目以 addEventListener 为主即可。

只有一种边缘场景是 addEventListener 做不到的:阻止浏览器在控制台打印红色错误信息

// 控制台不显示红色报错
window.onerror = function() {
    return true;
};

// 无法阻止控制台不显示红色报错
window.addEventListener('error', function(e) {
    e.preventDefault();
    return false
});

监控 SDK 采集运行时错误时,以 addEventListener('error') 为主即可覆盖 JS 异常和资源加载失败;需要屏蔽控制台红色报错时,才额外使用 window.onerrorreturn true

上报量过大压垮服务器

  • 线上不建议全量上报,需要做采样率
  • 过滤开发环境错误;
  • 过滤高频重复报错;
  • 对上报队列做合并、限流。

压缩代码无法定位源码

上报得到的是压缩混淆后的堆栈,需要上传 sourcemap,监控平台通过 sourcemap 还原原始源码位置。

业界成熟监控工具

  • Sentry(开源,推荐):支持多语言多框架,捕获 JS 异常、崩溃、性能指标,支持 sourcemap 解析、告警、issue 管理,可以自己 Docker 私有化部署,也可以直接使用 Sentry 云服务。
  • 阿里 ARMS:阿里云前端监控 SaaS 产品。
  • Fundebug:专注 JS 错误监控的 SaaS 平台。
  • BetterJS:百度早期开源的轻量前端监控库,已停止活跃维护,新项目不建议作为首选。

Sentry 快速接入示例(Vue 3)

import * as Sentry from "@sentry/vue";
import { createApp } from "vue";
import App from "./App.vue";

const app = createApp(App);

Sentry.init({
  app,
  dsn: "你的项目DSN地址",
  tracesSampleRate: 0.2, // 性能采样率,生产环境不要设置 1.0
});

// 手动上报异常
try {
  console.log(a);
} catch (err) {
  Sentry.captureException(err);
}
// 上报自定义消息
Sentry.captureMessage("自定义告警消息");

Vue 2 把 app 换成根实例构造函数即可:Sentry.init({ Vue, dsn: "..." })

总结

日志上报优先使用 sendBeacon 或图片打点;生产环境配置采样率,接入 sourcemap 还原混淆代码堆栈。

场景 捕获手段
局部可疑代码 try-catch
全局 JS 运行时异常 window.onerroraddEventListener('error')
静态资源加载失败 addEventListener('error', ..., true)
未捕获 Promise 异常 unhandledrejection
XHR、fetch 接口错误 劫持重写原生请求对象
Vue 组件内错误 Vue 2:Vue.config.errorHandler;Vue 3:app.config.errorHandler
React 组件渲染错误 Error Boundary
页面崩溃 beforeunload + sessionStorage,或 Service Worker 心跳

© lizhao all right reserved,powered by Gitbook文件修订时间: 2026-08-31 20:11:27

results matching ""

    No results matching ""