UNPKG

hc-web-log-mon

Version:

基于 JS 跨平台插件,为前端项目提供【 行为、性能、异常、请求、资源、路由、曝光、录屏 】监控手段

305 lines (268 loc) 9.31 kB
import type { InternalOptions, AnyFun, InitOptions, VoidFun } from '../types' import { typeofAny, deepAssign } from '../utils' import { isEmpty } from '../utils/is' import { _support } from '../utils/global' import { logError } from '../utils/debug' import { ref } from '../observer' import type { ObserverValue } from '../observer/types' /** * 管理全局的参数 */ export class Options implements InternalOptions { dsn = '' // 上报地址 appName = '' // 应用名称 appCode = '' // 应用code appVersion = '' // 应用版本 userUuid = '' // 用户id(外部填充进来的id) sdkUserUuid = '' // 用户id(sdk内部生成的id) debug = false // 是否开启调试模式(控制台会输出sdk动作) pv = { core: false // 页面跳转-是否自动发送页面跳转相关数据 } performance = { core: false, // 性能数据-是否采集静态资源、接口的相关数据 firstResource: false, // 性能数据-是否采集首次进入页面的数据(ps: tcp连接耗时,HTML加载完成时间,首次可交互时间) server: false // 接口请求-是否采集接口请求(成功的才会采集) } error = { core: false, // 是否采集异常数据(ps: 资源引入错误,promise错误,控制台输出错误) server: false // 接口请求-是否采集报错接口数据 } event = { core: false // 页面点击-是否采集点击事件 } recordScreen = true // 是否启动录屏 ext = {} // 自定义全局附加参数(放在baseInfo中) tracesSampleRate = 1 // 抽样发送 cacheMaxLength = 5 // 上报数据最大缓存数 cacheWatingTime = 5000 // 上报数据最大等待时间 ignoreErrors = [] // 错误类型事件过滤 ignoreRequest: Array<string | RegExp> = [] // 请求类型事件过滤 scopeError = false // 当某个时间段报错时,会将此类错误转为特殊错误类型,会新增错误持续时间范围 localization = false // 是否本地化:sdk不再主动发送事件,事件都存储在本地,由用户手动调用方法发送 sendTypeByXmlBody = false // 是否强制指定发送形式为xml,body请求方式 // whiteScreen = false // 开启白屏检测 // 添加到行为列表前的 hook (在这里面可以给出错误类型,然后就能达到用户想拿到是何种事件类型的触发) beforePushEventList: AnyFun[] = [] // 数据上报前的 hook beforeSendData: AnyFun[] = [] // 数据上报后的 hook afterSendData: VoidFun[] = [] // 本地化存储溢出后的回调 localizationOverFlow: VoidFun = () => { // do something } constructor(initOptions: InitOptions) { const _options = this.transitionOptions(initOptions) _options.ignoreRequest.push(new RegExp(_options.dsn)) deepAssign<Options>(this, _options) } /** * 对入参配置项进行转换 */ private transitionOptions(options: InitOptions): Options { const _options = deepAssign<Options>({}, this, options) const { beforePushEventList, beforeSendData, afterSendData } = options const { pv, performance, error, event } = _options if (typeof pv === 'boolean') { _options.pv = { core: pv } } if (typeof performance === 'boolean') { _options.performance = { core: performance, firstResource: performance, server: performance } } if (typeof error === 'boolean') { _options.error = { core: error, server: error } } if (typeof event === 'boolean') { _options.event = { core: event } } if (beforePushEventList) { _options.beforePushEventList = [beforePushEventList] } if (beforeSendData) { _options.beforeSendData = [beforeSendData] } if (afterSendData) { _options.afterSendData = [afterSendData] } return _options } } function _validateInitOption(options: InitOptions) { const { dsn, appName, appCode, appVersion, userUuid, debug, recordScreen, pv, performance, error, event, ext, tracesSampleRate, cacheMaxLength, cacheWatingTime, ignoreErrors, ignoreRequest, scopeError, localization, sendTypeByXmlBody, // whiteScreen, beforePushEventList, beforeSendData } = options const validateFunList = [] if (pv && typeof pv === 'object') { validateFunList.push(validateOption(pv.core, 'pv.core', 'boolean')) } else { validateFunList.push(validateOption(pv, 'pv', 'boolean')) } if (performance && typeof performance === 'object') { validateFunList.push( validateOption(performance.core, 'performance.core', 'boolean'), validateOption( performance.firstResource, 'performance.firstResource', 'boolean' ), validateOption(performance.server, 'performance.server', 'boolean') ) } else { validateFunList.push(validateOption(performance, 'performance', 'boolean')) } if (error && typeof error === 'object') { validateFunList.push( validateOption(error.core, 'error.core', 'boolean'), validateOption(error.server, 'error.server', 'boolean') ) } else { validateFunList.push(validateOption(error, 'error', 'boolean')) } if (event && typeof event === 'object') { validateFunList.push(validateOption(event.core, 'event.core', 'boolean')) } else { validateFunList.push(validateOption(event, 'event', 'boolean')) } const validateList = [ validateOption(dsn, 'dsn', 'string'), validateOption(appName, 'appName', 'string'), validateOption(appCode, 'appCode', 'string'), validateOption(appVersion, 'appVersion', 'string'), validateOption(userUuid, 'userUuid', 'string'), validateOption(debug, 'debug', 'boolean'), validateOption(recordScreen, 'recordScreen', 'boolean'), validateOption(ext, 'ext', 'object'), validateOption(tracesSampleRate, 'tracesSampleRate', 'number'), validateOption(cacheMaxLength, 'cacheMaxLength', 'number'), validateOption(cacheWatingTime, 'cacheWatingTime', 'number'), validateOption(ignoreErrors, 'ignoreErrors', 'array'), validateOptionArray(ignoreErrors, 'ignoreErrors', ['string', 'regexp']), validateOption(ignoreRequest, 'ignoreRequest', 'array'), validateOptionArray(ignoreRequest, 'ignoreRequest', ['string', 'regexp']), validateOption(scopeError, 'scopeError', 'boolean'), validateOption(localization, 'localization', 'boolean'), validateOption(sendTypeByXmlBody, 'sendTypeByXmlBody', 'boolean'), // validateOption(whiteScreen, 'whiteScreen', 'boolean'), validateOption(beforePushEventList, 'beforePushEventList', 'function'), validateOption(beforeSendData, 'beforeSendData', 'function') ] return validateList.every(res => !!res) } /** * 验证必填项 * @param options 入参对象 */ function _validateMustFill(options: InitOptions) { const validateList = [ validateOptionMustFill(options.appName, 'appName'), validateOptionMustFill(options.dsn, 'dsn') ] return validateList.every(res => !!res) } /** * 验证必填项 * @param target 属性值 * @param targetName 属性名 * @returns 是否通过验证 */ function validateOptionMustFill(target: any, targetName: string): boolean { if (isEmpty(target)) { logError(`【${targetName}】参数必填`) return false } return true } /** * 验证选项的类型是否符合要求 * @param target 源对象 * @param targetName 对象名 * @param expectType 期望类型 * @returns 是否通过验证 */ function validateOption( target: any, targetName: string, expectType: string ): boolean | void { if (!target || typeofAny(target) === expectType) return true logError( `TypeError:【${targetName}】期望传入${expectType}类型,目前是${typeofAny( target )}类型` ) return false } /** * 验证选项的类型 - 针对数组内容类型的验证 * @param target 源对象 * @param targetName 对象名 * @param expectTypes 期望类型 * @returns 是否通过验证 */ function validateOptionArray( target: any[] | undefined, targetName: string, expectTypes: string[] ): boolean | void { if (!target) return true let pass = true target.forEach(item => { if (!expectTypes.includes(typeofAny(item))) { logError( `TypeError:【${targetName}】数组内的值期望传入${expectTypes.join( '|' )}类型,目前值${item}${typeofAny(item)}类型` ) pass = false } }) return pass } export let options: ObserverValue<InternalOptions> /** * 初始化参数 * @param initOptions 原始参数 * @returns 是否初始化成功 */ export function initOptions(initOptions: InitOptions): boolean { // 必传校验 && 入参类型校验 if (!_validateMustFill(initOptions) || !_validateInitOption(initOptions)) return false options = ref(new Options(initOptions)) _support.options = options return true }