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

  背景:市面上的前端监控系统有很多,功能齐全,种类繁多,不管你用或是不用,它都在那里,密密麻麻。往往我需要的功能都在别人家的监控系统里,手动无奈,罢了,怎么才能拥有一个私人定制的前端监控系统呢?做一个自带前端监控系统的前端工程狮是一种怎样的体验呢?

  这是搭建前端监控系统的第二章,主要是讲如何对js错误进行监控,跟着我一步步做,你也能搭建出一个属于自己的前端监控系统。

  如果感觉有帮助,或者有兴趣,请关注 or Star Me 。

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

  本文由 www.webfunny.cn 前端监控提供;只需要简单几步就可以搭建一套属于自己的前端监控系统,快来试试吧 ^ _ ^。

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

  先看一下首页的结果:

  如果每天都去盯着前端的报错数据,真的很耗费精力,而且很难看出是今天发生的,还是一直存在的报错。

  所以呢,其实前端项目每天都会有些报错,比如:script error 。我们既不能控制,也不会影响我们的业务,只能让它一直存在。所以我们的前端应用,每天都会有一定数量的报错数据。只要日活量不会波动太大,那么报错数据就会比较平稳,所以我选择跟7天前的报错数据进行比较,如果出现大幅上升,那么就需要我对这个项目进行关注了,而不是每天查看具体的报错数据。

  对于前端应用来说,Js错误的发生直接影响前端应用的质量。对前端异常的监控是整个前端监控系统中的一个重要环节。前端异常包含很多种情况:1. js编译时异常(开发阶段就能排)2. js运行时异常;3. 加载静态资源异常(路径写错、资源服务器异常、CDN异常、跨域)4. 接口请求异常等。这一篇我们只介绍Js运行时异常。

  监控流程:监控错误 -> 搜集错误 -> 存储错误 -> 分析错误 -> 错误报警-> 定位错误 -> 解决错误

  首先,我们应该对Js报错情况有个大致的了解,这样才能够及时的了解前端项目的健康状况。所以我们需要分析出一些必要的数据。

  如:一段时间内,应用JS报错的走势(chart图表)、JS错误发生率、JS错误在PC端发生的概率、JS错误在IOS端发生的概率、JS错误在Android端发生的概率,以及JS错误的归类。

  然后,我们再去其中的Js错误进行详细的分析,辅助我们排查出错的位置和发生错误的原因。

  如:JS错误类型、 JS错误信息、JS错误堆栈、JS错误发生的位置以及相关位置的代码;JS错误发生的几率、浏览器的类型,版本号,设备机型等等辅助信息

一、JS Error 监控功能 (数据概览)

为了得到这些数据,我们需要在上传的时候将其分析出来。在众多日志分析中,很多字段及功能是重复通用的,所以应该将其封装起来。

// 设置日志对象类的通用属性
function setCommonProperty() {
this.happenTime = new Date().getTime(); // 日志发生时间
this.webMonitorId = WEB_MONITOR_ID; // 用于区分应用的唯一标识(一个项目对应一个)
this.simpleUrl = window.location.href.split('?')[0].replace('#', ''); // 页面的url
this.customerKey = utils.getCustomerKey(); // 用于区分用户,所对应唯一的标识,清理本地数据后失效
this.pageKey = utils.getPageKey(); // 用于区分页面,所对应唯一的标识,每个新页面对应一个值
this.deviceName = DEVICE_INFO.deviceName;
this.os = DEVICE_INFO.os + (DEVICE_INFO.osVersion ? " " + DEVICE_INFO.osVersion : "");
this.browserName = DEVICE_INFO.browserName;
this.browserVersion = DEVICE_INFO.browserVersion;
// TODO 位置信息, 待处理
this.monitorIp = ""; // 用户的IP地址
this.country = "china"; // 用户所在国家
this.province = ""; // 用户所在省份
this.city = ""; // 用户所在城市
// 用户自定义信息, 由开发者主动传入, 便于对线上进行准确定位
this.userId = USER_INFO.userId;
this.firstUserParam = USER_INFO.firstUserParam;
this.secondUserParam = USER_INFO.secondUserParam;
} // JS错误日志,继承于日志基类MonitorBaseInfo
function JavaScriptErrorInfo(uploadType, errorMsg, errorStack) {
setCommonProperty.apply(this);
this.uploadType = uploadType;
this.errorMessage = encodeURIComponent(errorMsg);
this.errorStack = errorStack;
this.browserInfo = BROWSER_INFO;
}
JavaScriptErrorInfo.prototype = new MonitorBaseInfo();

  封装了一个Js错误对象JavaScriptErrorInfo,用以保存页面中产生的Js错误。其中,setCommonProperty用以设置所有日志对象的通用属性。

  1)重写window.onerror 方法, 大家熟知,监控JS错误必然离不开它,有人对他进行了测试测试介绍感觉也是比较用心了

  2)重写console.error方法,为什么要重写这个方法,我不能够给出明确的答案,如果App首次向浏览器注入的Js代码报错了,window.onerror是无法监控到的,所以只能重写console.error的方式来进行捕获,也许会有更好的办法。待window.onerror成功后,此方法便不再需要用了

  3)重写window.onunhandledrejection方法。 当你用到Promise的时候,而你又忘记写reject的捕获方法的时候,系统总是会抛出一个叫 Unhandled Promise rejection. 没有堆栈,没有其他信息,特别是在写fetch请求的时候很容易发生。 所以我们需要重写这个方法,以帮助我们监控此类错误

  下边是启动JS错误监控代码

