前端也需要监控?技术人不可忽视的前端监控最全指南

作者:鲸腾 FE

一、前端监控的目的

为什么要进行前端监控?

首先我们要确定,异常是不可控的,并不能确定什么时候或者什么场景会发生异常,但它却会实实在在的影响用户体现。所以,我们非常有必要去做这样一件事情,去了解用户的行为,以用户数据为基础,来指导我们产品的优化方向。

前端的监控主要可以分为三大类:数据监控、性能监控和异常监控。下面我们来详细解读一下:

(1)数据监控

数据监控即我们常说的埋点统计,用于监听用户的操作行为。常见的数据监控包括:

  • PV / UV:PV 即 Page View ,也就是页面的浏览数量,没打开页面一次就会统计一次;UV 即 User View ,也就是不同用户访问的次数,在 PV 的基础上根据 User 信息的不同做了去重操作;
  • 用户在每个页面停留的时间信息。即从用户打开该页面到用户离开该页面的时间差,用于表示该页面对用户的留存程度;
  • 用户的来处。即从什么入口或什么渠道来到了当前页面,通常会在 URL 中添加查询参数来做区分统计;
  • 用户的页面操作行为。即用户在该页面点击了哪些按钮,或者从什么链接去到了某些页面等等,来分析用户的去向。

(2)性能监控

性能监控主要是监听前端项目在用户端展示的性能,这将直接关乎到用户的体验效果。监控这些数据,将能够是我们更好的去优化用户体验。常见的性能监控指标包括以下数据:

  • 不同用户和不同设备下的首屏加载时间,包括白屏时间;
  • HTTP 接口的响应时间;
  • 静态资源、包括图片的下载时间;

(3)错误监控

错误监控主要是指代码在个别特殊场景下,会出现异常报错,很有可能会引发线上的故障。而这部分异常,如果没有异常监控,则只能在用户发现的情况下进行选择性的上报,并不能让我们及时去发现和解决问题。这一部分的错误信息主要分为下面几类:

  • JS 代码运行错误、语法错误等;
  • AJAX 请求错误;
  • 静态资源加载错误;
  • Promise 异步函数错误;

二、错误信息采集

错误信息

错误信息监控简单来说就是要搜集报错信息的发生的位置,以及报错的类型,进行上报,便于我们能够更好的掌握错误信息,从而能够对症下药。按照 5W1H 的法则来说,我们需要关注以下的几项信息:

  • What ,发生了什么错误:语法错误、类型错误、数据错误、逻辑错误等;
  • When ,什么时间发生的错误,可带上时间戳进行上报;
  • Who ,哪个用户或者哪一类用户发生了错误,包括用户 ID 、设备信息、IP 信息等;
  • Where ,哪个项目、哪些页面发生错误,可以上报页面的 URL 以及代码报错行数等信息;
  • Why ,为什么会发生错误,也就是用户在什么样的场景下发生的错误,便于问题复现;
  • How ,根据以上的信息如何进行问题的定位,然后怎么处理并解决问题;

错误类型

SyntaxError :语法解析错误;

const a,

// Uncaught SyntaxError: Missing initializer in const declaration

· TypeError :类型错误;

const obj = null;

console.log(obj.name);

// Uncaught TypeError: Cannot read properties of null (reading 'name')

· ReferenceError :引用错误;

console.log(notDefined);

// Uncaught ReferenceError: notDefined is not defined

错误捕获

常见的错误捕获方法主要是 try / catch 、window.onerror 和window.addEventListener 等。

try / catch

这是我们在代码调试的过程中最常用的一个方式,但它只能捕获代码常规的运行错误,语法错误和异步错误并能捕获到。

// 常规运行时错误,可以捕获 ✅

try {
    
    

console.log(notdefined);

} catch(e) {
    
    

console.log('捕获到异常:', 'ReferenceError');

}

// 语法错误,不能捕获 ❌

try {
    
    

const notdefined,

} catch(e) {
    
    

console.log('捕获不到异常:', 'Uncaught SyntaxError');

}

