UNPKG

spy-client

Version:

spy client

377 lines (368 loc) 14.6 kB
(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; }));