/**
* 页面JS错误监控
*/
function recordJavaScriptError() {
// 重写console.error, 可以捕获更全面的报错信息
var oldError = console.error;
console.error = function () {
// arguments的长度为2时,才是error上报的时机
// if (arguments.length < 2) return;
var errorMsg = arguments[0] && arguments[0].message;
var url = WEB_LOCATION;
var lineNumber = 0;
var columnNumber = 0;
var errorObj = arguments[0] && arguments[0].stack;
if (!errorObj) errorObj = arguments[0];
// 如果onerror重写成功,就无需在这里进行上报了
!jsMonitorStarted && siftAndMakeUpMessage(errorMsg, url, lineNumber, columnNumber, errorObj);
return oldError.apply(console, arguments);
};
// 重写 onerror 进行jsError的监听
window.onerror = function(errorMsg, url, lineNumber, columnNumber, errorObj)
{
jsMonitorStarted = true;
var errorStack = errorObj ? errorObj.stack : null;
siftAndMakeUpMessage(errorMsg, url, lineNumber, columnNumber, errorStack);
}; function siftAndMakeUpMessage(origin_errorMsg, origin_url, origin_lineNumber, origin_columnNumber, origin_errorObj) {
var errorMsg = origin_errorMsg ? origin_errorMsg : '';
var errorObj = origin_errorObj ? origin_errorObj : '';
var errorType = "";
if (errorMsg) {
var errorStackStr = JSON.stringify(errorObj)
errorType = errorStackStr.split(": ")[0].replace('"', "");
}
var javaScriptErrorInfo = new JavaScriptErrorInfo(JS_ERROR, errorType + ": " + errorMsg, errorObj);
javaScriptErrorInfo.handleLogInfo(JS_ERROR, javaScriptErrorInfo);
};
};

OK, 错误日志有了,该怎么计算错误率呢?

  JS错误发生率 = JS错误个数(一次访问页面中,所有的js错误都算一次)/PV (PC,IOS,Android平台同理)

所以我们需要记下页面的PV记录

    /**
* 添加一个定时器,进行数据的上传
* 2秒钟进行一次URL是否变化的检测
* 10秒钟进行一次数据的检查并上传
*/
var timeCount = 0;
setInterval(function () {
checkUrlChange();
// 循环5后次进行一次上传
if (timeCount >= 25) {
// 如果是本地的localhost, 就忽略,不进行上传 var logInfo = (localStorage[ELE_BEHAVIOR] || "") +
(localStorage[JS_ERROR] || "") +
(localStorage[HTTP_LOG] || "") +
(localStorage[SCREEN_SHOT] || "") +
(localStorage[CUSTOMER_PV] || "") +
(localStorage[LOAD_PAGE] || "") +
(localStorage[RESOURCE_LOAD] || ""); if (logInfo) {
localStorage[ELE_BEHAVIOR] = "";
localStorage[JS_ERROR] = "";
localStorage[HTTP_LOG] = "";
localStorage[SCREEN_SHOT] = "";
localStorage[CUSTOMER_PV] = "";
localStorage[LOAD_PAGE] = "";
localStorage[RESOURCE_LOAD] = "";
utils.ajax("POST", HTTP_UPLOAD_LOG_INFO, {logInfo: logInfo}, function (res) {}, function () {})
}
timeCount = 0;
}
timeCount ++;
}, 200);

  上边的代码我用了定时器,大概的意思是200毫秒进行一次URL变化的检查,5秒进行一次数据的检查,如果有数据就进行上传,并清空上一次的数据。为什么用定时器呢,因为在单页应用中,路由的切换和地址栏的变化是无法被监控的,我确实没有想到特别好的办法来监控,所以用了这种方式。

  封装简易的Ajax

  为了将这些数据上传到我们的服务器,我们总不能每次都用xmlHttpRequest来发送ajax请求吧,所以我们需要自己封装一个简单的Ajax

