Sentry 错误监控

本篇文章主要是对最近公司接入前端监控,关于前端错误监控的一些了解。

前言

Sentry 是一项可帮助您实时监控和修复崩溃的服务。它支持前后端移动端等平台,囊括近百种编程语言,Node.js,PHP,Python,Go,JavaScript 等等。拥有了它,我们就能拥有了完整错误收集、监控和管理能力。这篇文章主要介绍了前端错误及 Sentry 的原理。

前端异常及捕获

在正式了解 Sentry 之前,我们首先要对 JavaScript 中的错误异常有一个清晰的认识。毕竟 Sentry 作为一个异常收集的工具,主要面对的就是前端异常。对于 JS 来说,通常异常的出现并不会直接导致 JS 引擎崩溃,只会使当前执行的任务终止,下面是常见的异常类型:

异常类型

思维导图

Try-Catch 能捕获什么

try-catch 只能捕获到同步状态下的运行时错误,无法捕获到语法和异步错误

  1. 同步状态下的运行时错误
1
2
3
4
5
6
7
8
9
10
try {
const name = "hdd";
console(nam);
} catch (e) {
console.log("捕获到异常:", e);
}

// output:
// 捕获到异常: ReferenceError: nam is not defined
// at <anonymous>:3:15
  1. 无法捕获语法错误:(如删除一个单引号)
1
2
3
4
5
6
7
8
9
try {
const name = 'hdd
console(nam)
} catch (e) {
console.log('捕获到异常:', e)
}

// output:
// Uncaught SyntaxError: Invalid or unexpected token

语法错误在开发中就能看到,一般不会存活到线上。 但是代码运行在不兼容的环境,也可能存在语法错误,如 IE 浏览器。

  1. 异步错误:
1
2
3
4
5
6
7
8
9
10
11
try {
setTimeout(() => {
undefined.map((v) => v);
});
} catch (e) {
console.log("捕获到异常:", e);
}

// output:
// Uncaught TypeError: Cannot read property 'map' of undefined
// at <anonymous>:3:14

可以看到,try-catch 并无法捕获到异步异常。需要借助全局的错误监听了~ ​

GlobalEventHandlers.onerror

混合事件 onerror 属性的触发有两种情况(MDN):

  1. 当 JavaScript 运行时错误(包括语法错误)发生时,window 会触发一个 ErrorEvent 接口的 error 事件,并执行 window.onerror()。
  2. 当一项资源(如<img><script>)加载失败,加载资源的元素会触发一个 Event 接口的 error 事件,并执行该元素上的 onerror() 处理函数。这些 error 事件不会向上冒泡到 window,不过(至少在 Firefox 中)能被单一的 window.addEventListener 捕获。

window.onerror

window.onerror() 返回 true,则阻止默认事件处理函数。

1
2
3
4
5
6
7
8
/**
* @param {string} message 错误信息
* @param {string} source 发生错误的脚本URL
* @param {number} lineno 发生错误的行号
* @param {number} colno 发生错误的列号
* @param {Error} error Error 对象
*/
window.onerror = function(message, source, lineno, colno, error) { ... }

window.addEventListener('error')

1
2
3
4
5
6
7
/**
* @param {Error} event Error 对象
*/
window.addEventListener('error', function(event) { ... })

// 捕获模式捕获错误
window.addEventListener('error', function(event) { ... }, true)

element.onerror

1
2
3
4
/**
* @param {Error} event Error 对象
*/
element.onerror = function(event) { ... }

Promise Catch

没有写 catch 的 Promise 中抛出的错误,无法被 onerror 或 try-catch 捕获到,所以在使用 Promise 的时候,一定要记得 catch 处理抛出的异常。 ​

也可以全局增加一个 unhandledrejection 事件监听,来全局监听 Uncaught Promise Error。

1
2
3
4
5
window.addEventListener("unhandledrejection", function (e) {
console.log(e);
// 防止默认处理(例如将错误输出到控制台)
event.preventDefault();
});

iframe 异常

对于 iframe 的异常,我们还需要使用 window.onerror 去监听指定的 iframe:

1
2
3
4
5
6
7
8
9
10
11
12
13
<iframe src="./iframe.html" frameborder="0"></iframe>
<script>
window.frames[0].onerror = function (message, source, lineno, colno, error) {
console.log("捕获到 iframe 异常:", {
message,
source,
lineno,
colno,
error,
});
return true;
};
</script>

