这是我参与「第五届青训营 」笔记创作活动的第12天
课程重点
- 为什么要聊前端监控
- 前端监控之常用性能指标
- 前端监控之前端常见异常
- 封装一个通用的SDK
为什么要聊前端监控
通过对页面数据的采集上报,来帮助开发者更快速地对质量差的页面进行分析和归因。 性能不佳的页面会导致糟糕的用户体验,从而引起流量减少、转化率降低等业务负面影响。
监控方向
前端监控主要包括三个方向
- 性能指标
- 异常事件
- 用户行为
前端监控之常用性能指标
以用户为中心的性能指标:不拘泥于技术细节,而是从用户能理解能体验到的方面作为性能监控的指标。
加载流:
- FP(First Paint)
- FCP(First Contentful Paint)
- FMP(First Meaningful Paint)非标准化。
- TTI(Time to Interactive) 越小代表用户可以更早操作页面,用户体验也就越好。
LCP: 最大的内容在可视区域内变得可见的时间点。对用户来说更容易理解,更容易计算和上报。
TBT: 量化主线程在空闲前的繁忙程度,因为主线程繁忙时UI线程会被阻塞。
前端监控之前端常见异常
静态资源错误
Failed to load resource: the server responded with a status of 404 大多数时候是网络异常捏,404 not found
请求异常
404、403、500、502
特殊情况: status=0 请求无法执行
JS错误
有各种各样的原因,不少情况都来自于边缘状况的判断不足,使用TS能减少出现的次数。
白屏异常
通常通过判断DOM树结构来粗略判断白屏是否产生。
产生原因:
-
JS错误导致关键资源渲染失败
-
资源加载失败
-
JS线程过于繁忙
封装一个通用的SDK
按需加载监控能力
function createSdk(url: string) {
const monitors: Array<{ name: string, start: Function }> = [];
const sdk = {
url,
report,
loadMonitor,
monitors,
start,
}
function report({ name: string, data: any }) {
// 注意:数据发送前需要先序列化为字符串
navigator.sendBeacon(url, JSON.stringify({ name: string, data: any }));
}
function loadMonitor({ name: string, start: Function }) {
monitors.push({ name: string, start: Function });
// 实现链式调用
return sdk;
}
function start() {
monitors.forEach(m => m.start());
}
return sdk;
}
const sdk = createSdk("111.com");
const jsMonitor = createJsErrorMonitor(sdk.report);
sdk.loadMonitor(jsMonitor).loadMonitor(createPerfMonitor(sdk.report));
sdk.start();
throw (new Error('test'));
function createJsErrorMonitor(report: ({ name: string, data: any }) => void) {
const name = "js-error";
function start() {
window.addEventListener("error", (e) => {
// 只有 error 属性不为空的 ErrorEvent 才是一个合法的 js 错误
if (e.error) {
report({ name, data: { type: e.type, message: e.message } });
}
});
window.addEventListener("unhandledrejection", (e) => {
report({ name, data: { type: e.type, reason: e.reason } });
});
}
return { name, start }
}
function createPerfMonitor(report: ({ name: string, data: any }) => void) {
const name = 'performance';
const entryTypes = ['paint', 'largest-contentful-paint', 'first-input']
function start() {
const p = new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
report({ name, data: entry });
}
})
p.observe({ entryTypes });
}
return { name, start }
}