搭建前端监控系统(六)JS截屏和录屏篇

怎样定位前端线上问题,一直以来,都是很头疼的问题,因为它发生于用户的一系列操作之后。错误的原因可能源于机型,网络环境,接口请求,复杂的操作行为等等,在我们想要去解决的时候很难复现出来,自然也就无法解决。 当然,这些问题并非不能克服,让我们来一起看看如何去监控并定位线上的问题吧。 

这是搭建前端监控系统的第六章,主要是介绍如何使用js进行页面截屏和录屏,跟着我一步步做,你也能搭建出一个属于自己的前端监控系统。

===============================================================================

=  开源项目:前端监控系统  --- 只需要简单几步,就可以搭建一套属于自己的前端监控系统,快试试吧。 =

===============================================================================

用户使用App的时候,对于我们开发人员来说,就是一个黑匣子,因为你看不见也摸不着啊。 如果用户上报了一个错误,测试人员找到你让你解决,那你就是两眼一抹黑,因为很多线上问题是很难复现的。我问过很多前端工程师,他们的回答都是:如果你没法复现Bug,我怎么去解决这个Bug呢?你咋一想,好像说得很有道理啊,那么,我们有没有一个办法可以解决用户和前端程序员之间的障碍呢?

如果我们能够做到:用户使用我们app的过程中,不再是黑匣子,而是透明的。用户的页面长什么样,他们都做了什么操作,发生了什么错误,我们都能够清晰的知道,那么,再有问题上报的时候,我们就不会再显得那么力不从心了。

一、JS怎么实现截图呢?

如果用户在页面上产生一些特殊行为,比如报错,白屏的时候,我们想看看页面长什么样子的,就可以利用js截屏技术达到我们的目的。这样我们对用户当时的页面有个直观的感受,解决问题的时候也会的得心应手一些。

我们需要用到一个开源库叫 html2Canvas ,这个库的名字,我想大家也是耳熟能详了,毕竟git上辣么多颗星星呢。 关于他原理的介绍,有篇文章已经说得很详细了,如何实现web录屏

既然我们可以对页面进行截图了,那么接下来的问题就是上传了。html2canvas的截图是图片数据,多则大几百Kb, 少则也有个上百Kb, 这么大的流量,对用户端,损耗确实过大。也许未来5G的大时代到了,这点流量根本不算什么,但是现在主要还是4g时代,这样的流量损耗不容忽视。所以我们需要对截图的质量有个选择,以下是我对我的网站进行的截图,分别用了三种压缩方式。

当然因为我的网站不支持手机版,丑了点,这不是重点哈,我主要看数据:

 

 参数               截图方式一                    截图方式二                   截图方式三


字符串长度                11783                         34047                        68111
图片压缩率              72%                        40%                       0%
截图大小               23Kb                       66Kb                     133Kb

综上分析,截图方式一, 压缩率高,虽然截图不是很清晰,但是,也能够看得出,线上用户页面是什么样子的。而且,也解决了,在低端机上截图消耗性能过大的弊端,二十几Kb的流量,也是我们完全能够接受的大小了。由此可见,该方式能够满足我们追踪用户行为的需求了,当然,也可以适当的提高清晰度,以实际情况而定。

那么如何使用html2canvas进行截图呢,代码如下:

/**
     * js处理截图
     */
    this.screenShot = function (cntElem, callback) {
      var shareContent = cntElem;//需要截图的包裹的(原生的)DOM 对象
      var width = shareContent.offsetWidth; //获取dom 宽度
      var height = shareContent.offsetHeight; //获取dom 高度
      var canvas = document.createElement("canvas"); //创建一个canvas节点
      var scale = 0.6; //定义任意放大倍数 支持小数
      canvas.style.display = "none";
      canvas.width = width * scale; //定义canvas 宽度 * 缩放
      canvas.height = height * scale; //定义canvas高度 *缩放
      canvas.getContext("2d").scale(scale, scale); //获取context,设置scale
      var opts = {
        scale: scale, // 添加的scale 参数
        canvas: canvas, //自定义 canvas
        logging: false, //日志开关,便于查看html2canvas的内部执行流程
        width: width, //dom 原始宽度
        height: height,
        useCORS: true // 【重要】开启跨域配置
      };
      html2canvas(cntElem, opts).then(function(canvas) {
        var dataURL = canvas.toDataURL();
        var tempCompress = dataURL.replace("data:image/png;base64,", "");
        var compressedDataURL = Base64String.compress(tempCompress);
        callback(compressedDataURL);
      });
    }

