前端错误监控
1. 监控并收集Javascript错误
众所周知,我们是有办法去监听前端Js错误的,他们分别 window.onerror、window.onunhandledrejection、console.error方法。
- 重写 window.onerror 方法
// 重写 onerror 进行jsError的监听
window.onerror = function(errorMsg, url, lineNumber, columnNumber, errorObj) {
var errorStack = errorObj ? errorObj.stack : null;
siftAndMakeUpMessage("on_error", errorMsg, url, lineNumber, columnNumber, errorStack);
};
- 重写 window.onunhandledrejection 方法
window.onunhandledrejection = function(e) {
var errorMsg = "";
var errorStack = "";
if (typeof e.reason === "object") {
errorMsg = e.reason.message;
errorStack = e.reason.stack;
} else {
errorMsg = e.reason;
errorStack = "";
}
// 分类解析
siftAndMakeUpMessage("on_error", errorMsg, WEB_LOCATION, 0, 0, "UncaughtInPromiseError: " + errorStack);
}
window.onunhandledrejection 能够捕获到Promise未处理的rejection异常,rejection异常并不会阻断页面运行,容易被很多小伙伴所遗忘,所以我们监控了此类型的错误。
- 重写 console.error 方法
// 重写console.error, 可以捕获更全面的报错信息
var oldError = console.error;
console.error = function (tempErrorMsg) {
var errorMsg = (arguments[0] && arguments[0].message) || tempErrorMsg;
var lineNumber = 0;
var columnNumber = 0;
var errorObj = arguments[0] && arguments[0].stack;
if (!errorObj) {
if (typeof errorMsg == "object") {
try {
errorMsg = JSON.stringify(errorMsg)
} catch(e) {
errorMsg = "错误无法解析"
}
}
siftAndMakeUpMessage("console_error", errorMsg, WEB_LOCATION, lineNumber, columnNumber, "CustomizeError: " + errorMsg);
} else {
// 如果报错中包含错误堆栈,可以认为是JS报错,而非自定义报错
siftAndMakeUpMessage("on_error", errorMsg, WEB_LOCATION, lineNumber, columnNumber, errorObj);
}
return oldError.apply(console, arguments);
};
console.error 是用来打印警告日志,所以我将其归类为自定义异常。一般像前端框架、引入第三方的插件都会用 console.error 来打印较为严重的警告信息,而我在工作中也会将后台抛出的错误信息(非后台异常)用console.error打印出来,上报到监控系统里。这样对排查异常也是有很大作用的(这一点会在行为记录查询中有体现)。
2. 存储并上报错误
Javascript错误产生后,应该存入浏览器的缓存中,然后定时上传,如果实时上传,将会对服务器造成压力。通过接口将Js错误信息上传到服务器,由后台server对数据进行清洗分类,然后再进行持久化存储。
// 设置日志对象类的通用属性
function setCommonProperty() {
this.wmVersion = WM_VERSION; // 探针版本号
this.happenTime = new Date().getTime(); // 日志发生时间
this.webMonitorId = WEB_MONITOR_ID; // 用于区分应用的唯一标识(一个项目对应一个)
this.simpleUrl = window.location.href.split('?')[0].replace('#', ''); // 页面的url
this.completeUrl = utils.b64EncodeUnicode(encodeURIComponent(window.location.href)); // 页面的完整url
this.customerKey = utils.getCustomerKey(); // 用于区分用户,所对应唯一的标识,清理本地数据后失效,
// 用户自定义信息, 由开发者主动传入, 便于对线上问题进行准确定位
var wmUserInfo = lsg.wmUserInfo ? JSON.parse(lsg.wmUserInfo) : {};
this.userId = wmUserInfo.userId;
this.firstUserParam = utils.b64EncodeUnicode(wmUserInfo.firstUserParam || "");
this.secondUserParam = utils.b64EncodeUnicode(wmUserInfo.secondUserParam || "");
}
// JS错误日志,继承于日志基类MonitorBaseInfo
function JavaScriptErrorInfo(uploadType, infoType, errorMsg, errorStack) {
setCommonProperty.apply(this);
this.uploadType = uploadType;
this.infoType = infoType;
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 = utils.getCookie("webfunny_ip"); // 用户的IP地址
this.country = "china"; // 用户所在国家
this.province = ""; // 用户所在省份
this.city = ""; // 用户所在城市
this.errorMessage = utils.b64EncodeUnicode(errorMsg)
this.errorStack = utils.b64EncodeUnicode(errorStack);
this.browserInfo = "";
}
Js错误信息需要包含系统版本号、应用版本号、平台信息、页面Url、错误信息、错误堆栈、发生时间等等,这样才能帮助我们准确定位。
3. 分析并聚合错误
首先,我们对捕获的异常类型进行了分类(TypeError、ReferenceError、UncaughtInPromiseError),这样错误类型可以一目了然。
同时我们对发生错误的操作系统(Android、ios、Pc)进行了分类统计,比如截图中的第一个错误,就只会在苹果手机上发生,排查范围也就缩小了很多。
另外,我们把错误影响的人数也统计出来,就可以知道这个错误影响了多少用户,从而确定修复的优先级。
4. 发送错误报警
这一步属于监控的附加功能,主要包括邮箱、钉钉、短信等消息通知。
5. 定位并解决JS错误
针对某一个错误,我们需要分析它发生的平台,影响的人数,系统版本,网络环境等等,同时也需要分析最为重要的一步,就是代码的位置。
首先,我们分析了错误发生的具体时间、发生次数、影响人数、IP地址、浏览器版本、操作系统等环境因素
其次,我们还需统计这个报错发生的时间曲线,如果是大量报错,我们可以很容易定位到错误发生的起始点,针对那个时间点,对报错的原因进行定位。
留言