已经在运⾏的线上Demo:前端监控系统
代码和讲解都放在这篇⽂章⾥:监控系统介绍及代码
如果实在嫌部署⿇烦,Demo系统可以提供 7天 的监控量,我会长期维护:⼀键部署
⼀直以来, 前端上线的项⽬,对于前端程序猿来说,完全是⼀个⿊盒⼦。 项⽬⼀旦上线,我们完全不知道⽤户在我们的项⽬⾥边做了什
么,跳转到哪⾥,是不是报错了。⼀旦线上⽤户出现问题,⽽我们⼜⽆法复现的时候,才能体会到什么叫绝望。 不管多么艰难,问题总是
会在哪⾥等着你。所以,如果我们可以把线上的项⽬变成⼀个⽩盒⼦,让我们能够知道⽤户在线上⼲了什么,复现不再困难了,对前端程序
员来说,是不是⼀件好事呢。
接下来我要写的是⼀个重要的功能, 因为它极⼤的提⾼了我解决问题的能⼒, 也让对我的⼯作产⽣了很⼤的影响。
截⽌到现在,来看看我已经完成了哪些功能:
PV/UV的统计上报,js错误的上报和分析, 接⼝的统计上报,页⾯截屏的统计上报。 那么,再补上今天要写的“⽤户点击⾏为的上报”,
我们基本上就能够分析出⼀个⽤户在页⾯上⼲了什么。
⼀、如何记录线上⽤户的⾏为
线上⽤户的基本⾏为包括: 访问页⾯, 点击⾏为,请求接⼝⾏为, js报错⾏为, 这⼏点基本上能够清楚的记录下⽤户在线上的所有⾏为。
当然还包括:资源加载⾏为,滚动页⾯⾏为, 元素进⼊⽤户视野等等⾏为,这些是更为细节的⾏为统计, 也许会在以后进⾏完善, 但是以
上的四种⾏为已经可以完成我们的统计需求。
访问页⾯, js报错⾏为我们已经有了,接下来看看如何统计点击⾏为和请求接⼝的⾏为吧。

// ⽤户⾏为⽇志,继承于⽇志基类MonitorBaseInfo
  function BehaviorInfo(uploadType, behaviorType, className, placeholder, inputValue, tagName, innerText) {
    setCommonProperty.apply(this);
    this.uploadType = uploadType;
    this.behaviorType = behaviorType;
    this.className = utils.b64EncodeUnicode(className);
    this.placeholder = utils.b64EncodeUnicode(placeholder);
    this.inputValue = utils.b64EncodeUnicode(inputValue);
    this.tagName = tagName;
    this.innerText = utils.b64EncodeUnicode(encodeURIComponent(innerText));
  }
/**
   * ⽤户⾏为记录监控
   * @param project 项⽬详情
   */
  function recordBehavior(project) {
    // ⾏为记录开关
    if (project && project.record && project.record == 1) {
      // 记录⾏为前,检查⼀下url记录是否变化
      checkUrlChange();
      // 记录⽤户点击元素的⾏为数据
      document.onclick = function (e) {
        var className = "";
        var placeholder = "";
        var inputValue = "";
        var tagName = e.target.tagName;
        var innerText = "";
        if (e.target.tagName != "svg" && e.target.tagName != "use") {
          className = e.target.className;
          placeholder = e.target.placeholder || "";
          inputValue = e.target.value || "";
          innerText = e.target.innerText.replace(/\s*/g, "");
          // 如果点击的内容过长,就截取上传
          if (innerText.length > 200) innerText = innerText.substring(0, 100) + "... ..." + innerText.substring(innerText.length - 99, innerText.length - 1);
          innerText = innerText.replace(/\s/g, '');
        }
        var behaviorInfo = new BehaviorInfo(ELE_BEHAVIOR, "click", className, placeholder, inputValue, tagName, innerText);
        behaviorInfo.handleLogInfo(ELE_BEHAVIOR, behaviorInfo);
      }
    }
  };

