背景:市面上的监控系统有不少,大多收费,对于小型前端项目来讲,必然是痛点。另外一点主要缘由是,功能虽然通用,却未必可以知足咱们本身的需求, 因此咱们自给自足也许是个不错的办法。html
这是搭建前端监控系统的第二章,主要是介绍如何统计js报错,跟着我一步步作,你也能搭建出一个属于本身的前端监控系统。前端
目前已经在运行的线上Demo : 前端监控系统 git
代码和讲解都放在这篇文章里: 监控系统介绍及代码程序员
若是实在嫌部署麻烦,Demo系统能够提供 7天 的监控量,我会长期维护:一键部署github
一直以来, 前端上线的项目,对于前端程序猿来讲,彻底是一个黑盒子。 项目一旦上线,咱们彻底不知道用户在咱们的项目里边作了什么,跳转到哪里,是否是报错了。一旦线上用户出现问题,而咱们又没法复现的时候,才能体会到什么叫绝望。 无论多么艰难,问题老是会在哪里等着你。因此,若是咱们能够把线上的项目变成一个白盒子,让咱们可以知道用户在线上干了什么,复现再也不困难了,对前端程序员来讲,是否是一件好事呢。web
接下来我要写的是一个重要的功能, 由于它极大的提升了我解决问题的能力, 也让对个人工做产生了很大的影响。ajax
截止到如今,来看看我已经完成了哪些功能:数据库
PVUV的统计上报,js错误的上报和分析, 接口的统计上报,页面截屏的统计上报。 那么,再补上今天要写的“用户点击行为的上报”, 咱们基本上就可以分析出一个用户在页面上干了什么。浏览器
1、如何记录线上用户的行为缓存
线上用户的基本行为包括: 访问页面, 点击行为,请求接口行为, 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代码, 这样就能够生效了。好了,这部分并非这篇幅的重点,咱们就说到这里。
2、如何查询线上用户的行为
终于,咱们把剩下的两种行为记录都成功上传了,那么该若是把他们都查询出来呢。咱们先来看一下页面上我查询出来的结果。
由于屏幕过小,没法展现全部的记录,记录信息包含:行为名称,行为发生时间, 行为发生页面, 错误信息, 错误截图, 以及用户自定义上传截图的时机。
说到这里有几个小问题须要注意。
1. 由于是用Js作探针,记录日志的时候很难保证每次记录均可以把用户的userId插入进去
2. 因此咱们给每一个用户都定义一个customerKey来作区分,若是用户不卸载app和清理app的缓存, customerKey将保持不变
3. 在查询用户的行为记录的时候,须要先查询出用户全部的customerKey(可能有多个),再用customerKey进行查询,即可以获得准确的结果。
3、如何分析线上用户的行为
其实咱们作了这么多,记录了这么多,就是为了这个目的:分析行为,快速定位问题。
那么咱们如何定位问题呢,我能够举例说明一下:
1. JS报错阻断行为,咱们能够看到发生错误的先后行为,就可以快速准肯定位问题。
2. 复杂的连接跳转发生了错误。有些错误是前端页面会通过复杂的跳转,回退以后才发生的,就算测试人员也很难测试出这种问题,由于线上的用户的任何行为都有可能出现。每每咱们知道的只是他在最后停留的页面发生了错误。 如此,通过咱们排查行为日志, 就可以复现出用户的行为, 从而复现BUG
3. 接口异常。 正常状况下,前端的接口都会设置超时时间的, 可是呢, 后台接口排查发现正常, 而前端就是没法正常执行, 这种问题没有显示的错误现象,而线上的反馈并不可以准确,前端只能背锅了。 而日志记录是能够把请求发出时间和返回时间记录下来, 是否超时,看一眼就知道。
4. 线上的用户根本就不会反馈异常, 他们能作的只是把最后一眼能看到的东西告诉你。 天知道他们以前经历了什么步骤。 最终的结果是,前端有问题,而后背锅,哈哈。
总之, 咱们知道用户在页面上干了什么, 便再也不担忧问题出现, 碰见问题也不会再手忙脚乱了。