热搜:前端 nest neovim nvim

前端监控指南

lxf2023-05-14 01:32:27

本篇文章分享来自小伙伴「ziqi」的一次学习总结分享,希望跟社区的同学一起探讨。

一、前端监控的目的

为什么要进行前端监控?

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

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

(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 事件处理函数可用于自动收集错误报告。

/**
 * @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>
<img src="https://yun.tuia.cn/image/kkk.png">
// 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>
<img src="https://yun.tuia.cn/image/kkk.png">

// 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 <h1>Something went wrong.</h1>;
    }

    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 接口上报更加保险。

四、可视化分析

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

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

本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!