@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
text/typescript
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
}
}