Недавно я исследовал решение для внешнего мониторинга, и из-за необходимости изучить исходный код badjs Xia Goose Factory я в основном читал отчет о внешнем интерфейсе, который называется badjs-report. Об использовании баджей см. ниже.официальная документация
Фронтенд-мониторинг болевых точек
Прежде чем разбираться в фреймворке или библиотеке, подумайте, какую проблему они пытаются решить.Внедрение фронтальной системы аномального мониторингаВ этой статье более подробно описаны проблемы, которые необходимо решить при внешнем мониторинге, а также приведено следующее:
- перехват ошибок
- сообщить об ошибке
- Автономное хранилище журнала ошибок
- Ошибка воспроизведения пути
- Фон управления визуализацией журнала
- Исходное расположение сжатых однострочных файлов
- Напоминание по электронной почте (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, любые вопросы можно задавать мне~~