用户情景重现系统的构建思路

789 阅读6分钟

项目上线之后,自然会有用户在使用过程中,发现这样或者那样的问题。此时我们需要尽快的定位问题所在,为此可能需要客服、前端、后端、产品等多个团队的同事一起重现和排查,响应速度难免不够理想(特别是非工作时间),那有什么办法可以提高排查速度,尽量减少不必要的人参与呢?这就是我想要构建用户情景重现系统的初衷。

Web前端应用的组成要素

开始之前,先看一下,Web前端应用是有3个要素组成:

  • 逻辑
  • 数据
  • 交互

逻辑

就是我们的Web前端代码三剑客——HTML、CSS、JS,以及其他的静态资源,例如图片、字体、媒体等。

数据

顾名思义,就是服务器返回的数据。

交互

用户在浏览器进行的交互,诸如鼠标的点击、滚动,键盘的输入等。

生命周期

这三者构成了一个Web前端应用的生命周期,

  1. 浏览器加载代码逻辑
  2. 代码逻辑请求获取服务器数据,再根据代码逻辑把相关内容呈现
  3. 用户交互触发
  4. 代码逻辑运行,改变展示内容或触发第1、2步
sequenceDiagram
代码逻辑->>浏览器: 1.页面及静态资源加载
服务器数据->>浏览器: 2.请求获取
用户交互-->>浏览器: 3.键盘/鼠标操作
浏览器->>浏览器: 4.运行已加载的逻辑

整体思路

理清楚Web前端应用的本质之后,我们开始思考要如何构建这个系统。

首先,因为前端代码逻辑&静态资源一般我们都由前端开发同学所掌握,那我们只要确定剩下的两个要素(数据交互)在情景重现中需要的数据包括哪些:

  • 接口的请求&返回
    • ajax(get/post)
    • jsonp
    • fetch
  • 用户行为
    • 点击事件
    • 输入事件
    • 复制/粘贴事件
    • 键盘按键事件
    • 鼠标滚动事件
    • ……

然后,我们需要把这些数据以特定的格式上报到服务器数据库,用作情景重现使用。

接着,我们需要管理后台,对接入的应用(项目)、以及应用中的情景进行管理。

最后,我们需要根据前面获取的数据把情景重现。

总而言之,项目整体分为三部分:

  • 项目接入
  • 管理后台(包括数据存储和重现的后端处理逻辑)
  • 情景重现

Screen Shot 2021-06-11 at 1.24.42 AM.png

项目接入部分

接口数据上报

  1. 针对ajax,我们通过更改XMLHttpRequest的原型方法open(),获取请求方法、地址、参数,以及通过添加load事件监听获取返回数据,并把这些进行上报。
var xhr_proto = window.XMLHttpRequest.prototype

xhr_proto._originOpen = xhr_proto.open;
xhr_proto.open = function (method, url, async, user, password) {
  this.addEventListener('load', () => {
    // TODO: 请求返回数据处理&上传
  })
  xhr_proto._originOpen.call(this, method, url, async, user, password);
}
  1. fetch API,我们通过重写window.fetch方法,在原来的fetch API的resolve和reject的时候,进行数据上报。

需要注意的是,fetch API返回的Response对象只能读取一次,所以需要调用Response.clone()获取一个克隆对象去读取内容。

  1. JSONP,我们使用MutationObserver监听添加到DOM树上的script标签,并且拆解src地址的参数,查看那个参数在window对象上有同名函数,然后替换掉该函数进行上报。

用户行为上报

在DOM树的根结点监听事件,并修改事件触发为捕捉方式。在回调函数中,上报触发事件的具体Node节点。别忘了带上时间戳才能更好的重现。

document.addEventListener(‘click’, listener, true);

管理后台

基本就是管理后台的常见模块,包括但不限于登录、权限管理等,以及我们要接入的项目、项目下的情景生成(根据用户id、时间段等)。

另外我们还需要对上报的数据进行存储,以及情景生成和重现时获取数据,甚至需要进行自定义数据(mock数据)。

情景重现

首先,当代码逻辑会持续迭代,我们如果代码使用增量发布的话,在项目接入部分我们还需要上报代码的时间戳/md5戳,方便我们重现时加载正确版本的代码。

  • 用户行为数据,我们采用页面注入的方式(直接注入数据或注入请求获取)。
  • 接口数据,我们可以考虑两种形式:
    • 采取类似用户行为数据的方式,在页面加载时获取全部的接口数据
    • 采取真实的请求发送方式获取数据

但其实无论哪一种,都需要像接口上报时一样对数据请求的方法进行hack,或直接本地返回数据,或修改数据请求的地址到本系统的服务器。

  • 播放和暂停,根据数据上报带上的时间戳,去进行播放,同时也允许暂停。

局限性/待完善

基本到这里我们的 用户情景重现系统 就完成了。但是,这个系统也不是所有场景都能覆盖,还是有一定的局限性或者待完善部分。

  • SSR(服务器渲染)项目除了直接在页面加载后把整个页面内容上传,暂时没有更好的解决方案。
  • MPA(多页面应用)跳转/刷新页面的场景只能通过多个情景来重现,连贯性不够好。
  • 涉及到iframe的页面跟上面那点类似,甚至如果某些iframe的内容和外面的页面有交互的话,暂时无法支持。
  • 代码逻辑需要用到cookie、本地存储(LocalStorage等),支持得也不是很好,需要额外的处理。
  • WebSocket、WebWorker部分待完善,目前我们的项目用得较少。

解决了什么

有了这套系统之后,能解决我们的痛点包括:

  1. 线上故障快速重现定位,缩短定位问题时需要找齐各方才能开展的痛点,即使只有不懂技术的同事(客服、产品、运营等)或者不负责该项目的同事,都能迅速了解到问题出在哪。
  2. 一些事务性的功能,执行过后就无法或很难复现的业务场景,能非常方便地掌握当时用户的情景。
  3. 前端页面自测,在接口不变的前提下,可以作为mock平台,测试前端代码,特别是一些技术优化改造的场景,或者是出现条件苛刻的某些路径。
  4. 当项目逐渐复杂和长期迭代后,新参与的同事(无论产品、研发、测试等)可以利用积累的接口数据,快速了解功能。