浅谈前端监控(一)

什么是前端监控,都监控哪些东西

顾名思义,前端监控是对前端项目部署上线以后的一些观察和收集行为,一方面主要收集前端的报错信息,另一方面可以对某些操作进行 埋点 记录一些用户行为,后期可以针对这些用户行为进行数据分析。下面详细说一下针对报错信息方面的监控


如何进行前端监控

总体思路是在全局注册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);
}
}

DEMO地址

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