UNPKG

@sdc-monitor/demo

Version:

<div align="center"> <a href="#" target="_blank"> <img src="https://i.loli.net/2021/07/28/EvPwd4NjVH3tBfO.jpg" alt="mito-logo" height="90"> </a> <p>A Lightweight SDK For Monitor Web</p>

263 lines (247 loc) 8.26 kB
import { BREADCRUMBCATEGORYS, BrowserBreadcrumbTypes, ErrorTypes } from '@sdc-monitor/shared' import { getTimestamp, getUrlWithEnv } from './helpers' import { Severity } from './Severity' /** * 返回包含id、class、innerTextde字符串的标签 * @param target html节点 */ export function htmlElementAsString(target: HTMLElement): string { const tagName = target.tagName.toLowerCase() if (tagName === 'body') { return null } let classNames = target.classList.value classNames = classNames !== '' ? ` class="${classNames}"` : '' const id = target.id ? ` id="${target.id}"` : '' const innerText = target.innerText return `<${tagName}${id}${classNames !== '' ? classNames : ''}>${innerText}</${tagName}>` } /** * 将地址字符串转换成对象 * @returns 返回一个对象 */ export function parseUrlToObj(url: string): { host?: string path?: string protocol?: string relative?: string } { if (!url) { return {} } // eslint-disable-next-line no-useless-escape const match = url.match(/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/) if (!match) { return {} } const query = match[6] || '' const fragment = match[8] || '' return { host: match[4], path: match[5], protocol: match[2], relative: match[5] + query + fragment // everything minus origin } } /** * 根据browser行为类型返回种类 * * @export * @param {BrowserBreadcrumbTypes} type * @return {*} {BREADCRUMBCATEGORYS} */ export function getBreadcrumbCategoryInBrowser(type: BrowserBreadcrumbTypes): BREADCRUMBCATEGORYS { switch (type) { case BrowserBreadcrumbTypes.XHR: case BrowserBreadcrumbTypes.FETCH: return BREADCRUMBCATEGORYS.HTTP case BrowserBreadcrumbTypes.CLICK: case BrowserBreadcrumbTypes.ROUTE: return BREADCRUMBCATEGORYS.USER case BrowserBreadcrumbTypes.CUSTOMER: case BrowserBreadcrumbTypes.CONSOLE: return BREADCRUMBCATEGORYS.DEBUG case BrowserBreadcrumbTypes.UNHANDLEDREJECTION: case BrowserBreadcrumbTypes.CODE_ERROR: case BrowserBreadcrumbTypes.RESOURCE: default: return BREADCRUMBCATEGORYS.EXCEPTION } } /** * 根据时间戳,返回年月日时分秒 * @param timestamp */ export function timestampToTime(timestamp: number): string { // 时间戳为10位需*1000,时间戳为13位不需乘1000 var date = new Date(timestamp); var Y = date.getFullYear() + "-"; var M = (date.getMonth() + 1 < 10 ? "0" + (date.getMonth() + 1) : date.getMonth() + 1) + "-"; var D = (date.getDate() < 10 ? "0" + date.getDate() : date.getDate()) + " "; var h = (date.getHours() < 10 ? "0" + date.getHours() : date.getHours()) + ":"; var m = (date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes()) + ":"; var s = (date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds()); return Y + M + D + h + m + s; } /** * 根据当前入参,处理为接口需要的入参,并返回 * @param data */ export function transformParams(data: any): object { // 如果不属于这些类型,则阻止上报 const type = ['HTTP', 'RESOURCE', 'JAVASCRIPT', 'LOG', 'PROMISE', 'VUE']; if (!(data.data.type && type.includes(data.data.type))) return; // 错误等级转换 const obj = { low: 'INFO', normal: 'WARN', high: 'ERROR', critical: 'ERROR' }; const params = { "level": obj[data.data.level], "authInfo": { "sdkVersion": data.authInfo.sdkVersion, "apikey": data.authInfo.apikey }, "time": timestampToTime(data.data.time), "type": data.data.type, "url": data.data.url }; const errData = new Object(); // 请求的入参 const request = data.data.request; const response = data.data.response; if (data.data.type === 'HTTP') { // 目前请求只需要这些信息 errData['http'] = { "request": { "httpType": request.httpType, "data": request.data || null, "method": request.method, "url": request.url }, "response": { "data": response.data, "status": response.status } }; } // 错误栈入参 const stack = data.data.stack; let transformStack = new Array(); if (data.data.stack) { // vue类型的错误目前是string,需要手动提取错误栈 if (data.data.type === 'VUE' && typeof stack === 'string') { transformStack = extractErrorStack({ stack: stack }, data.data.level)['stack']; } else { transformStack = stack; } // 错误栈,目前只需要这四个信息 errData['stack'] = transformStack.map((i) => { return { "func": i.func, "line": i.line, "column": i.column, "message": data.data.message }; }); } params['data'] = errData; return params; } /** * 解析error的stack,并返回args、column、line、func、url: * wx AppOnError use too * @param ex * @param level */ export function extractErrorStack(ex: any, level: Severity) { const normal = { time: getTimestamp(), url: getUrlWithEnv(), name: ex.name, level, message: ex.message } if (typeof ex.stack === 'undefined' || !ex.stack) { return normal } const chrome = /^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|webpack|<anonymous>|[a-z]:|\/).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i, gecko = /^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|webpack|resource|\[native).*?|[^@]*bundle)(?::(\d+))?(?::(\d+))?\s*$/i, winjs = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i, // Used to additionally parse URL/line/column from eval frames geckoEval = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i, chromeEval = /\((\S*)(?::(\d+))(?::(\d+))\)/, lines = ex.stack.split('\n'), stack = [] let submatch, parts, element // reference = /^(.*) is undefined$/.exec(ex.message) for (let i = 0, j = lines.length; i < j; ++i) { if ((parts = chrome.exec(lines[i]))) { const isNative = parts[2] && parts[2].indexOf('native') === 0 // start of line const isEval = parts[2] && parts[2].indexOf('eval') === 0 // start of line if (isEval && (submatch = chromeEval.exec(parts[2]))) { // throw out eval line/column and use top-most line/column number parts[2] = submatch[1] // url parts[3] = submatch[2] // line parts[4] = submatch[3] // column } element = { url: !isNative ? parts[2] : null, func: parts[1] || ErrorTypes.UNKNOWN_FUNCTION, args: isNative ? [parts[2]] : [], line: parts[3] ? +parts[3] : null, column: parts[4] ? +parts[4] : null } } else if ((parts = winjs.exec(lines[i]))) { element = { url: parts[2], func: parts[1] || ErrorTypes.UNKNOWN_FUNCTION, args: [], line: +parts[3], column: parts[4] ? +parts[4] : null } } else if ((parts = gecko.exec(lines[i]))) { const isEval = parts[3] && parts[3].indexOf(' > eval') > -1 if (isEval && (submatch = geckoEval.exec(parts[3]))) { // throw out eval line/coluqqqqqqqqqqqqqqqqqqqqqqqqqqqqmn and use top-most line number parts[3] = submatch[1] parts[4] = submatch[2] parts[5] = null // no column when eval } else if (i === 0 && !parts[5] && typeof ex.columnNumber !== 'undefined') { // FireFox uses this awesome columnNumber property for its top frame // Also note, Firefox's column number is 0-based and everything else expects 1-based, // so adding 1 // NOTE: this hack doesn't work if top-most frame is eval stack[0].column = ex.columnNumber + 1 } element = { url: parts[3], func: parts[1] || ErrorTypes.UNKNOWN_FUNCTION, args: parts[2] ? parts[2].split(',') : [], line: parts[4] ? +parts[4] : null, column: parts[5] ? +parts[5] : null } } else { continue } if (!element.func && element.line) { element.func = ErrorTypes.UNKNOWN_FUNCTION } stack.push(element) } if (!stack.length) { return null } return { ...normal, stack: stack } }