// 异步错误,不能捕获 ❌

try {
    
    

setTimeout(() => {
    
    


console.log(notdefined);


}, 0)

} catch(e) {
    
    

console.log('捕获不到异常:', 'Uncaught ReferenceError');

}

window.onerror

当 JS 运行时错误发生时,window 会触发一个 ErrorEvent 接口的 error 事件,并执行 window.onerror() 。

加载一个全局的 error 事件处理函数可用于自动收集错误报告。


```javascript
/**

* @param { string } message 错误信息
* @param { string } source 发生错误的脚本URL
* @param { number } lineno 发生错误的行号
* @param { number } colno 发生错误的列号
* @param { object } error Error对象

*/

window.onerror = function(message, source, lineno, colno, error) {
    
    

console.log('捕获到的错误信息是:', message, source, lineno, colno, error )

}

// 常规运行时错误,可以捕获 ✅

window.onerror = function(message, source, lineno, colno, error) {
    
    

console.log('捕获到异常:',{
    
    message, source, lineno, colno, error});

}

console.log(notdefined);

// message: "Uncaught ReferenceError: notdefined is not defined"

// source: "file:///C:/Users/qinzq42866/Desktop/error.html"

// lineno: 14

// colno: 19

// error: ReferenceError: notdefined is not defined at file

// 语法错误,不能捕获 ❌

window.onerror = function(message, source, lineno, colno, error) {
    
    

console.log('未捕获到异常:',{
    
    message, source, lineno, colno, error});

}

const notdefined,

// Uncaught SyntaxError: Missing initializer in const declaration

// 异步错误,可以捕获 ✅

window.onerror = function(message, source, lineno, colno, error) {
    
    

console.log('捕获到异常:',{
    
    message, source, lineno, colno, error});

}

setTimeout(() => {
    
    

console.log(notdefined);

}, 0)

// message: "Uncaught ReferenceError: notdefined is not defined"

// source: "file:///C:/Users/qinzq42866/Desktop/error.html"

// lineno: 15

// colno: 21

// error: ReferenceError: notdefined is not defined at file

// 资源错误,不能捕获 ❌

<script>

window.onerror = function(message, source, lineno, colno, error) {
    
    

console.log('捕获到异常:',{
    
    message, source, lineno, colno, error});

}

</script>

// GET https://yun.tuia.cn/image/kkk.png 404 (Not Found)

最后需要补充的是:window.onerror 函数只有在返回 true 的时候,异常才不会向上抛出,否则即使是知道异常的发生,控制台还是会显示 Uncaught Error 。

window.addEventListener

当一项静态资源加载失败时,加载资源的元素会触发一个 Event 接口的 Error 事件,这些 Error 事件不会向上冒泡到 window ,但能被捕获。而 window.onerror 不能检测捕获。

// 图片、script、css加载错误,都能被捕获 ✅


<script>

  window.addEventListener('error', (error) => {
    
    

     console.log('捕获到异常:', error);

  }, true)

</script>


// fetch错误,不能捕获 ❌


<script>

  window.addEventListener('error', (error) => {
    
    

    console.log('未捕获到异常:', error);

  }, true)

</script


<script>

  fetch('https://tuia.cn/test')

</script>

由于网络请求异常不会发生事件冒泡,因此必须在事件捕获的阶段将其捕捉到才行,这种方式虽然能够捕捉到网络请求的异常,但是却无法判断 HTTP 的状态,因此仍然需要配合服务端的日志进行配合分析。

需要注意的是:不同浏览器下返回的 Error 对象是不一样的,需要做兼容处理。

Promise 错误

没有写 catch 的 Promise 中抛出的错误是无法被 onerror 或 try / catch 捕获到的,这也是为什么我们一定要在 Promise 后面加上 catch 去捕获和处理异常。

为了防止有漏掉的 Promise 异常信息,建议在全局增加一个对 unhandledrejection 的监听,用来全局监听 Uncaught Promise Error 。

说明:当 Promise 被 reject 且没有 reject 处理器的时候,会触发 unhandledrejection 事件;这可能发生在 window 下,但也可能发生在 Worker 中。 这对于调试回退错误处理非常有用。

window.addEventListener("unhandledrejection", event => {
    
    

console.warn('UNHANDLED PROMISE REJECTION:', ${
    
    event.reason});

});

window.onunhandledrejection = event => {
    
    

console.warn('UNHANDLED PROMISE REJECTION:', ${
    
    event.reason});

};

window.addEventListener("unhandledrejection", function(e){
    
    

e.preventDefault()

console.log('捕获到异常:', e);

});

Promise.reject('promise error');

说明:如果去掉控制台的异常显示,需要加上 event.preventDefault() ;

Vue 错误

由于 Vue 会捕获到所有 Vue 单文件组件或者 Vue.extend 继承的代码,所以在 Vue 里面出现的错误并不会直接抛给 window.onerror ,而是会被 Vue 自身的 Vue.config.errorHandler 捕获。

Vue.config.errorHandler = (err, vm, info) => {
    
    

console.error('通过vue errorHandler捕获的错误');

console.error(err);

console.error(vm);

console.error(info);

}

React 错误

React 16 提供了一个内置函数 componentDidCatch ,使用它可以轻松的捕获到 React 组件内部抛出的错误信息。


class ErrorBoundary extends React.Component {
    
    
  constructor(props) {
    
    
    super(props);
    this.state = {
    
     hasError: false };
  }

  componentDidCatch(error, errorInfo) {
    
    
    console.log('捕获到错误:', error, errorInfo);
  }

  render() {
    
    
    if (this.state.hasError) {
    
    
      return `Something went wrong.`;
    }

    return this.props.children; 
  }
}

三、上报方式

通常来说,上报方式可以分两种:一种是通过 Img 方式上报,另一种是 AJAX 接口上报。

Img上报

这种方式非常的简单快捷,也是最推荐的一种方式。该方式通过动态的创建一个 Img 标签的方式,向服务器请求资源,然后把上报信息通过查询参数的方式带到请求的后面。至于请求的图片资源,为了节省流量和响应速度,我们可以请求一个 1×1 的透明 GIF 图片。这样一来不会产生跨域的问题,而且不需要等待服务器返回的数据,只要进行上报即可,非常的简单。

function report(error) {
    
    

let reportUrl = 'http://jartto.wang/report';

new Image().src = `${
      
      reportUrl}?logs=${
      
      error}`;

}

但是,也有相应的弊端。由于 URL 的长度有限,所以携带的查询参数并不是无限的,因此需要筛选有用的信息进行上报,而不是无限的长度。

AJAX 接口上报

由于这种方式本身也是一个请求,存在发生异常的情况。而且,通常来说上报接口的域名和业务列域名是不同的,因此还会存在跨域的问题需要处理。必须要等到接口响应后状态为 200 才能确定这次信息上报是成功的。

当然,AJAX 接口上报也有其自身的优点,那就可以携带的参数更大,而且还可以默认携带 Token 等信息。

总结

综上所述,在上报参数长度可控的情况下还是更推荐通过 Img 方式进行监控信息上报。如果上报的数据量较大,不可控的情况下,使用 AJAX 接口上报更加保险。

四、可视化分析

当服务端收集到相应的监控信息时,需要对数据进行持久化存储。落到数据库里的数据,对于运营人员和开发人员来说,并不是十分友好的。我们仍然需要一个后台系统,通过查询数据库里上报的监控信息,利用图表等行为将数据更好的展现出来,可以更好的让运营人员分析用户行为,从而更好的方向去优化我们的产品,当然也能够让开发人员更好分析和处理问题。

当然,对于线上的生产问题,服务端收到错误信息时,也可以通过短信或者微信通知等方式告知对应的开发人员,便于开发能够及时的修复线上问题。

猜你喜欢

转载自blog.csdn.net/weixin_44433834/article/details/123247194