前端监控:用户行为/性能监控/异常捕获上报

1,682 阅读6分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。详情

前端监控一般主要就是三种:页面埋点、性能监控、异常监控。

用户行为监控

页面埋点应该是最经常使用的监控方式,可以监听用户的行为,主要设计以下数据:

  • PV / UV(浏览量 / 不同IP地址的人数)
  • 停留时长
  • 流量来源
  • 用户交互

统计这些数据是有意义的,比如我们知道了用户来源的渠道,可以促进产品的推广,知道用户在每一个页面停留的时间,可以针对停留较长的页面,增加广告推送等等。

对于这几类统计,一般的实现思路大致可以分为两种:

  1. 手写埋点方式。

    这一种是比较常用的方式,可以自主选择需要监控的数据然后在相应的地方写入代码。优点:灵活性大。缺点:工作量大,需要监控的地方都要插入埋点代码。

  2. 无埋点的方式。

    这种无埋点的方式基本不需要开发者手写埋点了,而是统计所有的事件并且定时上报。这种方式虽然没有前一种方式繁琐了,但是因为统计的是所有事件,所以还需要后期过滤出需要的数据。

性能监控

性能监控指的是监听前端页面的性能,主要包括监听网页或者说产品在用户端的体验。

根据性能监控的结果可以优化产品。常见的性能监控数据包括:

  • 不同用户,不同机型和不同系统下的首屏加载时间
  • 白屏时间
  • http等请求的响应时间
  • 静态资源整体下载时间
  • 页面渲染时间
  • 页面交互动画完成时间

监控指标具体可以分类为:网络层面和页面展示层面。

  1. 从网络层面来看涉及的指标有:重定向耗时、DNS解析耗时、TCP连接耗时、SSL耗时、TTFB网络请求耗时、数据传输耗时、资源加载耗时等

  2. 页面展示层面的指标是针对用户体验提出的几个指标,包含FP、FCP、LCP、FMP、DCL、L等,这几个指标其实就是chrome浏览器中performance模块的指标(如图所示)。

image-20220222210041184.png

性能监控怎么做?使用浏览器自带的 Performance API 就可以实现这个功能。

image-20220222205935010.png

window.performance中的timing属性中的内容不就是为了求解上述指标所需要的值吗?看着上面的属性值再对应下面的performance访问流程图,整个过程是不是一目了然。

image-20220222200201532.png

异常监控

前端错误分类

前端错误主要包含两类:

  • 即时运行错误(代码错误)
  • 资源加载错误

捕获方式

1、try...catch

try {
    console.log('1->begin')
    error
    console.log('2->begin')
} catch (e) {
    console.log('catch',e)
}

当使用try...catch后,控制台就不再爆红。错误被捕获

image-20220222201401626.png

但是try catch对于异步的捕获就不生效了

try {
  setTimeout(() => {
    console.log('1')
    error
    console.log('2')
  })
} catch (e) {
  console.log('catch', e)
}

上面这种情况是无法捕获错误的。

2、window.onerror

window.onerror函数。这个函数是全局的。同步异步的错误都可以捕获

window.onerror = function(msg, url, row, col, error) { ... }
// msg为异常基本信息
// url为发生异常`Javascript`文件的`url`
// row为发生错误的行号
function fun1() {
  console.log('1->begin')
  error
  console.log('1->end')
}
window.onerror = (...args) => {
  console.log('onerror:',args)
}

需要注意一个问题:onerror返回值

如果返回返回true 就不会被上抛了。不然控制台中还会看到错误日志。

问题延伸1:

window.onerror默认无法捕获跨域js运行错误。捕获出来的信息如下:(基本属于无效信息)

image-20220222202445042.png

问题延申2:

window.onerror只能捕获即时运行错误,无法捕获资源加载错误。原理是:资源加载错误,并不会向上冒泡,object.onerror捕获后就会终止(不会冒泡给window),所以window.onerror并不能捕获资源加载错误。

3、监听error事件

源加载错误,虽然会阻止冒泡,但是不会阻止捕获。我们可以在捕获阶段绑定error事件。

window.addEventListener('error',() => {}
window.addEventListener('error', args => {
    console.log(
      'error event:', args
    );
    return true;
  }, 
  true // 利用捕获方式
);

资源加载错误问题

  • 方式1object.onerrorimg标签、script标签等节点都可以添加onerror事件,用来捕获资源加载的错误。

  • 方式2:performance.getEntries。可以获取所有已加载资源的加载时长,通过这种方式,可以间接的拿到没有加载的资源错误。

    // 已经成功加载的资源
    performance.getEntries().forEach(item=>{console.log(item.name)})
    // 需要加载的的img集合。
    document.getElementsByTagName('img')
    

    于是,document.getElementsByTagName('img')获取的资源数组减去通过performance.getEntries()获取的资源数组,剩下的就是没有成功加载的,这种方式可以间接捕获到资源加载错误。

  • 方式3: Error事件捕获。

4、Promise和async/await异常捕获

Promise有catch的方法可以捕获自身的错误。但是每个Promise都协商catch显然不太可能。所以我们可以用unhandledrejection来统一处理

window.addEventListener("unhandledrejection", e => {
  throw e.reason
});

async/await方法同理可以用unhandledrejection统一处理。

总结

异常类型同步方法异步方法资源加载Promiseasync/await
try/catch✔️✔️
onerror✔️✔️
error事件监听✔️✔️✔️
unhandledrejection事件监听✔️✔️

工程化项目中的异常捕获

跨域错误

Vue中

我们可以利用vue提供的handleError方法。一旦Vue发生异常都会调用这个方法。在src/main文件中定义错误捕获:

Vue.config.errorHandler = function (err, vm, info) {
  console.log('errorHandle:', err)
}

React中

1、我们可以在src/index.js中利用error事件监听:

window.addEventListener('error', args => {
    console.log('error', error)
})

2、ErrorBoundary标签

错误边界仅可以捕获其子组件的错误。错误边界无法捕获其自身的错误。如果一个错误边界无法渲染错误信息,则错误会向上冒泡至最接近的错误边界。这也类似于 JavaScript 中 catch {} 的工作机制。

① 创建ErrorBoundary组件

import React from 'react'; 
export default class ErrorBoundary extends React.Component {
    constructor(props) {
      super(props);
    }
  
    componentDidCatch(error, info) {
      // 发生异常时打印错误
      console.log('componentDidCatch',error)
    }
  
    render() {
      return this.props.children;
    }
  }

② 在src/index.js中包裹App标签

import ErrorBoundary from './ErrorBoundary'

ReactDOM.render(
    <ErrorBoundary>
        <App />
    </ErrorBoundary>
    , document.getElementById('root'));

异常上报方式

  • 方式一:采用Ajax通信的方式上报

    这和我们再业务程序中并没有什么区别。在这里就不赘述。(此方式虽然可以上报错误,但是我们并不采用这种方式)

  • 方式二: 利用Image对象上报(推荐。网站的监控体系都是采用的这种方式)

方式二:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<script>
    //通过Image对象进行错误上报
    (new Image()).src = 'http://blog.com/myPath?badjs=msg';   // myPath表示上报的路径(我要上报到哪里去)。后面的内容是自己加的参数。
</script>
</body>
</html>

这种方式,不需要借助第三方的库,一行代码即可搞定。

关于前端监控具体实现可以参考扩展:

从0到1搭建前端异常监控系统(Vue + Webpack + Node.js + Egg.js + Jest)

一文彻底搞懂前端监控