前端代码异常监控

相关内容

  1. 什么是异常
  2. JS 处理异常的方式
  3. 异常上报方式
  4. 异常监控上报常见问题
  5. GitHub 前端代码异常监控方案

什么是异常

JS 脚本错误

一般分为语法错误 和 运行时错误

// 语法错误
// 语法错误在开发阶段就会暴露,所以一般不需要对其处理
var error = 'error'// 大写分号
// Uncaught SyntaxError: Invalid or unexpected toke
// 运行时错误
error // 未定义变量
// Uncaught ReferenceError: error is not defined

静态资源加载异常

<img src="notfount.jpg" alt="加载不存在图片" />
<!-- Failed to load resource: net::ERR_FILE_NOT_FOUND -->

Promise 异常

Promise.reject('wrong')
// Uncaught (in promise) wrong

// axios 返回 Promise,请求异常会抛出错误
axios({
  method: 'GET',
  url: 'http://xxx.com' // 无效的地址
})
// GET http://xxx.com/ net::ERR_CONNECTION_TIMED_OUT
// Uncaught (in promise) Error: Network Error

跨域异常 Script error.

当不同域的脚本发生错误,无法获取错误信息

// http://localhost:5000/error.js
throw Error('跨域脚本抛出的错误')
// http://localhost:5000/error.js
throw Error('本站脚本抛出的错误')
<!-- http://localhost:3000/index.html -->
<script>
window.onerror = function() {
  console.log(arguments)
  return true
}
</script>
<script src="http://localhost:3000/test.js"></script>
<script src="http://localhost:5000/test.js"></script>

报错:

在这里插入图片描述

JS 异常处理

  1. JS 代码块作为一个任务压入任务队列中,JS 线程不断从队列中提取任务执行。
  2. 当任务执行过程中出现异常,且异常没有捕获处理,则会沿着调用栈一层层向外抛出,最终终止当前任务的执行。
  3. JS 线程会继续从任务队列中提取下一个任务继续执行。
<script>
error
console.log('永远不会执行')
</script>
<script>
console.log('我继续执行')
</script>

对异常进行上报,首先程序要能感知或捕获到异常的发生并进行处理。

try catch

try catch 是代码中常用的捕获脚本错误的方式,当 try 包装的代码块报错时,catch 将捕捉到错误的信息,页面也可以继续执行,不会被阻塞。

不过 try catch 只能捕获到 同步的运行时错误,对 语法错误异步错误 无能为力。

语法错误 在编辑器中开发时会直接抛出,在项目上线前就可以发现。

异步错误 不容易发现,需要特别注意。

try {
  setTimeout(() => {
    error
  })
} catch (e) {
  console.log('无法感知异步错误')
}

总结:

  • try catch 只能捕获 JS 脚本 同步的运行时错误
  • 无法捕获 语法错误 和 异步错误

window.onerror

window.onerror 可以捕获 同步和异步的运行时错误

当 JS 运行时错误发生时, window 会触发一个 ErrorEvent 接口的 error 事件,并执行 window.onerror()

可以重新自定义这个方法,对异常进行处理。

/**
 * @param {String}  msg    错误信息
 * @param {String}  url    出错文件
 * @param {Number}  row    行号
 * @param {Number}  col    列号
 * @param {Object}  error  错误详细信息
 */
 window.onerror = function (msg, url, row, col, error) {
  console.log({
    msg,  url,  row, col, error
  })
  // 返回 true,可以阻止异常向上抛出
  return true
};
setTimeout(() => {
  error
})

不过,window.onerror 无法捕获 语法错误网络请求异常错误

语法错误 开发阶段容易察觉,所以不用特别处理。

<script>
// 先定义好 onerror
window.onerror = function(msg, url, row, col, err) {
  // ...
}
  
// 接口请求异常 无法捕获
axios({
  method: 'GET',
  url: 'http://localhost:3000'
})
</script>

<!-- 静态资源请求异常 无法捕获 -->
<!-- window.onerror 是 JS 脚本发生错误时触发的事件执行函数,所以静态资源请求异常无法触发该事件。 -->
<img src="notfount.jpg" />

总结:

在实际使用过程中,onerror 主要用来捕获预料之外的错误,而 try catch 用来在可预见情况下监控特定的错误,两者结合使用更加高效。

  • onerror 是 JS 脚本的全局捕获方法,最好写在所有 JS 脚本的前面,避免无法被捕获。
  • onerror 无法捕获网络异常的错误。
    • 静态资源
      • 这是由于加载资源的元素触发 error 事件不会冒泡到 window,所以需要在捕获 阶段捕捉才行。
    • 接口请求
      • net::ERR_CONNECTION_REFUSED
  • onerror 无法捕获 Promise 错误
    • 例如 axios 请求接口异常后,返回的Promise会抛出错误
    • Promise 错误可以被 Promise catch 捕获

window.addEventListener

当加载一个资源失败时,加载资源的元素会触发一个 Event 接口的 error 事件,并执行该元素上的 onerror 处理函数。

这些 error 事件不会向上冒泡到 window,因此必须在捕获阶段将其捕捉,可以通过 window.addEventListener 设置事件传播模式 useCapture,在捕获阶段捕捉错误。

<script>
window.addEventListener('error', (error) => {
	console.log('捕获到异常:', error);
}, true)
</script>

<img src="notfount.jpg" />

