从 0 到 1 开始前端异常监控

基础认知:

快速定位线上问题,捕获一些由于特殊情况导致的无法重现的客户问题

JS 处理异常的方式

try-catch 异常处理

但是 try-catch 处理异常的能力有限, 只能捕获捉到运行时非异步错误,对于语法错误和异步错误就显得无能为力,捕捉不到

下面是try-catch基本使用场景

示例:运行时错误

try {
  error    // 未定义变量 
} catch(e) {
  console.log('我知道错误了');
  console.log(e);
}
前端异常监控封面1

然而对于语法错误和异步错误就捕捉不到了 示例:语法错误

try {
 // ;符号大写,其实这种错误一般不会出现,因为在前期编辑器阶段就会体现出现,不会让程序正常发布
  var error = 'error';   
} catch(e) {
  console.log('我感知不到错误');
  console.log(e);
}

它会在开发的时候,浏览器就会报这个错误

前端

window.onerror 异常处理

window.onerror 捕获异常能力比 try-catch 稍微强点,无论是异步还是非异步错误,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('我知道错误了');
  console.log({
    msg,  url,  row, col, error
  })
  return true;
};

error;

示例:异步错误

window.onerror=function(msg,url,row,col,error){
  console.log('我知道异步错误了');
  console.log({
    msg,  url,  row, col, error
  })
  return true;
};

//定时器异步
setTimeout(() => {
  error;
});

关于 window.onerror 还有两点需要值得注意

  • 1.对于 onerror 这种全局捕获,最好写在所有 JS 脚本的前面,因为你无法保证你写的代码是否出错,如果写在后面,一旦发生错误的话是不会被 onerror 捕获到的。

  • 2.另外 onerror 是无法捕获到网络异常的错误。

当我们遇到 404 网络请求异常的时候, onerror 是无法帮助我们捕获到异常的。

可以看到没具体的错误信息和错误状态,但是有一个错误目标对象

这点知识还是需要知道,要不然用户访问网站,图片 CDN 无法服务,图片加载不出来而开发人员没有察觉就尴尬了。

Promise 错误

通过 Promise 可以帮助我们解决异步回调地狱的问题,但是一旦 Promise 实例抛出异常而你没有用 catch 去捕获的话, onerrortry-catch 也无能为力,无法捕捉到错误。

window.addEventListener('error',(msg,url,row,col,error)=>{
  console.log('我感知不到 promise 错误');
  console.log(
    msg, url, row, col, error
  );
}, true);

Promise.reject('promise error');

new Promise((resolve, reject) => {
  reject('promise error');
});

new Promise((resolve) => {
  resolve();
}).then(() => {
  throw 'promise error'
});

所以我们需要主动监听 Promise 抛出的全局异常

window.addEventListener("unhandledrejection",function(e){
  e.preventDefault()
  console.log('我知道 promise 的错误了');
  console.log(e.reason);
  return true;
});

Promise.reject('promise error');

new Promise((resolve, reject) => {
  reject('promise error');
});

new Promise((resolve) => {
  resolve();
}).then(() => {
  throw 'promise error'
});

前端异常监控现状

Vue全局异常捕获

Vue 全局配置 errorHandler 可以进行全局错误收集,我们可以根据这个特性对前端异常做这样的处理:业务错误直接写在业务里;代码错误、 ajax 请求异常等错误可以进行全局捕获然后抛出,不至于前端页面挂掉

  • 代码层面的错误

import Vue from 'vue'
/**
 * err object  异常信息
 * vm          错误异常实例  
 * info        是 Vue 特定的错误信息 
 */
// 只在 2.2.0+ 可用
Vue.config.errorHandler=function(err,vm,info){
    //此处处理具体的错误信息
 return true;
 }

但是由于 errorHandler 无法捕获 promise 抛出的异常和网络层面资源的异常,所以还需要添加

//捕捉Promise异常
window.addEventListener("unhandledrejection",function(e){

});

//捕获网络错误,资源错误这些类似的错误,需要添加以下事件监听
window.addEventListener("error", function(e){
    
});

// 记住
window.addEventListener("error",()=>{})
和
`window.onerror=()=>{}`是由区别的

一个让你很开心的Api

navigator.sendBeacon() 方法可用于通过HTTP将少量数据异步传输到Web服务器。

  • 数据可靠,浏览器关闭请求也照样能发

  • 异步执行,不会影响下一页面的加载

  • 同时不会延迟页面的卸载或影响下一导航的载入性能

  • API使用简单

window.addEventListener('unload',logData,false);
function logData(){
   navigator.sendBeacon("http://mob/api/sendLog",{
     //参数
   });
}

浏览器兼容性很乐观

实现方案

1:数据传递部分 根据浏览器兼容是否支持 navigator.sendBeacon ,如果支持使用 navigator.sendBeacon 发送数据,如果不支持使用创建 Image 标签的形式发送数据

var img = new Image();
img.onload =function (){
}
img.src = `http://code/api/sendLog?data=${JSON.stringify({})}`;

//或者

window.addEventListener('unload', function(){
  navigator.sendBeacon("http://code/api/sendLog",{});
}, false);

2:需要收集的数据部分

{
    accountId:"账号标识ID",
    url:"当前页面URL",
    browser:"所属浏览器",
    version:"浏览器版本",
    system:"所属系统",
    referer:"入口页面",
    jsPath:"异常js文件路径",
    errorObj:"异常错误信息json字符串",
    connection:"连接网络情况"
}

下一篇将分享具体的功能实现,关注我下篇文件见