二、JS怎么实现录屏?

Fundebug很久之前出了一个录屏功能,进入Fundebug首页,第一条便是 黑科技!支持录屏。 这下就惊呆我了,js做前端监控,居然还能录屏? 你丫这是要逆天啊? 所以,赶紧注册了账号,进行试用。 因为当时fundebug的录屏功能还在适用阶段,所以我还误以为是靠一连串的截图组成的视频,反倒惹人笑话😂。

实现JS录屏需要用到一个开源库rrweb,当看到这个库的时候颇有一番感慨:

一叹前辈高人技艺之精湛,二叹开源世界心胸之广阔,正是有这些无私奉献的大牛们,才让这技术的世界多姿多彩。

这个录屏库的原理跟html2canvas不同,并非是靠截图,而是记录下整个dom树的结构,然后再分别记录不同dom节点的变化,最后在iframe里边将他们重新渲染出来,就是我们能看到的录屏结果了。至于具体的录屏效果,大家可以进入我的首页,点击「录屏测试」菜单查看效果,确实很惊艳。

录屏的api,rrweb已经写得很清楚了,我就不再浪费笔墨了。Js的录屏效果很惊艳,但是同时也带来了一个比较棘手的问题,以我的网站为例,一段将近20秒的视频,视频数据竟然达到了将近20M之多。像我网站首页,最大的dom树结构长度高达几十万之多,所以解决录屏信息的长度便是首要任务。大家可以看看我的测试数据,我录制了一段近2分钟的视频,数据总量压缩在1.5M左右,那么除以2分钟的之间,10秒钟差不多控制在120kb大小左右。跟fundebug提出的,10s内录屏数据压缩在百K以内,感觉也相差不多了,总之我一人之力有限,时间亦有限,所以暂时先做到这一步,并投入使用看看效果再说。下边说说,如何进行字符串压缩

 三、JS如何压缩JSON字符串?

对字符串进行压缩,可能工作中很多情况下是用不到的,但是如果做上传日志等相关的工作,如遇到截图,录屏等,那么字符串压缩就是不得不做了。

因为rrweb的录屏数据全都是JSON格式,自然而然要转化成JSON字符串进行上传了。我这里使用两步对JSON字符串进行压缩:

1. 简化JSON中的key值。

由于rrweb是将整个dom树转化成json格式的数据,所以里边出现了大量的重复的key,所以我这里使用更短小的key值,将他们替换,以达到缩短JSON字符串的目的。另外,里边包含了很多样式的内容,大家都知道,样式的key值很长,而且大多数都重复,所以样式里的值,我们也要对其进行缩短替换。代码如下:

