博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
浅析badjs源码(前端监控方案)
阅读量:5930 次
发布时间:2019-06-19

本文共 6594 字,大约阅读时间需要 21 分钟。

最近在研究前端监控方案,由于工作需要研究了下鹅厂的badjs源码,主要是看了前端上报这一块,也就是badjs-report。关于badjs的使用可以看下

前端监控痛点

了解一个框架或者库之前要先思考它想解决的是什么问题。这篇文章比较详细地总结了前端监控所需要解决的问题,总结了下有:

  1. 错误拦截
  2. 上报错误
  3. 离线错误日志存储
  4. 错误路径回放
  5. 日志可视化管理后台
  6. 压缩单行文件的源码定位
  7. 邮箱(短信)提醒

上面的功能除了第四点和第六点,badjs2都已经实现到。其中错误拦截、上报错误和离线错误日志存储是由前端组件badjs-report来实现的。而badjs-report的代码主要有三大入口:init初始化、onerror改写和reportOfflinelog上报离线日志。下面将一一介绍这三大入口如何调用其他函数并实现功能(限于篇幅限制,下面贴的代码有所删减,可结合源码理解)。

BJ_REPORT.init初始化

badjs-report是在全局对象中插入BJ_REPORT对象,它提供了init()来进行初始化,该函数方法接受一个对象作为配置参数。

首先是将传入的配置参数对象的值覆盖私有_config对象的值。

