Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

前端错误监控 #71

Open
iiuhuy opened this issue Jan 2, 2020 · 0 comments
Open

前端错误监控 #71

iiuhuy opened this issue Jan 2, 2020 · 0 comments

Comments

@iiuhuy
Copy link
Owner

iiuhuy commented Jan 2, 2020

前端错误监控

前端代码异常监控实战
可以在浏览器控制台运行代码

  • 1.JS 处理异常的方式
  • 2.上报方式
  • 3.异常监控上报常见问题

JS 异常处理

对于 JavaScript,异常的出现不会直接导致 JS 引擎崩溃,会使当前执行的任务终止。

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

所以在对脚本错误进行上报之前,我们需要对异常进行处理,程序需要先感知到脚本错误的发生,然后再谈异常上报。

脚本错误一般分为两种:语法错误运行时错误

try-catch 异常处理

try-catch 在我们的代码中经常见到,通过给代码块进行 try-catch 进行包装后,当代码块发生出错时 catch 将能捕捉到错误的信息,页面也将可以继续执行。

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

// 示例:运行时错误
try {
  error; // 未定义变量
} catch (e) {
  console.log("我知道错误了");
  console.log(e);
}

对于语法错误和异步错误就捕捉不到了。

// 示例:语法错误
try {
  var error = 'error'   // 大写分号
} catch(e) {
  console.log('我捕获不到错误');
  console.log(e);
}

语法错误在编译的时候就会抛出异常,会导致应用崩溃,编码的时候一般会发现。

// 示例:异步异常
try {
  setTimeout(() => {
    error; // 异步错误
  });
} catch (e) {
  console.log("我感知不到错误");
  console.log(e);
}

// --------------------- //
// 除非在 setTimeout 函数中再套上一层 try-catch;否则是无法检测到的
try {
  setTimeout(() => {
    try {
      error; // 异步错误
    } catch (e) {
      console.log("抛出的错误", e);
    }
  });
} 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 对于语法错误也还是无能为力,所以编码的时候要避免语法错误。不过一般语法错误都会使页面崩溃,比较能够察觉到。

window.onerror 函数只有在返回 true 的时候,异常才不会向上抛出,否则即使是知道异常的发生控制台还是会显示 Uncaught Error: xxxxx。

还需要注意的点:

  • 1.对于 onerror 这种全局捕获,最好写在所有 JS 脚本的前面,因为无法保证写的代码是否出错,如果写在后面,一旦发生错误的话是不会被 onerror 捕获到的。
  • 2.另外 onerror 是无法捕获到网络异常的错误。
<script>
  window.onerror = function (msg, url, row, col, error) {
    console.log("我知道异步错误了");
    console.log({
      msg,
      url,
      row,
      col,
      error,
    });
    return true;
  };
</script>
<img src="./404.png" />

网络请求异常不会事件冒泡,必须在捕获阶段将其捕捉到才行。

window.addEventListener

<script>
window.addEventListener('error', (msg, url, row, col, error) => {
  console.log('我知道 404 错误了');
  console.log(
    msg, url, row, col, error
  );
  return true;
}, true);
</script>
<img src="./404.png" alt="">

window.addEventListener 虽然能捕获到网络请求的异常,但是无法判断 HTTP 的状态,是 404 还是其他,比如 500。所以还需要配合服务端日志才进行排查分析才行。

这点局限性还是要知道的,否则用户访问网站,图片 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 的时候在最后加上 catch 是个好习惯,但是难免有时候不会加上,给忘记了。

现在很多库都会有 Promise 的语法,例如常用的 axios,不知道什么时候这些异步请求会抛出异常而你并没有处理它,所以最好添加一个 Promise 全局异常捕获事件 unhandledrejection

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 error1");
});
new Promise((resolve) => {
  resolve();
}).then(() => {
  throw "promise error2";
});

没做 Promise 全局异常处理的话,看看控制台你就知道什么花儿为什么这样红。

异常上报方式

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

  • 1.通过 Ajax 发送数据
  • 2.动态创建 img 标签的形式
// 实例 - 动态创建 img 标签进行上报
function report(error) {
  var reportUrl = "http://xxxx/report";
  new Image().src = reportUrl + "error=" + error;
}

监控上报常见问题

Script error 脚本错误是什么

因为我们在线上的版本,经常做静态资源 CDN 化,这就会导致我们常访问的页面跟脚本文件来自不同的域名,这时候如果没有进行额外的配置,就会容易产生 Script error。

可通过 npm run nocors 查看效果。

Script error 是浏览器在同源策略限制下产生的,浏览器处于对安全性上的考虑,当页面引用非同域名外部脚本文件时中抛出异常的话,此时本页面是没有权利知道这个报错信息的,取而代之的是输出 Script error 这样的信息。

window.onerror 能否捕获 iframe 的错误

当你的页面有使用 iframe 的时候,你需要对你引入的 iframe 做异常监控的处理,否则一旦你引入的 iframe 页面出现了问题,你的主站显示不出来,而你却浑然不知。

首先需要强调,父窗口直接使用 window.onerror 是无法直接捕获,如果你想要捕获 iframe 的异常的话,有分好几种情况。

1.iframe 页面和主站是同域名的情况,直接给 iframe 添加 onerror 事件即可

<iframe src="./iframe.html" frameborder="0"></iframe>
<script>
  window.frames[0].onerror = function (msg, url, row, col, error) {
    console.log('我知道 iframe 的错误了,也知道错误信息');
    console.log({
      msg,  url,  row, col, error
    })
    return true;
  };
</script>

2.嵌入的 iframe 页面和你的主站不是同个域名的,但是 iframe 内容不属于第三方,是你可以控制的,那么可以通过与 iframe 通信的方式将异常信息抛给主站接收

与 iframe 通信的方式有很多,常用的如:postMessage,hash 或者 name 字段跨域等等,这里就不展开了,感兴趣的话可以看:跨域,你需要知道的全在这里(暂未添加)

3.非同域且网站不受自己控制的话

除了通过控制台看到详细的错误信息外,没办法捕获,这是出于安全性的考虑,你引入了一个百度首页,人家页面报出的错误凭啥让你去监控呢,这会引出很多安全性的问题。

压缩代码如何定位到脚本异常位置

线上的代码几乎都经过了压缩处理,几十个文件打包压缩并丑化成了一个代码,当我们收到 a is not defined 的时候,我们根本不知道这个变量 a 究竟是什么含义,此时报错的错误日志显然是无效的。

第一想到的办法是利用 sourcemap 定位到错误代码的具体位置。

另外也可以通过在打包的时候,在每个合并的文件之间添加几行空格,并相应加上一些注释,这样在定位问题的时候很容易可以知道是哪个文件报的错误,然后再通过一些关键词的搜索,可以快速地定位到问题的所在位置。

收集异常信息量太多,怎么办

如果你的网站访问量很大,假如网页的 PV 有 1kw,那么一个必然的错误发送的信息就有 1kw 条,我们可以给网站设置一个采集率:

Reporter.send = function (data) {
  // 只采集 30%
  if (Math.random() < 0.3) {
    send(data); // 上报错误信息
  }
};

这个采集率可以通过具体实际的情况来设定,方法多样化,可以使用一个随机数,也可以具体根据用户的某些特征来进行判定。

参考文章

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant