在我长期的固有认知中:为了收集和上报网站交互过程中 JavaScript
的报错信息和其它相关数据,我们一般会采用自研或者第三方的SDK
,也可以简单理解为埋点
。这也是为了方便生产问题的排查,做到可溯源。
但是,在前一段,和隔壁组的同事交流时,我发现了一个有点东西
的系统--Rejouer
。这个系统可以做到完整复现用户的操作行为,类似于录屏
的功能,起初我还真的以为他们就是给我放了一个录屏
,后来发现没那么简单。
本文也是在和这位同事探讨的过程中得到的一些启发。
不要重复造轮子!
这是我一直以来的信念。轮子的出现必定是要为业务服务。那么Rejouer
出现的背景是什么呢?
我们来回顾一下日常处理线上问题的场景:
上面图片中的是一个正常处理线上问题的流程。我们一般会通过之前埋入的一些业务打点,根据用户uid
去追溯问题,这样基本可以解决大部分的线上问题。但在实际的业务场景中,总是会出现一些奇奇怪怪的问题:
环境差异
:客户版本和配置差异内容传递不完整
:在上图第 2 步中,出现信息传递不对称,造成问题无法复现敏感性
:上图第 4 步中出现本地无法复现,咨询客户,但又涉及较为私密的信息,用户不愿录屏复现的场景客户数据差异
:即使用户愿意录屏,但可能由于服务抖动,用户此时再次走流程也无法复现除了上面提到的这些问题,还有一个影响线上问题解决速度最致命
的:链路过长!!
由此可见定位问题的痛点也是足够多。
上面提到了那么多的业务痛点,那么为了解决这些问题,我们肯定期望能有这么一个系统:
有了上面的需求,下面让我们来做下技术调研,看如何进行实现。
初期的调研,探寻了 5 种可行的方案:
我们一一来进行分析。
首先简单了解一下webRTC
:它是一套客户端点对点流式信息传播的技术方案。
其不仅可以用于「音视频录制、视频通话」,还可以用在「照相机、音乐播放器、共享远程桌面、即时通信工具、P2P 网络加速、文件传输、实时人脸识别」等场景上。
但我们来看下它的兼容性情况:
不支持 IE,这个就有点不太友好了,而且是完全不支持(毕竟我们还是有部分的用户在使用IE
浏览器的 )
除了兼容性之外,还有另外一个问题:webrtc
在启动能力前,会有原生的chrome
窗口弹出来询问用户,且在开启getDisplayMedia
之后会有一个状态条,所以在整个的操作中会有一些生硬,用户有明显的知道自己的屏幕正在被录制。
显然针对这种场景,更适合用在测试场景下,或者明确授权情况下。也不符合我们的需求。
puppeteer
是谷歌官方出品的一个通过 DevTools
协议控制 headless Chrome
的 Node
库,我们可以通过 puppeteer
提供的 API
直接控制 Chrome
,进而模拟大部分用户在浏览器上的操作,来进行 UI Test
或者作为爬虫访问页面来收集数据。
但puppeteer
主要的问题是它强依赖于Chromium
,客户操作也较为复杂。
因此也不是太符合我们的设定。
简单来说:浏览器插件就是一个用Web
技术开发、用来增强浏览器功能的软件。
正如其名,由于浏览器厂商多样性,其开发适配成本也不低。同时还伴随着诸如权限、培训使用等问题。
也不符合我们的需求。
利用Canvas
截图,使用 html2Canvas
库,不停的画页面然后不停的截图,再将图片组成视频播放出来。
这种也是我首先想到的方案,不过在脑海里没有停留多久,就被否决了:性能太差了!!
而且还存在诸如图片跨域兼容性问题。
好家伙,找了这么久,一个能用的都没有。把我都给整不会了
冷静的分析一波:其实还有一个 API:MutationObserver
。该接口提供了监视对 DOM 树所做更改的能力。我们可以利用这个接口,保存每次变化的DOM
数据,并把这些数据转换成可视化的数据结构,然后分别保存起来。接着使用特定的方式对之前保存起来的DOM
数据进行还原并重新渲染出来。DOM
节点的变化也就意味了页面轨迹发送了变化。这样就可以把这些轨迹记录下来。
理想很丰满,现实真的挺残酷!
真正自己去落地时还是遇到很多问题的,包括其中的很多细节,那么类似的社区里面有没有开源的解决方案呢?
你别说,还真有:rrweb
打开rrweb[1]官方地址:
诚如所言:rrweb
是一个开源的 Web 会话回放库
,它提供易于使用的 API
来记录用户的交互并远程回放。
再看首页右侧的录制回放演示。
这不就是我们想要的吗?
体验了一波之后,我感觉rrweb
基本能满足我们的业务诉求了:
关于
rrweb
的基本使用,这里不会赘述,可参考rrweb 官方使用指南[2]
rrweb
内部做了什么?回想我们起初做的一些调研,都没有成功落地。最终Rejouer
的落地也是基于rrweb
做了很多适配自身业务的封装。
本文后半部分更多是来探讨
rrweb
,Rejouer
由于是内部项目,更多细节暂时不便透露
对于rrweb
内部的一些实现,我们还是比较好奇的。下面我准备深入去挖掘他的内部实现细节。
既然要分析它的内部运作流程,那么有几个前置的知识点需要提前同步一下:
⻚⾯中的视图状态可以通过 DOM
树的形式描述,所以当我们尝试录制⼀个⻚⾯时,我们实际上是在记录 DOM
树在各个时间点上的状态,在 rrweb
中我们称⼀次这样的状态记录为⼀个快照。
定时快照的概念相对比较好理解:定时对⻚⾯制作快照完成录制。
但有很大的弊端:
所以产生了增量快照的概念。
其实我们可以只在页面初始化
完成之后 clone
一次完整的页面内容,等到页面有变动的时候,只记录变化
的部分。这样一来,好处就显而易见了:
这里提到了只记录变化的部分
,那这个变化
又该怎么去衡量呢?
我们可以暂且把这种变化称为视图变更
。
引发视图变更的操作大致有:
DOM 变动
节点创建、销毁
节点属性变化
⽂本变化
⿏标交互
⻚⾯或元素滚动
视窗⼤⼩改变
输⼊
⿏标移动(特指⿏标的视觉位置)
这里的DOM 变动
我们可以采用MutationObserver
来监听,它会以批量的方式返回 dom 的更新记录
。
但是它没法跟踪像 input
、textarea
、select
这类可交互元素的输入。
对于这种可交互的元素,我们主要靠监听 input
和 change
来记录输入的过程。
但是有些元素的值是通过程序直接设置的,这样是不会触发 input
和 change
事件的。这种情况下我们可以通过劫持对应属性的 setter
来达到监听的目的。
ok,有了这些知识,下面我们就来看一下rrweb
的运行流程吧。
这里可以简单用一张图来概括:
在 DOM
加载完成后,record
会做一次完整的 DOM
序列化,我们把它叫做全量快照
,全量快照记录了整个 HTML
数据结构。
然后在页面发生视图变动时(DOM 变化和用户操作),记录增量数据,完成页面的录制,然后保存到远程服务器。
回放时会先创建沙箱环境,接着重建全量快照
,在通过 requestAnimationFrame
模拟定时器的方式来播放增量快照。
本文主要整理了在后埋点时代
针对业务中一些难复现、易引起客户纠纷的一些场景,在页面录制/回放
道路上的探索过程。探讨了几种解决方案的利弊,最终也是基于社区非常优秀的rrweb
做的封装。同时也分析了一下rrweb
的核心实现。关于rrweb
更多细节的探讨可参考rrweb:打开 web 页面录制与回放的黑盒子[3] 。
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8