Анализ исходного кода баджей (интерфейсное решение для мониторинга)

GitHub исходный код

Недавно я исследовал решение для внешнего мониторинга, и из-за необходимости изучить исходный код badjs Xia Goose Factory я в основном читал отчет о внешнем интерфейсе, который называется badjs-report. Об использовании баджей см. ниже.официальная документация

Фронтенд-мониторинг болевых точек

Прежде чем разбираться в фреймворке или библиотеке, подумайте, какую проблему они пытаются решить.Внедрение фронтальной системы аномального мониторингаВ этой статье более подробно описаны проблемы, которые необходимо решить при внешнем мониторинге, а также приведено следующее:

  1. перехват ошибок
  2. сообщить об ошибке
  3. Автономное хранилище журнала ошибок
  4. Ошибка воспроизведения пути
  5. Фон управления визуализацией журнала
  6. Исходное расположение сжатых однострочных файлов
  7. Напоминание по электронной почте (SMS)

За исключением четвертого и шестого пунктов, вышеперечисленные функции реализованы в badjs2. Среди них перехват ошибок, отчет об ошибках и автономное хранение журнала ошибок реализуются интерфейсным компонентом badjs-report. Код badjs-report имеет три основных элемента: инициализация инициализации, перезапись при ошибке и отчет 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() — открыть базу данных и установить события прослушивателя успеха и обновления.

// 打开数据库
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 });
   }
};

переписать при ошибке

После инициализации BJreport необходимо переписать window.onerror, чтобы перехватывать ошибки, возникающие в программе. Переписанный onerror в основном форматирует сообщение об ошибке и помещает ошибку в очередь ошибок, в то же время метод 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);
};

Функция создания отчетов о баджах в основном реализуется через _process_log(), включая случайные отчеты, отчеты об игнорировании, автономное хранение журнала и отложенные отчеты. Сначала объект ошибки будет помещен в _log_list при отправке, а затем _process_log() будет циклически очищать _log_list.

Сначала решите, следует ли игнорировать отчет в соответствии со случайной конфигурацией.

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

В каждом цикле сначала определите, не превышено ли количество повторных отчетов

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

Затем следуйте игнорируйте пользовательские правила для фильтрации

// 格式化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;
        }
    }
}

Затем сохраните автономные журналы в базе данных и поместите журналы, о которых необходимо сообщать, в 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() для создания отчетов используется новый тег 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() в обратном вызове и, наконец, загружает данные посредством отправки формы.

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, любые вопросы можно задавать мне~~