总结:

  • 可以通过 window.addEventListener 在事件捕获阶段捕捉 静态资源请求异常
  • 不过这种方式无法判断 HTTP 的状态,所以还需要配合服务端日志进行排查分析才行。
  • 不同浏览器下返回的 error 对象可能不同,需要注意兼容处理。
  • 需要注意避免 addEventListener 重复监听。

Promise - catch & unhandledrejection

Promise 可以使用 catch 方便的捕获它抛出的异常。

但是如果没有写 catch,以上方法都无法捕获到错误,类似 try catch,Promise catch 也相当于在可遇见情况下监视错误。

new Promise(() => {
  throw new Error('throw 抛出错误相当于调用 reject')
}).catch(console.log)

Promise.reject('reject 抛出错误').catch(console.log)

axios 请求会返回一个 Promise,发送的网络请求发生异常后,Promise 会抛出错误,这时可以用 catch 捕获。

项目中可能会用到很多 Promise 实例或基于 Promise 的库(axios),为了避免漏掉 Promise 异常,可以全局注册 unhandledrejection 事件监听 未被 catch 的Promise 异常

MDN:

Promise 被 reject 且没有 reject 处理器的时候,会触发 unhandledrejection 事件;这可能发生在 window下,但也可能发生在 Worker中。 这对于调试回退错误处理非常有用。

// addEventListener
window.addEventListener("unhandledrejection", event => {
  console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`);
});
// onunhandledrejection
window.onunhandledrejection = event => {
  console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`);
};
window.addEventListener('unhandledrejection', function (event) {
  // ...您的代码可以处理未处理的拒绝...

  // 防止默认处理(例如将错误输出到控制台)
  event.preventDefault();
});

Script error.

线上的项目一般会做 CDN 优化,这就会导致访问的页面和脚本来自不同的域名。

如果没有进行相应的配置,当访问的脚本报错,就会产生 Script error.

Script error. 是浏览器在同源策略限制下产生的。

浏览器出于安全的考虑,当页面引用非同域的外部脚本文件中抛出异常的话。

当前页面是没有权力知道这个报错信息的,目的是避免数据泄露到不安全的域中。

取而代之的就是输出 Script error. 这样的信息。

解决办法:

  1. 将脚本放到同域下,这样舍弃了 CDN 的优势
  2. 开启 CORS(跨源资源共享机制),从根本上解决,需要后端配置

CORS(跨源资源共享机制)

首先为页面的 script 标签添加 crossOrigin 属性。

<!-- http://localhost:3000/index.html -->
<script>
window.onerror = function() {
  console.log(arguments)
  return true
}
</script>
<script src="http://localhost:5000/test.js" crossorigin></script>
// http://localhost:5000/error.js
throw Error('跨域脚本抛出的错误')

其次需要后端为 http://localhost:5000 配置 Access-Control-Allow-Origin响应头。

serve 开启的web服务为例:

# --cors 命令启用 CORS 并配置 `Access-Control-Allow-Origin` 为 `*`
serve . --cors

Vue & React 错误捕获

异常上报方式

监控拿到报错信息后,就需要将捕捉的错误信息发送到信息收集平台上,常用的发送形式有两种:

  1. 通过 Ajax 发送数据

Ajax 请求本身也有可能会发生异常,可能引发跨域问题,所以更推荐使用动态创建 img 标签的形式进行上报。

  1. 动态创建按 img 标签
function report (error) {
  let reportUrl = 'http://xxx/report'
  new Image().src = reportUrl + '?error=' + error
}

异常监控上报常见问题

压缩代码无法定位到错误的具体位置

现在大多数项目都是通过 webpack 等工具打包压缩后发布到线上的。

多个文件压缩打包成一个文件,变量名被替换成了简短的未知字符串,代码被合并成了一行。

想要定位错误在源代码中的位置,就要使用 Source Map

参考 脚本错误量极致优化-让脚本错误一目了然

异常信息量太大

如果网站访问量很大,错误采集就会有两个问题:

  1. 上报请求次数庞大
  2. 错误日志记录太多

采集率

如果没有必要将所有错误信息全部采集下来,可以设置一个采集率,减少采集的信息量。

采集率可以通过使用任意方式设定,随机数、时间、用户特征等。

function report (error) {
  // 使用一个随机数
  if (Math.random() < 0.3) {
    let reportUrl = 'http://xxx/report'
  	new Image().src = reportUrl + '?error=' + error
  }
}

本地存储

如果不需要及时记录错误,可以先将错误日志存储到客户端,定时执行上报操作。

例如:

  1. 发生异常,捕获错误信息,存储到 localStorage
  2. 对比当前时间和上次上报的时间
  3. 如果大于10分钟,就执行上报,并清空本地存储的日志
  4. 如果小于10分钟,就不作处理

当然,这种方式可能也会丢失一些记录,例如某个客户端在上报前退出,并再也不访问网站。

总结

  1. 在可预见的地方增加 try catch 监控
  2. 全局监控 JS 异常 window.onerror
  3. 全局监控静态资源异常 window.addEventListener
  4. 捕获没有 Catch 的 Promise 异常:unhandledrejection
  5. VUE errorHandler 和 React componentDidCatch
  6. 跨域 crossOrigin 解决

参考

GitHub 前端代码异常监控方案

GitHub 搜索关键字 前端异常monitor,筛选 JavaScript 可以查到一些开源的监控方案,例如:

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值