/**
*
* @param method 请求类型(大写) GET/POST
* @param url 请求URL
* @param param 请求参数
* @param successCallback 成功回调方法
* @param failCallback 失败回调方法
*/
this.ajax = function(method, url, param, successCallback, failCallback) {
var xmlHttp = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
xmlHttp.open(method, url, true);
xmlHttp.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
var res = JSON.parse(xmlHttp.responseText);
typeof successCallback == 'function' && successCallback(res);
} else {
typeof failCallback == 'function' && failCallback();
}
};
xmlHttp.send("data=" + JSON.stringify(param));
}

二、JS Error 详细信息解析

  统计JS Error的目的,一、是为了了解线上项目的健康状况,二、是为了分析错误,帮助我们查找问题之所在,并且解决它。

  所以,如何定位线上的问题,并解决问题,是我们现在要讨论的重点。下面我们需要对几个关键点进行分析:

  ①  某种错误发生的次数——发生次数跟影响用户是成正比的, 如果发生次数跟影响用户数量都很高,那么这是一个比较严重的bug, 需要立即解决。 反之, 如果次数很多,影响用户数量很少。说明这种错误只发生在少量设备中,优先级相对较低,可以择时对该类机型设备进行兼容处理。当然,ip地址访问次数也能说明这个问题

  

  ②  页面发生了哪些错误——这个有利于我们缩小问题的范围,方便我们排查,如:

  

  ③  错误堆栈——这点不用说,是定位错误最重要的因素。正常情况下,代码都是被压缩的,所以我在后台解析并截取出错代码附近的一部分代码,进行展示,排查错误。PS: 我看到网上有人利用jsMap反向找到代码的具体位置,想法很不错,后期我会加上。 另外,代码虽然被压缩,但是依然很轻松定位到出错的位置,如下图所示, 所以这个功能暂时作为附加题,不用那么着急加上。

 

  ④  设备信息——当错误发生是,分析出用户当时使用设备的浏览器信息,系统版本,设备机型等等,能够帮我们快速的定位到需要兼容的设备,进而提升解决问题的效率。

  ⑤  用户足迹——我个人觉得比较有用,但是代价太高。 因为这个需要记录下用户在页面上的所有行为,需要上传非常多的数据,功能待定。

     这个功能已经在后边进行完善了,点击 查看足迹 按钮即可查出这个用的行为足迹,在定位线上问题方面,有很大的作用 , 我在后边的篇幅中有介绍   搭建前端监控系统(五)怎样定位线上问题

  

  到此,已经收集到了JS错误日志的大部分信息了,并且已经分析出JS错误的详细信息了。

三、JS报错的实时监控与报警

  既然我们已经具有了搜集js报错和分析报错的能力了,那么我们也可以做到Js报错实时监控,以及实时预警了,这样可以防范线上事故于未然,及时的制止线上事故的持续发生, 减少损失。

  如上图所示,我展示了从当前时间向前推算24小时,每小时报错数量。另外展示了7天前同一时间段的报错数量,如果你的项目健康稳定,那么在相同时间段的报错数量应该不会相差太大。如果出现相差太大的情况发生,说明线上出现了问题,此刻应该发出警告,避免线上事故的发生。demo上暂未加上警告功能,但是原理清楚了,后边自然水到渠成。

  上一章: 搭建前端监控系统(一)阿里云服务器搭建篇

  下一章: 搭建前端监控系统(三)静态资源加载监控


  