我们先来看⼀下点击⾏为的代码,其实很简单,就是重写⼀下document的onclick⽅法,然后把相应的元素的属性,内容等等保存起来,
**但是,**我们费了这么⼤的⼒⽓保存了如此多的⽇志,就为了简单的记录⼀下⽤户的点击⾏为,实在太浪费了。 所以,这个点击⾏为统计
会被添加到未来的留存分析当中去,到时候能够实现⽆埋点记录⽇志的功能,让我们的监控系统更加的强⼤和丰富。留存分析会参考
GrowingIo, 有兴趣可以了解⼀下。
我们需要记录下元素的className, tagName, innerText等等,我们需要⾜够的的内容才能够确定⽤户点击的是哪个按钮。这种⽅式⽐较
弱智,将会在以后写留存分析功能的时候进⾏完善⼀下,但是⽬前⾜以满⾜我们的要求了。

// 接⼝请求⽇志,继承于⽇志基类MonitorBaseInfo
  function HttpLogInfo(uploadType, url, status, statusText, statusResult, currentTime) {
    setCommonProperty.apply(this);
    this.uploadType = uploadType;
    this.httpUrl = utils.b64EncodeUnicode(url);
    this.status = status;
    this.statusText = statusText;
    this.statusResult = statusResult;
    this.happenTime = currentTime;
  }
/**
   * 页⾯接⼝请求监控
   */
  function recordHttpLog() {
    // 监听ajax的状态
    function ajaxEventTrigger(event) {
      var ajaxEvent = new CustomEvent(event, {
        detail: this
      });
      window.dispatchEvent(ajaxEvent);
    }
    var oldXHR = window.XMLHttpRequest;
    function newXHR() {
      var realXHR = new oldXHR();
      realXHR.addEventListener('abort', function () { ajaxEventTrigger.call(this, 'ajaxAbort'); }, false);
      realXHR.addEventListener('error', function () { ajaxEventTrigger.call(this, 'ajaxError'); }, false);
      realXHR.addEventListener('load', function () { ajaxEventTrigger.call(this, 'ajaxLoad'); }, false);
      realXHR.addEventListener('loadstart', function () { ajaxEventTrigger.call(this, 'ajaxLoadStart'); }, false);
      realXHR.addEventListener('progress', function () { ajaxEventTrigger.call(this, 'ajaxProgress'); }, false);
      realXHR.addEventListener('timeout', function () { ajaxEventTrigger.call(this, 'ajaxTimeout'); }, false);
      realXHR.addEventListener('loadend', function () { ajaxEventTrigger.call(this, 'ajaxLoadEnd'); }, false);
      realXHR.addEventListener('readystatechange', function() { ajaxEventTrigger.call(this, 'ajaxReadyStateChange'); }, false);
      return realXHR;
    }
    window.XMLHttpRequest = newXHR;
    window.addEventListener('ajaxLoadStart', function(e) {
      var currentTime = new Date().getTime()
      setTimeout(function () {
        var url = e.detail.responseURL;
        var status = e.detail.status;
        var statusText = e.detail.statusText;
        if (!url || url.indexOf(HTTP_UPLOAD_LOG_API) != -1) return;
        var httpLogInfo = new HttpLogInfo(HTTP_LOG, url, status, statusText, "发起请求", currentTime);
        httpLogInfo.handleLogInfo(HTTP_LOG, httpLogInfo);
      }, 2000)
    });
    window.addEventListener('ajaxLoadEnd', function(e) {
      var currentTime = new Date().getTime()
      var url = e.detail.responseURL;
      var status = e.detail.status;
      var statusText = e.detail.statusText;
      if (!url || url.indexOf(HTTP_UPLOAD_LOG_API) != -1) return;
      var httpLogInfo = new HttpLogInfo(HTTP_LOG, url, status, statusText, "请求返回", currentTime);
      httpLogInfo.handleLogInfo(HTTP_LOG, httpLogInfo);
    });
  }

