spy-client
Version:
spy client
377 lines (368 loc) • 14.6 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.__spyHead = factory());
})(this, (function () { 'use strict';
/**
* @file utils
* @author kaivean
*/
function getUrlInfoFromURL(url) {
if (URL && url) {
try {
var obj = new URL(url);
if (obj.host !== undefined) {
return {
protocol: obj.protocol,
host: obj.host,
pathname: obj.pathname,
ext: '',
};
}
}
catch (e) {
console.error(e);
}
}
return;
}
function getUrlInfo(url) {
var info = getUrlInfoFromURL(url);
if (!info) {
var parser = document.createElement('a');
parser.href = url;
info = {
protocol: parser.protocol,
host: parser.host || location.host,
pathname: parser.pathname,
ext: '',
};
}
var split = info.pathname.split('.');
info.ext = split[split.length - 1];
return info;
}
function f(n) {
return +n.toFixed(1);
}
function getResTiming(t) {
return {
wait: f(t.domainLookupStart - (t.navigationStart || t.fetchStart || t.startTime)),
dns: f(t.domainLookupEnd - t.domainLookupStart),
connect: f(t.connectEnd - t.connectStart),
req: f(t.responseStart - t.requestStart),
res: f(t.responseEnd - t.responseStart),
};
}
function getxpath(el) {
if (!el) {
return { xpath: '' };
}
var xpath = [];
while (el && el.nodeType === 1 && el !== el.parentNode) {
var t = el.tagName.toLowerCase();
if (el.getAttribute('id')) {
t += '[#' + el.getAttribute('id') + ']';
}
else if (el.classList && el.classList.length) {
t += '[.' + el.classList[el.classList.length - 1] + ']';
}
xpath.push(t);
if (el === document.body) {
break;
}
el = el.parentNode; // 修复缺陷检查
}
return {
xpath: xpath.join('<'),
};
}
/**
* @file 资源、JS、白屏监控的基础
* @author kaivean
*/
var spyHead = {
conf: {},
winerrors: [],
errorDestroy: function () { },
observerDestroy: function () { },
entryMap: {},
init: function (conf) {
this.conf = conf;
},
addError: function (obj) {
// 有些错误一下出现很多次,都聚合都一个错误,加上次数
if (this.winerrors.length > 0) {
var lastObj = this.winerrors[this.winerrors.length - 1];
if (obj.info.msg === lastObj.info.msg) {
lastObj.info.count += (lastObj.info.count || 0);
return;
}
}
if (this.winerrors.length < 1000) {
this.winerrors.push(obj);
}
},
send: function (obj, isSend, logServer) {
var conf = this.conf;
obj.type = obj.type || 'except';
obj.pid = conf.pid;
obj.lid = conf.lid;
obj.ts = Date.now();
this.addError(obj);
this.interceptor && this.interceptor(obj);
if (isSend === false) {
return;
}
logServer = logServer || conf.logServer;
var logUrl = "".concat(logServer, "?pid=").concat(obj.pid, "&lid=").concat(obj.lid, "&ts=").concat(obj.ts)
+ "&type=".concat(obj.type, "&group=").concat(obj.group, "&info=").concat(encodeURIComponent(JSON.stringify(obj.info)));
if (obj.dim) {
logUrl += '&dim=' + encodeURIComponent(JSON.stringify(obj.dim));
}
var img = new Image();
img.src = logUrl;
img.onload = img.onerror = function () {
img = null;
};
},
};
/**
* @file 资源和JS错误监控
* @author kaivean
*/
function init$2(conf) {
var resourceError = conf.resourceError || {};
var jsError = conf.jsError || {};
var isSendJserror = Math.random() < (jsError.sample ? jsError.sample : 0);
var isSendResource = Math.random() < (resourceError.sample ? resourceError.sample : 0);
var winerrors = spyHead.winerrors;
var resourceErrorCount = 0;
function spyListenError(event) {
try {
var el = event.target;
var obj = { info: {}, dim: {}, group: '' };
var info = obj.info;
var srcElement = event.srcElement;
// 设备信息
var dataConnection = navigator.connection || {};
info.downlink = dataConnection.downlink; // 网站下载速度 M/s
info.effectiveType = dataConnection.effectiveType; // 网络类型
info.rtt = dataConnection.rtt; // 网络往返时间 ms
info.deviceMemory = navigator.deviceMemory || 0;
info.hardwareConcurrency = navigator.hardwareConcurrency || 0;
// JS错误
if (srcElement === window) {
obj.group = jsError.group;
// 异常信息
// promise错误从reason取
var error = (event.error || event.reason) || {};
// promise错误从error.message取
info.msg = event.message || error.message || '';
info.file = event.filename;
info.ln = event.lineno;
info.col = event.colno;
info.stack = (error.stack || '').split('\n').slice(0, 3).join('\n');
// 针对esl的MODULE_TIMEOUT处理
if (info.msg.indexOf('MODULE_TIMEOUT') !== -1) {
var matches = info.msg.match(/^.*Hang:(.*); Miss:(.*)/);
if (matches && matches[2]) {
info.msg = 'MODULE_TIMEOUT for miss:' + (matches[2]);
}
}
// 历史错误
var historys = [];
for (var index = 0; index < winerrors.length; index++) {
var item = winerrors[index];
var prefix = item.info.count > 1 ? "(".concat(item.info.count, ")") : '';
historys.push(prefix + item.info.msg);
}
info.hisErrors = historys.join('----');
var allow = true;
if (jsError.handler) {
allow = jsError.handler(obj);
}
if (allow !== false) {
spyHead.send(obj, isSendJserror);
}
}
// 资源错误
else {
obj.group = resourceError.group;
obj.dim.type = srcElement.tagName.toLowerCase();
var url = srcElement.src || srcElement.href || '';
// 日志本身失败,要忽略
if (url.indexOf('/mwb2.gif?') > -1) {
return;
}
info.msg = url || 'unknown load eror';
obj.dim.host = getUrlInfo(url).host;
if (el && el.tagName === 'IMG') {
info.xpath = getxpath(el).xpath;
}
if (resourceErrorCount) {
info.hisErrCount = resourceErrorCount;
}
var allow = true;
if (resourceError.handler) {
allow = resourceError.handler(obj);
}
if (allow !== false) {
spyHead.send(obj, isSendResource);
}
resourceErrorCount++;
}
}
catch (e) {
console.error(e);
}
}
window.addEventListener('error', spyListenError, true);
// Promise未处理拒绝监控
window.addEventListener('unhandledrejection', spyListenError, true);
spyHead.errorDestroy = function () {
window.removeEventListener('error', spyListenError, true);
window.removeEventListener('unhandledrejection', spyListenError, true);
spyHead.winerrors = [];
};
}
/**
* @file PerformanceObserver指标采集
* @author kaivean
*/
function init$1() {
// Longtask监控
if (window.PerformanceObserver) {
var observer_1 = new window.PerformanceObserver(function spyObserveLongtask(list) {
var entryMap = spyHead.entryMap;
var entries = list.getEntries();
for (var i = 0; i < entries.length; i++) {
var entry = entries[i];
if (!entryMap[entry.entryType]) {
entryMap[entry.entryType] = [];
}
entryMap[entry.entryType].push(entry);
}
});
spyHead.observerDestroy = function () {
observer_1.disconnect();
};
// 在ios下,没有一个类似的监控项是支持的,就抛错,chrome会console warn
try {
observer_1.observe({ entryTypes: [
'longtask',
'layout-shift',
'first-input',
'largest-contentful-paint',
] });
}
catch (e) { }
}
}
/**
* @file 白屏监控
* @author kaivean
*/
function init(conf) {
var whiteScreenError = conf.whiteScreenError || {};
var handler = whiteScreenError.handler;
var selector = whiteScreenError.selector;
var subSelector = whiteScreenError.subSelector;
var timeout = whiteScreenError.timeout || 6000;
var isSend = Math.random() < (whiteScreenError.sample ? whiteScreenError.sample : 0);
// 补充白屏信息:期间的网络时间
function getNetTime() {
if (!window.performance) {
return false;
}
var pf = getResTiming(window.performance.timing);
var netStr = "&wait=".concat(pf.wait)
+ "&dns=".concat(pf.dns)
+ "&connect=".concat(pf.connect)
+ "&requestTime=".concat(pf.req)
+ "&resoneTime=".concat(pf.res);
return netStr;
}
// 补充白屏信息:期间发生的JS Error 和 资源 Error
function getHisError() {
if (!(spyHead.winerrors)) {
return false;
}
var errors = spyHead.winerrors;
var historys = [];
for (var i = 0; i < errors.length; i++) {
var stack = (errors[i].info.stack || '').split('\n')[0];
historys.push("(".concat(i, ")").concat(stack || errors[i].info.msg));
}
return historys.join(';;');
}
// 补充白屏信息: 设备信息
function getDeviceInfo() {
var ret = {};
// 设备信息
var dataConnection = navigator.connection || {};
ret.downlink = dataConnection.downlink; // 网站下载速度 M/s
ret.effectiveType = dataConnection.effectiveType; // 网络类型
ret.rtt = dataConnection.rtt; // 网络往返时间 ms
ret.deviceMemory = navigator.deviceMemory || 0;
ret.hardwareConcurrency = navigator.hardwareConcurrency || 0;
return ret;
}
function isWhiteScreen() {
var ele = document.querySelector(selector);
if (!ele) {
return true;
}
var sub = ele.querySelector(subSelector);
if (!sub) {
return true;
}
if (ele.clientHeight < (window.innerHeight * 2 / 3)) {
return true;
}
return false;
}
if (selector) {
setTimeout(function () {
if (isWhiteScreen()) {
var obj = {
group: whiteScreenError.group,
info: {
msg: '',
netTime: getNetTime(),
hisErrors: getHisError(),
deviceInfo: getDeviceInfo(),
},
};
obj.info.msg = 'WhiteScren Error';
var allow = true;
if (handler) {
allow = handler(obj);
}
if (allow !== false && obj.info.msg) {
spyHead && spyHead.send(obj, isSend);
}
}
}, timeout);
}
}
/**
* @file SpyClient
* @author kaivean
*/
spyHead.init = function (conf) {
if (!conf.logServer) {
conf.logServer = 'https://sp1.baidu.com/5b1ZeDe5KgQFm2e88IuM_a/mwb2.gif';
}
this.conf = conf;
init$2(conf);
init$1();
init(conf);
};
// 兼容全局变量定义的初始化方式
if (window.__spyclientConf) {
spyHead.init(window.__spyclientConf);
}
window.__spyHead = window.__spyHead || spyHead;
return spyHead;
}));