搭建前端监控系统(二)JS错误监控篇的更多相关文章

  1. 前端监控系列2 |聊聊 JS 错误监控那些事儿

    作者:彭莉,火山引擎 APM 研发工程师.2020年加入字节,负责前端监控 SDK 的开发维护.平台数据消费的探索和落地. 有必要针对 JS 错误做监控吗? 我们可以先假设不对 JS 错误做监控,试想 ...

  2. Webfunny知识分享:JS错误监控

    现在的前端开发已不再是刀耕火种的年代了,各种框架.编译工具层出不穷,前端监控系统也不甘其后,遍地开花. 前端正承受着越来越重的职责,前端的业务也变得越来越复杂,此时此刻我们就更需要一套完善的监控系统来 ...

  3. Fundebug上线Node.js错误监控啦

    作为全栈JavaScript错误实时监测平台,Fundebug的Node.js实时错误监测服务上线啦,我们能够帮助开发者及时,高效地发现并且解决Node.js错误,从而提高开发效率,并提升用户体验. ...

  4. Security基础(六):部署Zabbix监控平台、配置及使用Zabbix监控系统、自定义Zabbix监控项目、实现Zabbix报警功能

    一.部署Zabbix监控平台 目标: 本案例要求部署一台Zabbix监控服务器,一台被监控主机,为进一步执行具体的监控任务做准备: 在监控服务器上安装LAMP环境    修改PHP配置文件,满足Zab ...

  5. 打造云原生大型分布式监控系统(四): Kvass+Thanos 监控超大规模容器集群

    概述 继上一篇 Thanos 部署与实践 发布半年多之后,随着技术的发展,本系列又迎来了一次更新.本文将介绍如何结合 Kvass 与 Thanos,来更好的实现大规模容器集群场景下的监控. 有 Tha ...

  6. 大数据系统之监控系统(二)Flume的扩展

    一些需求是原生Flume无法满足的,因此,基于开源的Flume我们增加了许多功能. EventDeserializer的缺陷 Flume的每一个source对应的deserializer必须实现接口E ...

  7. 利用树莓派实现监控系统二(用motion实现监控)

    终于要开始实现监控啦!ಠ౪ಠ 看起来很简单的步骤,但是过程却无比艰辛,踩坑是最好的成长 (/= _ =)/~┴┴ 第一步,不是安装!而是 update!ㄟ( ▔, ▔ )ㄏ sudo apt-get ...

  8. python-Django监控系统二次开发Nagios

    1.Nagios安装 yum install -y nagios.i686 yum install -y nagios-plugins-all.i686 安装完后会在apache的配置文件目录下/et ...

  9. zabbix监控系统的应用---数据监控、导入模板、告警

    一.zabbix监控nginx服务 1)在server2中安装nginx服务 --->  rpm  -ivh  nginx-1.8.0-1.el6.ngx.x86_64.rpm 2)编辑配置文件 ...

随机推荐

  1. Codesmith怎么判断sqlserver数据库字段是不是标识自增字段

    Codesmith怎么判断sqlserver数据库字段是不是标识自增字段 使用ExtendedProperty扩展信息判断 CS_isIdentity:是否为标识符,不支持Access CS_isCo ...

  2. 在windows 下使用eclipse进行编译和烧写

    eclipse IDE是一款开源的前端编程软件,它提供了编写,编译和调试ESP-IDF项目的图形集成开发环境. 首先在https://www.obeo.fr/en/eclipse-download?I ...

  3. (转)web前端知识精简

    Web前端技术由 html.css 和 javascript 三大部分构成,是一个庞大而复杂的技术体系,其复杂程度不低于任何一门后端语言.而我们在学习它的时候往往是先从某一个点切入,然后不断地接触和学 ...

  4. Solutions and Summay for Linked List Naive and Easy Questions

    1.Remove Linked List Elements package linkedlist; /* * Question: Remove all elements from a linked l ...

  5. ajax跨域问题小结

    跨域:跨域名的访问,是浏览器对ajax的一种限制,这样可以有效的房子跨站攻击 跨域的范畴: 域名不同  或 端口不同 或 二级域名不同 解决方案: 第一种:由于前端基础薄弱,且该方式老掉牙,不讲解: ...

  6. Android 框架式编程 —— 起篇

    一般的,在开发的时候,写过的代码在需求变更后,发现需要改动非常多的地方,那么说明之前的代码的架构肯定是存在问题的. 下面我们结合面向对象的六大基本原则谈Android 框架式编程.首先先介绍一下面向对 ...

  7. MySQL体系结构和存储引擎概述

     MySQL体系结构和存储引擎概述 一.定义数据库和实例 数据库: 物理操作系统文件或其他形式文件类型的集合.数据库文件可以是frm.MYD.ibd 结尾的文件. 从概念上来说,数据库是文件的集合,是 ...

  8. JavaScript实现单张图片上传功能

    前台jsp代码 <%@ page language="java" pageEncoding="UTF-8" contentType="text/ ...

  9. linux 软件安装篇

    在linux下安装软件,不像windows一样,下一步下一步安装,但是也有很方便的方式.也有自定义的安装方式,总体来说,套路还不算太深,但是要实践才能出真知哦! linux版本有很多,但是大部分命令都 ...

  10. Java高级开发必会的50个性能优化细节

    在JAVA程序中,性能问题的大部分原因并不在于JAVA语言,而是程序本身.养成良好的编码习惯非常重要,能够显著地提升程序性能. 1. 尽量在合适的场合使用单例 使用单例可以减轻加载的负担,缩短加载的时 ...