让我们来看看接⼝⾏为统计的代码先,本来这个我想单独拿出来说⼀说的,但是现在么有那么多时间把它相关的功能开发出来,所以只写了
⼀个简版的。
接⼝⾏为的统计包括: 发起请求,接收请求,接收状态,请求时长, 通过前端对接⼝的统计和分析,我们是可以观察出线上接⼝的质量,
同时也能够对前端的逻辑做出相应的调整,已达到页⾯加载的最佳效果。 数据库字段定义都在分析后台的项⽬⾥, 可以直接去看。
⾸先,我们要监听页⾯的ajax请求, 如上所⽰,写了⼀段监听ajax请求的代码(我是在⽹上扒下来的 thanks), 可以监听到页⾯上所有的
ajax请求,对整个ajax请求过程进⾏了原⼦性分析,我们可以监听到请求过程中任何⼀个时段的事件,⾮常好⽤。 **但是,**有⼀点⾮常重
要, 如果你的项⽬⾥边⽤的是fetch请求数据的话, 那么这些监听就⽆效了。 因为fetch代码是浏览器注⼊的, 肯定先⽤监控代码执⾏,
然后你再监听ajax就⼀点⽤都没有了。 所以你需要在写好ajax监听之后,重写fetch代码, 这样就可以⽣效了。好了,这部分并不是这篇幅
的重点,我们就说到这⾥。
⼆、如何查询线上⽤户的⾏为
终于,我们把剩下的两种⾏为记录都成功上传了,那么该如果把他们都查询出来呢。我们先来看⼀下页⾯上我查询出来的结果。
因为屏幕太⼩,⽆法展⽰所有的记录,记录信息包含:⾏为名称,⾏为发⽣时间, ⾏为发⽣页⾯, 错误信息, 错误截图, 以及⽤户⾃定义
上传截图的时机。
说到这⾥有⼏个⼩问题需要注意。
因为是⽤Js做探针,记录⽇志的时候很难保证每次记录都可以把⽤户的userId插⼊进去
所以我们给每个⽤户都定义⼀个customerKey来做区分,如果⽤户不卸载app和清理app的缓存, customerKey将保持不变
在查询⽤户的⾏为记录的时候,需要先查询出⽤户所有的customerKey(可能有多个),再⽤customerKey进⾏查询,便可以得到准确的结
果。
三、如何分析线上⽤户的⾏为
其实我们做了这么多,记录了这么多,就是为了这个⽬的:分析⾏为,快速定位问题。
那么我们如何定位问题呢,我可以举例说明⼀下:
JS报错阻断⾏为,我们可以看到发⽣错误的前后⾏为,就能够快速准确定位问题。
复杂的链接跳转发⽣了错误。有些错误是前端页⾯会经过复杂的跳转,回退之后才发⽣的,就算测试⼈员也很难测试出这种问题,因为线上
的⽤户的任何⾏为都有可能出现。往往我们知道的只是他在最后停留的页⾯发⽣了错误。 如此,经过我们排查⾏为⽇志, 就能够复现出⽤
户的⾏为, 从⽽复现BUG
接⼝异常。 正常情况下,前端的接⼝都会设置超时时间的, 但是呢, 后台接⼝排查发现正常, ⽽前端就是⽆法正常执⾏, 这种问题没有
显⽰的错误现象,⽽线上的反馈并不能够准确,前端只能背锅了。 ⽽⽇志记录是可以把请求发出时间和返回时间记录下来, 是否超时,看
⼀眼就知道。
线上的⽤户根本就不会反馈异常, 他们能做的只是把最后⼀眼能看到的东西告诉你。 天知道他们之前经历了什么步骤。 最终的结果是,前
端有问题,然后背锅,哈哈。
总之, 我们知道⽤户在页⾯上⼲了什么, 便不再担⼼问题出现, 遇见问题也不会再⼿忙脚乱了。
推荐⼀下我⾃⼰的前端学习群562862926,⾥⾯有⼤神总结的前端教学视频,欢迎有兴趣的朋友进群⼀起学习
--------------------------------------------------------
作者:资料库痴柏191
链接:https://wenku.baidu.com/view/caabb69e1937f111f18583d049649b6648d709be.html
来源:百度文库