var JSON_KEY = {"type":"≠","childNodes":"ā","name":"á","id":"ǎ","tagName":"à","attributes":"ē","style":"é","textContent":"ě","isStyle":"è","isSVG":"ī","content":"í","href":"ǐ","src":"ì","class":"ō","tabindex":"ó","aria-label":"ǒ","viewBox":"ò","focusable":"ū","data-icon":"ú","width":"ǔ","height":"ù","fill":"ǖ","aria-hidden":"ǘ","stroke":"ǚ","stroke-width":"ǜ","paint-order":"ü","stroke-opacity":"ê","stroke-dasharray":"ɑ","stroke-linecap":"?","stroke-linejoin":"ń","stroke-miterlimit":"ň","clip-path":"Γ","alignment-baseline":"Δ","fill-opacity":"Θ","transform":"Ξ","text-anchor":"Π","offset":"Σ","stop-color":"Υ","stop-opacity":"Φ"};
var JSON_CSS_KEY = {"background":"≠","background-attachment":"ā","background-color":"á","background-image":"ǎ","background-position":"à","background-repeat":"ē","background-clip":"é","background-origin":"ě","background-size":"è","border":"Г","border-bottom":"η","color":"┯","style":"Υ","width":"б","border-color":"ū","border-left":"ǚ","border-right":"ň","border-style":"Δ","border-top":"З","border-width":"Ω","outline":"α","outline-color":"β","outline-style":"γ","outline-width":"δ","left-radius":"Ж","right-radius":"И","border-image":"ω","outset":"μ","repeat":"ξ","repeated":"π","rounded":"ρ","stretched":"σ","slice":"υ","source":"ψ","border-radius":"Б","radius":"Д","box-decoration":"Й","break":"К","box-shadow":"Л","overflow-x":"Ф","overflow-y":"У","overflow-style":"Ц","rotation":"Ч","rotation-point":"Щ","opacity":"Ъ","height":"Ы","max-height":"Э","max-width":"Ю","min-height":"Я","min-width":"а","font":"в","font-family":"г","font-size":"ж","adjust":"з","aspect":"и","font-stretch":"й","font-style":"к","font-variant":"л","font-weight":"ф","content":"ц","before":"ч","after":"ш","counter-increment":"щ","counter-reset":"ъ","quotes":"ы","list-style":"+","image":"-","position":"|","type":"┌","margin":"┍","margin-bottom":"┎","margin-left":"┏","margin-right":"┐","margin-top":"┑","padding":"┒","padding-bottom":"┓","padding-left":"—","padding-right":"┄","padding-top":"┈","bottom":"├","clear":"┝","clip":"┞","cursor":"┟","display":"┠","float":"┡","left":"┢","overflow":"┣","right":"┆","top":"┊","vertical-align":"┬","visibility":"┭","z-index":"┮","direction":"┰","letter-spacing":"┱","line-height":"┲","text-align":"6","text-decoration":"┼","text-indent":"┽","text-shadow":"10","text-transform":"┿","unicode-bidi":"╀","white-space":"╂","word-spacing":"╁","hanging-punctuation":"╃","punctuation-trim":"1","last":"3","text-emphasis":"4","text-justify":"5","justify":"7","text-outline":"8","text-overflow":"9","text-wrap":"11","word-break":"12","word-wrap":"13"}

this.compressJson = function(o) {
      if (o instanceof Array) {
        var n = []
        for (var i = 0; i < o.length; ++i) {
          n[i] = this.compressJson(o[i])
        }
        return n
      } else if (o instanceof Object) {
        var n = {}
        for (var i in o) {
          if (i == "_cssText") {
            o[i]= o[i].replace(/ {/g, "{").replace(/; /g, ";").replace(/: /g, ":").replace(/, /g, ",").replace(/{ /g, "{")
            for (var key in JSON_CSS_KEY) {
              var cssAttr = JSON_CSS_KEY[key]
              var cssReg = new RegExp(key, "g");
              o[i]= o[i].replace(cssReg, cssAttr)
            }
          }

          if (JSON_KEY[i]) {
            n[JSON_KEY[i]] = this.compressJson(o[i])
            delete n[i]
          } else {
            n[i] = this.compressJson(o[i])
          }
        }
        return n
      }
      return o
    }

2. 使用lz-string 库对字符串进行压缩

lz-string有两个api: 

LZString.compress(String) --- 压缩效果极好,但是我后台无法解析,我经历很多尝试,都解析不出来。
LZString.compressToBase64(String) --- 压缩效果一般,但是后台可以很好的解析出来,无奈,只能选择了第二api进行压缩。

至此,前端监控系统便可以完成截屏和录屏的功能了。 

posted @ 2019-11-07 16:15  一步一个脚印一个坑  阅读(7489)  评论(1编辑  收藏  举报