init: function(config) {	if (T.isOBJ(config)) {		// 遍历覆盖        for (var key in config) {            _config[key] = config[key];        }    }}复制代码

接着拼接上报url和清空错误缓存。

// 没有设置id将不上报var id = parseInt(_config.id, 10);if (id) {    _config._reportUrl = (_config.url || "/badjs") +        "?id=" + id +        "&uin=" + _config.uin +        "&";}// 清空错误列表,_process_log函数会在下面讲到if (_log_list.length) {	_process_log();}复制代码

接着初始化indexedDB数据库。badjs是将离线日志信息存储于indexedDB数据库中,然后通过调用reportOfflineLog()方法来上传离线日志。

if (!Offline_DB._initing) {    Offline_DB._initing = true;    Offline_DB.ready(function(err, DB) {        if (DB) {            setTimeout(function() {		        // 清除过期日志                DB.clearDB(_config.offlineLogExp);                setTimeout(function() {                    _config.offlineLogAuto && _autoReportOffline();                }, 5000);            }, 1000);        }    });}复制代码

Offline_DB.ready()的主要工作是打开数据库并设置success和upgradeneeded监听事件

// 打开数据库var request = window.indexedDB.open("badjs", version);// 打开成功request.onsuccess = function(e) {    self.db = e.target.result;    // 打开成功后执行回调    setTimeout(function() {        callback(null, self);    }, 500);};// 版本升级(初始化时会先触发upgradeneeded,再触发success)request.onupgradeneeded = function(e) {   var db = e.target.result;   if (!db.objectStoreNames.contains('logs')) {       db.createObjectStore('logs', { autoIncrement: true });   }};复制代码

改写onerror

在BJreport初始化后就需要来改写window.onerror,以便捕获到程序发生的错误。重写后的onerror主要是格式化错误信息,并把错误push进错误队列中,同时push()方法也会触发_process_log()。

var orgError = global.onerror;global.onerror = function(msg, url, line, col, error) {    var newMsg = msg;	// 格式化错误信息    if (error && error.stack) {        newMsg = T.processStackMsg(error);    }    if (T.isOBJByType(newMsg, "Event")) {        newMsg += newMsg.type ?            ("--" + newMsg.type + "--" + (newMsg.target ?                (newMsg.target.tagName + "::" + newMsg.target.src) : "")) : "";    }    // 将错误信息对象推入错误队列中,执行_process_log方法进行上报    report.push({        msg: newMsg,        target: url,        rowNum: line,        colNum: col,        _orgMsg: msg    });    _process_log();    // 调用原有的全局onerror事件    orgError && orgError.apply(global, arguments);};复制代码

badjs上报的功能主要通过_process_log()来实现,有随机上报、忽略上报、离线日志存储和延迟上报。首先在push的时候会把错误对象push进_log_list,然后_process_log()会循环清空_log_list。

先根据config的random来决定是否忽略该次上报

// 取随机数,来决定是否忽略该次上报var randomIgnore = Math.random() >= _config.random;复制代码

每次循环时先判断是否超过重复上报数

// 重复上报if (T.isRepeat(report_log)) continue;复制代码

然后按照用户定义的ignore规则进行筛选

// 格式化log信息var log_str = _report_log_tostring(report_log, submit_log_list.length);// 若用户自定义了ignore规则,则按照规则进行筛选if (T.isOBJByType(_config.ignore, "Array")) {    for (var i = 0, l = _config.ignore.length; i < l; i++) {        var rule = _config.ignore[i];        if ((T.isOBJByType(rule, "RegExp") && rule.test(log_str[1])) ||            (T.isOBJByType(rule, "Function") && rule(report_log, log_str[1]))) {            isIgnore = true;            break;        }    }}复制代码

接着将离线日志存入数据库,将需要上报的日志push进submit_log_list

// 通过了ignore规则if (!isIgnore) {    // 若离线日志功能已开启,则将日志存入数据库    _config.offlineLog && _save2Offline("badjs_" + _config.id + _config.uin, report_log);    // level为20表示是offlineLog方法push进来的,只存入离线日志而不上报    if (!randomIgnore && report_log.level != 20) {        // 若可以上报,则推入submit_log_list,稍后由_submit_log方法来清空该队列并上报        submit_log_list.push(log_str[0]);        // 执行上报回调函数        _config.onReport && (_config.onReport(_config.id, report_log));    }}复制代码

循环结束后根据需要进行上报或者延迟上报

if (isReportNow) {  _submit_log(); // 立即上报} else if (!comboTimeout) {    comboTimeout = setTimeout(_submit_log, _config.delay); // 延迟上报}复制代码

在_submit_log()方法中,采用的是new一个img标签来进行上报

var _submit_log = function() {    // 若用户自定义了上报方法,则使用自定义方法    if (_config.submit) {        _config.submit(url, submit_log_list);    } else {        // 否则使用img标签上报        var _img = new Image();        _img.src = url;    }    submit_log_list = [];};复制代码

上传离线日志

badjs需要用户主动调用BJ_REPORT.reportOfflineLog()方法来上传数据库中的离线日志。

reportOfflineLog()方法首先是调用Offline_DB.ready打开数据库,然后在回调中通过DB.getLogs()来获取到数据库中的日志,最后通过form表单提交来上传数据。

reportOfflineLog: function() {    Offline_DB.ready(function(err, DB) {        // 日期要求是startDate ~ endDate        var startDate = new Date - 0 - _config.offlineLogExp * 24 * 3600 * 1000;        var endDate = new Date - 0;        DB.getLogs({            start: startDate,            end: endDate,            id: _config.id,            uin: _config.uin        }, function(err, result) {            var iframe = document.createElement("iframe");            iframe.name = "badjs_offline_" + (new Date - 0);            iframe.frameborder = 0;            iframe.height = 0;            iframe.width = 0;            iframe.src = "javascript:false;";            iframe.onload = function() {                var form = document.createElement("form");                form.style.display = "none";                form.target = iframe.name;                form.method = "POST";                form.action = _config.offline_url || _config.url.replace(/badjs$/, "offlineLog");                form.enctype.method = 'multipart/form-data';                var input = document.createElement("input");                input.style.display = "none";                input.type = "hidden";                input.name = "offline_log";                input.value = JSON.stringify({ logs: result, userAgent: navigator.userAgent, startDate: startDate, endDate: endDate, id: _config.id, uin: _config.uin });                iframe.contentDocument.body.appendChild(form);                form.appendChild(input);                // 通过form表单提交来上报离线日志                form.submit();                setTimeout(function() {                    document.body.removeChild(iframe);                }, 10000);                iframe.onload = null;            };            document.body.appendChild(iframe);        });    });}复制代码

结语

为了防止篇幅过长,上述源码我做了一些删减,如果想看完整源码可以看下我自己加了中文注释的版本https://github.com/Q-Zhan/badjs-report-annotated,有任何问题都可以提issue给我~~

写在最后

我个人开了一个公众号“前端搬运小工”,我会定期推送优秀的前端精选文章,拒绝无脑基础入门的文章,带给你不一样的前端视角。

转载于:https://juejin.im/post/5b434402f265da0f7d4edf77

你可能感兴趣的文章
IntersectionObserver + Custom Elements 实现图片懒加载(滚动加载)/点击重试
查看>>
KSImageNamed-Xcode-master
查看>>
memcache
查看>>
Struts2参数知识点
查看>>
tomcat 8.0虚拟机配置文档
查看>>
轻松实现基于Heartbeat的高可用web服务集群
查看>>
分析y一款APP
查看>>
pxc群集搭建
查看>>
JS中加载cssText延时
查看>>
常用的脚本编程知识点
查看>>
坐标转换convertRect
查看>>
XILINX_zynq_详解(6)
查看>>
ubuntu安装LDAP
查看>>
计算机网络术语总结4
查看>>
新手小白 python之路 Day3 (string 常用方法)
查看>>
求职路 第二章 深圳篇
查看>>
如何限制青少年无节制的玩电脑--使用智能卡登录系统
查看>>
HTML5 Geolocation API工作原理[转载]
查看>>
soapUI的简单使用(webservice接口功能测试)
查看>>
框架 Hibernate
查看>>