前端监控
概述
监控分为异常监控和性能监控,用于主动收集线上异常情况和性能数据。
相关第三方产品:
异常监控
JS 错误
监控代码
window.addEventListener(
"error",
(e) => {
if (e instanceof ErrorEvent) {
console.log({
type: "jsError",
message: e.message, // 错误信息
filename: e.filename, // 错误发生的文件
lineno: e.lineno, // 错误发生的行号
colno: e.colno, // 错误发生的列好
stack: e.error.stack, // 错误的栈信息
});
}
},
true
);
触发示例
// SyntaxError 语法错误
{k:};
const a;
let b = 1, b = 2;
// ReferenceError 引用错误
notdefined();
// RangeError 范围错误
[].length = -1;
// TypeError 类型错误
new 1;
const a = 1;a = 2;
// URIError URI错误
decodeURI("%");
注意
无法捕获 SyntaxError,因为语法错误出现在解析阶段,还未到运行阶段。
资源加载失败
监控代码
window.addEventListener(
"error",
(e) => {
let url = e.target ? e.target.src || e.target.href : null;
if (url) {
console.log({
type: "resourceError",
url, // 资源链接
html: e.target.outerHTML, // 资源元素html
tagName: e.target.tagName, // 资源元素标签名
});
}
},
true
);
触发示例
<script src="https://test.cn/xxx.js"></script>
<link href="https://test.cn/xxx.css" rel="stylesheet" />
<img src="https://test.cn/xxx.png" />
Promise 未处理 reject
监控代码
window.addEventListener("unhandledrejection", function (e) {
console.log({
type: "promiseError",
reason: e.reason, // reject原因
});
});
触发示例
new Promise((resolve, reject) => {
reject("拒绝");
});
Vue 错误
监控代码
app.config.errorHandler = (error, instance, hook) => {
console.log({
subType: "vueError",
message: error.message, // 错误信息
stack: error.stack, // 错误的栈信息
component: instance.$options.name, // 发生错误的组件
hook, // 发生错误的钩子
});
};
触发示例
const app = Vue.createApp({
name: "root",
created() {
[].length = -1;
},
});
document.addEventListener("DOMContentLoaded", () => {
app.mount("#app");
});
性能监控
TTFB
Time to First Byte,接收到服务器响应数据第一个字节的时间。TTFB 包含了 DNS、TCP建立、发送请求、服务器处理、接收响应的过程,充分反映网络情况与服务器性能。良好的 TTFB 应该在控制在 800ms 以内。
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.responseStart);
}
}).observe({ type: "navigation" });
(entry 类型为 PerformanceNavigationTiming 类)
DCL
DOMContentLoaded 事件时间,此时文档解析完成(包含同步加载解析/执行 Css 和 JS),document.readyState = interactive,触发 document 的 DOMContentLoaded 事件。
DCL 之后便是开始渲染页面并加载外部资源,故 DCL 略小于 FP。
// 方式1:监听 navigation 的 domContentLoadedEventEnd 指标
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.domContentLoadedEventEnd);
}
}).observe({ type: "navigation" });
// 方式2:监听 DOMContentLoaded 事件
document.addEventListener("DOMContentLoaded", ()=>{
console.log(performance.now())
})
(entry 类型为 PerformanceNavigationTiming 类)
FP
First Paint,首个任意像素渲染完成的时间,又称白屏时间,即在此时间之前都是白屏。
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if(entry.name == "first-paint") {
console.log(entry.startTime);
}
}
}).observe({ type: "paint", buffered: true });
(entry 类型为 PerformancePaintTiming 类)
FCP
First Contentful Paint,首个任意内容渲染完成的时间,在此时间之前都视为无内容。内容包含文本、图片、背景图、svg、canvas。
监听 FCP:
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if(entry.name == "first-contentful-paint") {
console.log(entry.startTime);
}
}
}).observe({ type: "paint", buffered: true });
(entry 类型为 PerformancePaintTiming 类)
FP 和 FCP 能反映当前页面的网络加载性能情况、DOM 结构复杂度情况、Script 的执行效率情况。良好的 FP / FCP 应该控制在 1.8s 以内。正常情况下测的 FP 和 FCP 的值基本一样,可以通过下面代码验证两者区别。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script>
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry);
}
}).observe({ type: "paint", buffered: true });
</script>
<style>
body {
/* 设置背景色,触发 FP */
background: gainsboro;
}
</style>
</head>
<body>
<!-- 不属于内容,不会触发 FCP -->
<h1></h1>
<!-- 直接触发 FCP,此时 FP = FCP -->
<!-- <h1>hello world<h1> -->
<script>
// 3s后再添加内容,触发 FCP,此时 FCP > FP
setTimeout(() => {
let h1 = document.createElement("h1");
h1.innerText = "你好";
document.body.append(h1);
}, 3000);
</script>
</body>
</html>
LCP
Largest Contentful Paint,最大内容绘制时间,良好的 LCP 应该控制在 2.5s 以内。LCP 计算的元素范围:
<img>
元素;- 内嵌在
<svg>
元素内的<image>
元素; <video>
元素(使用封面图像);- 通过
url()
函数(而非使用 CSS 渐变)加载的带有背景图像的元素; - 包含文本节点或其他行内级文本元素子元素的块级元素;
初次创建 LCP 应该是在页面初始资源加载完成,即 window 的 load 事件触发时(个人推测)。 当后续有更大内容出现时会再次创建一个新的 LCP,即可能存在多个 LCP,直至用户与页面产生第一次交互时停止监听。
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.startTime);
}
}).observe({ type: "first-contentful-paint", buffered: true });
(entry 类型为 LargestContentfulPaint 类)
L
Load 事件时间,可称为首屏时间,此时文档加载完所有外部资源(图片、视频、iframe 等),document.readyState = complete,触发 window 的 load 事件。
// 方式1:监听 navigation 的 loadEventEnd 指标
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.loadEventEnd);
}
}).observe({ type: "navigation" });
// 方式2:监听 load 事件
window.addEventListener("load", ()=>{
console.log(performance.now())
})
(entry 类型为 PerformanceNavigationTiming 类)
CLS
Cumulative Layout Shift,累计布局偏移,页面元素因动态改变或异步资源加载而发生的位移,是衡量视觉稳定性的核心指标。良好的 CLS 应该控制在 0.1 以内。
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.value);
}
}).observe({ type: "layout-shift", buffered: true });
(entry 类型为 LayoutShift 类)
FID
First Input Delay,首次交互响应延迟时间,良好的 FID 应该控制在 100ms 以内。
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.processingStart - entry.startTime);
}
}).observe({ type: "first-input", buffered: true });
(entry 类型为 PerformanceEventTiming 类)
INP
Interaction to Next Paint,交互至下一次绘制时间,良好的 FID 应该控制在 200ms 以内。
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.duration);
}
}).observe({ type: "event", buffered: true });
(entry 类型为 PerformanceEventTiming 类)
Resource
资源加载信息,包含页面中的所有请求的相关信息。
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry);
}
}).observe({ type: "resource", buffered: true });
(entry 类型为 PerformanceResourceTiming 类)
Lighthouse
Lighthouse(灯塔)是谷歌浏览器中一个页面审核工具,提供针对性能、无障碍、最佳做法、SEO 四个方面的审核,生成对应的分数及相关建议。
其中性能方面的分数由 5 个指标构成:
- First Contentful Paint(FCP):10%
- Largest Contentful Paint(LCP):25%
- Cumulative Layout Shift(CLS):25%
- Speed Index(SI):10%
- Total Blocking Time(TBT):30%
SI
Speed Index,速度指数,衡量在网页加载期间内容直观地显示的速度,良好的速度指数应该在 3.4s 以内。
TTI
Time to Interactive,页面第一次完全达到可交互状态的时间,需要满足以下条件:
- 从 FCP 指标后开始计算;
- 持续 5 秒内无长任务(执行时间超过 50ms)且无两个以上正在进行中的 GET 请求;
- 往前回溯至 5 秒前的最后一个长任务结束的时间。
TTI 已经从 Lighthouse 的性能指标中移除,并使用更好的 TBT 代替。
比如第一种情况在 10s 内有三个 51ms 的长任务,和第二种情况一个 10s 的长任务,TTI 都是 10s,而这两种情况的性能截然不同。反观 TBT,第一种情况为 3ms,第二种情况为 9950ms,更好的反映页面性能。
TBT
Total Blocking Time, 页面阻塞总时长。计算方法是:将 FCP 和 TTI 之间所有长任务(执行时间超过 50ms)的阻塞部分相加。阻塞部分指 50ms 之后的时间,如时长为 70ms 的长任务,阻塞部分将为 20ms。良好的 TBT 应该在 200ms 以内。