- 这是我参与第五届青训营伴学笔记创作活动的第14天
- 本文主要了解前端监控的概念和意义,包括前端监控的一些重要指标,包含了一些实战,实战部分写的可能不是很好。因为我自己也是云里雾里。主要还是了解一下前端监控的知识,以及各部分有什么作用。
前端监控
是什么

为什么
做什么
性能指标
- 早期Web就是纯静态 通过文档发起请求到各阶段的耗时作为性能指标
- 侧重于技术细节,难以反应用户的关心指标

- 以用户为中心的性能指标(FP FCP FMP LCP TTI FID CLS)

异常监控
静态资源错误
- 404 网络异常 等

请求异常
- Http请求码

- 请求成功率 = 请求成功 / (请求成功数 + 请求失败数)
Js错误
白屏异常
- 没有标准化监听方法
- 通常可以提供过判断DOM树的结构来粗略判断白屏是否发生。
- JS错误导致关键资源渲染失败
- 请求异常或静态资源加载失败
- 长时间JS线程阻塞
监控实战
性能(FP FCP LCP FID)
const entryTypes = ['paint', 'largest-contentful-panit', 'first-input']
const p = new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
console.log(entry)
}
})
p.observe({ entryTypes });
window.performance.getEntriesByType('paint')
window.performance.getEntriesByType('largest-contentful-panit')
function createPerfMOnitor(report:({name:string, data:any})=>void) {
const name = 'performance'
const entryTypes = ['paint', 'largest-contentful-panit', 'first-input']
function start() {
const p = new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
console.log(entry)
report({name, data:entry})
}
})
p.observe({ entryTypes });
}
return {name, start}
}
JS错误监控
window.addEventListener("error", (e)=> {
if (e.error) {
console.log('capture an error', e.error)
}
});
throw new Error("1111")
window.addEventListener("unhandlerejection", (e)=> {
console.log('capture unhandlerejection', e)
})
Promise.reject("2222")
function createJSErrorMOnitor(report:({name:string, data:any})=> void) {
const name = 'js-error'
function start() {
window.addEventListener("error", (e)=> {
if (e.error) {
console.log('capture an error', e.error)
report({name, data:{type:e.type, message:e.message}})
}
});
window.addEventListener("unhandlerejection", (e)=> {
console.log('capture unhandlerejection', e)
report({name, data:{type:e.type, reason:e.reason}})
})
}
return {name, start}
}
静态资源错误
window.addEventListener("error", (e)=> {
const target = e.target || e.srcElement;
if (!target) {
return
}
if (target instanceof HTMLElement) {
let url
if (target.tagName.toLocaleLowerCase() === 'link') {
url = target.getAttribute("href")
} else {
url = target.getAttribute("src")
}
console.log('异常', url)
}
}, true);
const link = document.createElement("link")
link.href = "1.css"
link.rel = "stylesheet"
document.head.append(link)
function createResourceMOnitor(report:({name:string, data:any})=> void) {
const name = 'resource'
function start() {
window.addEventListener("error", (e)=> {
const target = e.target || e.srcElement;
if (!target) {
return
}
if (target instanceof HTMLElement) {
let url
if (target.tagName.toLocaleLowerCase() === 'link') {
url = target.getAttribute("href")
} else {
url = target.getAttribute("src")
}
report({name, data: {url}})
}
}, true);
}
return {name, start}
}
请求错误
function hookMethod(obj: any, key: string, hookFunc: Function) {
return (...params: any[]) => {
obj[key] = hookFunc(obj[key], ...params);
}
}
hookMethod(XMLHttpRequest.prototype, 'open', (origin: Function) =>
function (this, method: string, url: string) {
this.payload = {
method,
url,
}
origin.apply(this, [method.url]);
}
)()
hookMethod(XMLHttpRequest.prototype, 'send', (origin: Function) =>
function (this, ...params: any[]) {
this.addEventListener("readystatechange", function () {
if (this.readState === 4 && this.status >= 400) {
this.payload.status = this.status
console.log(this.payload)
}
});
origin.apply(this, ...params);
}
)()
const xhr = new XMLHttpRequest();
xhr.open("post", "111.cc")
xhr.send()
function createJSErrorMOnitor(report:({name:string, data:any})=> void) {
const name = 'js-error'
function hookMethod(obj: any, key: string, hookFunc: Function) {
return (...params: any[]) => {
obj[key] = hookFunc(obj[key], ...params);
}
}
function start() {
hookMethod(XMLHttpRequest.prototype, 'open', (origin: Function) =>
function (this, method: string, url: string) {
this.payload = {
method,
url,
}
origin.apply(this, [method.url]);
}
)()
hookMethod(XMLHttpRequest.prototype, 'send', (origin: Function) =>
function (this, ...params: any[]) {
this.addEventListener("readystatechange", function () {
if (this.readState === 4 && this.status >= 400) {
this.payload.status = this.status
console.log(this.payload)
report({name, data:this.payload})
}
});
origin.apply(this, ...params);
}
)()
}
return {name, start}
}
封装通用SDK
function createSdk(url: String) {
const monitors: Array<{name:String, start:Function}> = []
const sdk = {
url,
monitors,
report,
loadMonitor,
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;
}
console.log("开始")
const sdk = createSdk("111.com")
const jsMonitor = createJSErrorMOnitor(sdk.reprot)
const perMonitor = createPerfMOnitor(sdk.reprot)
sdk.loadMonitor(jsMonitor).loadMOnitor(perMonitor).start()
throw Error("test")
function createJSErrorMOnitor(report:({ name: String, data :Any})=> void) {
const name = 'js-error'
function start() {
window.addEventListener("error", (e)=> {
if (e.error) {
console.log('capture an error', e.error)
report({name, data:{ type: e.type, message:e.message}})
}
});
window.addEventListener("unhandlerejection", (e)=> {
console.log('capture unhandlerejection', 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-panit', 'first-input']
function start() {
const p = new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
console.log(entry)
report({name, data:entry})
}
})
p.observe({ entryTypes });
}
return {name, start}
}