项目上线之后,自然会有用户在使用过程中,发现这样或者那样的问题。此时我们需要尽快的定位问题所在,为此可能需要客服、前端、后端、产品等多个团队的同事一起重现和排查,响应速度难免不够理想(特别是非工作时间),那有什么办法可以提高排查速度,尽量减少不必要的人参与呢?这就是我想要构建用户情景重现系统的初衷。
Web前端应用的组成要素
开始之前,先看一下,Web前端应用是有3个要素组成:
- 逻辑
- 数据
- 交互
逻辑
就是我们的Web前端代码三剑客——HTML、CSS、JS,以及其他的静态资源,例如图片、字体、媒体等。
数据
顾名思义,就是服务器返回的数据。
交互
用户在浏览器进行的交互,诸如鼠标的点击、滚动,键盘的输入等。
生命周期
这三者构成了一个Web前端应用的生命周期,
- 浏览器加载
代码逻辑 代码逻辑请求获取服务器数据,再根据代码逻辑把相关内容呈现用户交互触发代码逻辑运行,改变展示内容或触发第1、2步
sequenceDiagram
代码逻辑->>浏览器: 1.页面及静态资源加载
服务器数据->>浏览器: 2.请求获取
用户交互-->>浏览器: 3.键盘/鼠标操作
浏览器->>浏览器: 4.运行已加载的逻辑
整体思路
理清楚Web前端应用的本质之后,我们开始思考要如何构建这个系统。
首先,因为前端代码逻辑&静态资源一般我们都由前端开发同学所掌握,那我们只要确定剩下的两个要素(数据、交互)在情景重现中需要的数据包括哪些:
- 接口的请求&返回
- ajax(get/post)
- jsonp
- fetch
- 用户行为
- 点击事件
- 输入事件
- 复制/粘贴事件
- 键盘按键事件
- 鼠标滚动事件
- ……
然后,我们需要把这些数据以特定的格式上报到服务器数据库,用作情景重现使用。
接着,我们需要管理后台,对接入的应用(项目)、以及应用中的情景进行管理。
最后,我们需要根据前面获取的数据把情景重现。
总而言之,项目整体分为三部分:
- 项目接入
- 管理后台(包括数据存储和重现的后端处理逻辑)
- 情景重现
项目接入部分
接口数据上报
- 针对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);
}
- fetch API,我们通过重写
window.fetch方法,在原来的fetch API的resolve和reject的时候,进行数据上报。
需要注意的是,fetch API返回的Response对象只能读取一次,所以需要调用Response.clone()获取一个克隆对象去读取内容。
- 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部分待完善,目前我们的项目用得较少。
解决了什么
有了这套系统之后,能解决我们的痛点包括:
- 线上故障快速重现定位,缩短定位问题时需要找齐各方才能开展的痛点,即使只有不懂技术的同事(客服、产品、运营等)或者不负责该项目的同事,都能迅速了解到问题出在哪。
- 一些事务性的功能,执行过后就无法或很难复现的业务场景,能非常方便地掌握当时用户的情景。
- 前端页面自测,在接口不变的前提下,可以作为mock平台,测试前端代码,特别是一些技术优化改造的场景,或者是出现条件苛刻的某些路径。
- 当项目逐渐复杂和长期迭代后,新参与的同事(无论产品、研发、测试等)可以利用积累的接口数据,快速了解功能。