根据上面的例子可以看出,不同的场景下需要使用不同的错误收集方式。接下来,让我们看看 Sentry 的具体接入流程。

Sentry 接入流程

自动上报

无感知收集,配置 Sentry。主要利用了 Sentry 的 JavaScript SDK。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
isBuild &&
Sentry.init({
app,
dsn: "https://xxxxxxxxxxxxxxxxx@sentry.**.com/6",
integrations: [
new Integrations.BrowserTracing({
routingInstrumentation: Sentry.vueRouterInstrumentation(router),
}),
],
allowUrls: [/https:\/\/**.**.com.tw/],
maxBreadcrumbs: 20, // 面包屑最大长度
sampleRate: 0.1, // 采样率
tracesSampleRate: 0.1, // 上报率
logErrors: true,
attachProps: true,
beforeSend(event) {
// filter Rendertron Server
if (
event.request &&
event.request.headers &&
event.request.headers["User-Agent"].includes("Rendertron")
) {
return null;
}

// set userinfo (全局设置时,页面刷新会丢失id)
event.user = {
...event.user,
id: Cookies.get("**_TOKEN"),
};

return event;
},
});

手动上报

关键位置手动设置相关参数。

1
2
3
4
5
6
7
8
9
 Sentry.captureException('captureException');
Sentry.captureMessage('captureMessage');
// 设置用户信息:
scope.setUser({ "email": "xx@xx.cn"})
// 给事件定义标签:
scope.setTags({ 'api', 'api/ list / get'})
// 设置事件的严重性:
scope.setLevel('error')
...

Sentry 错误捕获原理

捕获流程可以分为全局捕获和单点捕获。 ​

全局捕获

代码集中,易于管理。

  • 全局接口
    • window.onerrorwindow.addEventListener('error')window.addEventListener('unhandledrejection')
  • 框架级别的全局监听
    • vue、react 都有自己的错误采集接口
    • Vue.config.errorHandler
    • 如 aixos 中使用 interceptor 进行拦截(全局接口拦截)
  • 在全局对函数进行包裹,实现在调用函数时自动捕获异常
    • setTimeoutsetIntervalrequestAnimationFrame

单点捕获

作为全局捕获的补充,对某些特殊情况进行捕获,但是分散且不利于管理。

  • 对单个代码块包裹,在逻辑中大点,有针对性的异常捕获,常使用try...catch
  • 专门的函数,用来收集异常,在异常发生时调用
    • 例如在 axios 通过请求,发生错误时候,通过 Sentry.caputureException() 主动上报
  • 专门的函数包裹其他函数,在有异常的时候上报

其他功能

性能监控

Sentry 提供了性能监控功能,主要是依靠了 web-vitals 包,想了解具体使用可以看看这篇文章。里面讲解了一下一些 web-vitals 的关键指数及相关的优化策略。这里就不太多展开。 ​

  • Largest Contentful Paint (LCP):最大内容绘制,测量加载性能。为了提供良好的用户体验,LCP 应在页面首次开始加载后的 2.5 秒内发生。
  • First Input Delay (FID):首次输入延迟,测量交互性。为了提供良好的用户体验,页面的 FID 应为 100 毫秒或更短。
  • Cumulative Layout Shift (CLS):累积布局偏移,测量视觉稳定性。为了提供良好的用户体验,页面的 CLS 应保持在 0.1. 或更少。

为了确保您能够在大部分用户的访问期间达成建议目标值,对于上述每项指标,一个良好的测量阈值为页面加载的第 75 个百分位数,且该阈值同时适用于移动和桌面设备。

如果一个页面满足上述全部三项指标建议目标值的第 75 个百分位数,那么评估核心 Web 指标合规性的工具应评判该页面为通过。

录制

Sentry 的屏幕录制主要依靠 rrweb 。大致流程为:

  • 首先保存一个一开始完整的 dom 的快照,然后为每一个节点生成一个唯一的 id。
  • 当 dom 变化的时候通过 MutationObserver 来监听具体是哪个 DOM 的哪个属性发生了什么变化,保存起来。
  • 监听页面的鼠标和键盘的交互事件来记录位置和交互信息,最后用来模拟实现用户的操作。
  • 然后通过内部写的解析方法来解析。
  • 通过渲染 dom,并用 RAF 来播放,就好像在看一个视频一样。

上传 sourceMap

上传了 sourceMap,能让我们快速定位错误所对应的源码 大致有三种方式

参考

评论

加载中,最新评论有1分钟延迟...