什么是前端监控,都监控哪些东西
顾名思义,前端监控是对前端项目部署上线以后的一些观察和收集行为,一方面主要收集前端的报错信息,另一方面可以对某些操作进行 埋点 记录一些用户行为,后期可以针对这些用户行为进行数据分析。下面详细说一下针对报错信息方面的监控
如何进行前端监控
总体思路是在全局注册error事件,对所有的错误事件进行拦截,获取拦截后的报错信息和用户及设备信息,将收集的信息上传到服务端。
前端监控的类别(一)—— 稳定性
错误名称 | 备注 |
---|---|
JS错误 | JS错误(使用error监听)和Promise错误(使用unhandledrejection监听) |
资源异常 | 捕获一些静态资源文件404的异常信息(使用error监听) |
接口异常 | 捕获 ajax 和 axios 的动向(原理都是 XMLHttpRequest) |
白屏监听 | 捕获页面是否白屏 |
收集JS报错信息
window.addEventListener("error",(event)=>{
log = {
kind:"stability",// 监控指标的大类
type:"error",// 小类型
errorType:"jsError",// JS执行错误
message:event.message, // 报错信息
filename:event.filename, // 报错文件
position:`${event.lineno}:${event.colno}`, // 报错行数
stack:getLines(event.error.stack), // 堆栈信息
}
},true)
收集 Promise 报错信息
收集 promise 错误 需要借助 unhandledrejection 事件, DMN官方解释当Promise被 reject 且没有 reject 处理器的时候触发。
window.addEventListener( "unhandledrejection",(event) => {
let lastEvent = getLastEvent();
let message;
let filename;
let line = 0;
let column = 0;
let stack = "";
let reason = event.reason;
if (typeof reason === "string") {
message = event.event;
} else if (typeof reason === "object") {
if (reason.stack) {
let matchResult = reason.stack.match(/at\s+(.+):(\d+):(\d+)/);
filename = matchResult[1];
line = matchResult[2];
column = matchResult[3];
}
stack = getLines(reason.stack);
}
let log = {
kind: "stability", // 监控指标的大类
type: "error", // 小类型
errorType: "promiseError", // promise错误
message: message, // 报错信息
filename: filename, // 报错文件
position: `${line}:${column}`, // 报错行数
stack: stack, // 堆栈信息
};
},true);
/*堆栈信息格式化*/
function getLines(stack) {
return stack
.split("\n")
.slice(1)
.map((item) => item.replace(/^\s+at\s+/g, ""))
.join("^");
}
资源 (404)异常捕获
window.addEventListener("error",(event)=>{
if(event.target && (event.target.src || event.target.href)){
log = {
kind:"stability",// 监控指标的大类
type:"error",// 小类型
errorType:"resourceError",// js 或 css资源加载错误
url:'', // 访问哪个路径报错
filename:event.target.src || event.target.href, // 报错文件
tagName:event.target.tagName//标签名
}
}
},true)
接口异常
export default function injectXHR(){
let XMLHttpRequest = window.XMLHttpRequest;
let oldOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method,url,async){
/**
* 在项目中需要过滤掉 监控接口的,否则接口监听会出现死循环
* if(url.match(/url/)){
* this.logData = {method,url,async}
* }
*/
this.logData = {method,url,async}
return oldOpen.apply(this,arguments)
}
let oldSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function(body){
if(this.logData){
let startTime = Date.now();
let handler = (type)=> (event)=>{
let duration = Date.now() - startTime;
let status = this.status; // 200 OR 500
let statusText = this.statusText; // ok OR Server Error
let log = {
kind:"stability",
type:'xhr',
eventType:type, // 错误类型 load error abort
pathname:this.logData.url, // 请求路径
status:status+"-"+statusText, // 状态码
duration:duration, // 持续时间
response:this.response?JSON.stringify(this.response):"", // 响应体
params:body || ""
}
console.log('请求接口监听上报',log)
}
this.addEventListener('load',handler("load"),false);
this.addEventListener("error",handler("error"),false)
this.addEventListener("abort",handler("abort"),false)
}
return oldSend.apply(this,arguments)
}
}
白屏异常捕获
elementsFromPoint 该函数返回在特定坐标点下的HTML元素的数组,接受两个参数(x坐标,y坐标)
export function blankScreen() {
let wrapperElements = ['html', 'body', '#container', '.content'];
let emptyPoints = 0;
function getSelector(element) {
if (element.id) {
return "#" + element.id;
} else if (element.className) {// a b c => .a.b.c
return "." + element.className.split(' ').filter(item => !!item).join('.');
} else {
return element.nodeName.toLowerCase();
}
}
function isWrapper(element) {
let selector = getSelector(element);
if (wrapperElements.indexOf(selector) != -1) {
emptyPoints++;
}
}
onload(function () {
for (let i = 1; i <= 9; i++) {
let xElements = document.elementsFromPoint(
window.innerWidth * i / 10, window.innerHeight / 2);
let yElements = document.elementsFromPoint(
window.innerWidth / 2, window.innerHeight * i / 10);
isWrapper(xElements[0]);
isWrapper(yElements[0]);
}
if (emptyPoints > 0) {
let centerElements = document.elementsFromPoint(
window.innerWidth / 2, window.innerHeight / 2
);
let log = {
kind: 'stability',
type: 'blank',
emptyPoints,
screen: window.screen.width + "X" + window.screen.height,
viewPoint: window.innerWidth + "X" + window.innerHeight,
selector: getSelector(centerElements[0])
};
console.log("白屏渲染",log)
}
});
}
// 页面加载完触发
function onload (callback) {
if (document.readyState === 'complete') {
callback();
} else {
window.addEventListener('load', callback);
}
}