├── .gitignore ├── .vscode └── settings.json ├── README.md ├── README的副本.md ├── babel.config.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html └── src ├── App.vue ├── assets └── images │ └── sdk_plan.png ├── main.js ├── monitor ├── errors │ └── global-error.js ├── globalEvent │ └── index.js ├── hook │ └── index.js ├── index.js ├── indexedDb │ └── index.js ├── listener │ ├── event.js │ ├── fetch.js │ └── index.js ├── mixins │ └── index.js ├── processError │ └── index.js └── utils │ ├── browser.js │ ├── proccessUtils.js │ └── proccessVariables.js ├── router └── index.js └── views └── monitor └── index.vue /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # monitor 2 | monitor库用于前端错误监控并上报到服务端,便于查找错误和维护 3 | 4 | # sdk功能规划 5 | 监控console 监控event 监控fetch 6 | 监控脚本 监控history 监控页面可见效 7 | 8 | # 功能规划图 9 | -------------------------------------------------------------------------------- /README的副本.md: -------------------------------------------------------------------------------- 1 | # catch-error-monitor 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Customize configuration 19 | See [Configuration Reference](https://cli.vuejs.org/config/). 20 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "catch-error-monitor", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build" 8 | }, 9 | "dependencies": { 10 | "core-js": "^3.6.5", 11 | "vue": "^2.6.11", 12 | "vue-router": "^3.2.0" 13 | }, 14 | "devDependencies": { 15 | "@vue/cli-plugin-babel": "~4.5.0", 16 | "@vue/cli-plugin-router": "~4.5.0", 17 | "@vue/cli-service": "~4.5.0", 18 | "sass": "^1.26.5", 19 | "sass-loader": "^8.0.2", 20 | "vue-template-compiler": "^2.6.11" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monitor-by-mc/monitor/5d819ec5428e9241814add2a6d29ed2defbf0511/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <%= htmlWebpackPlugin.options.title %> 10 | 11 | 12 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/images/sdk_plan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monitor-by-mc/monitor/5d819ec5428e9241814add2a6d29ed2defbf0511/src/assets/images/sdk_plan.png -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | 5 | import Monitor from './monitor/index' 6 | 7 | const monitor = new Monitor({ 8 | reportUrl: 'http://baidu.com', 9 | delay: 3000, 10 | uid: 'myy', 11 | offLine: true, 12 | // beforeProccess: (error) => console.log('beforeProccess', error), 13 | // afterProccess: (error) => console.log('afterProccess', error), 14 | vueConstrutor: Vue, 15 | reportWarn: true, 16 | reportType: 'navigator' 17 | }) 18 | 19 | // console.log(monitor) 20 | // check push 21 | 22 | // console.log(a) 23 | 24 | new Vue({ 25 | router, 26 | render: (h) => h(App) 27 | }).$mount('#app') 28 | -------------------------------------------------------------------------------- /src/monitor/errors/global-error.js: -------------------------------------------------------------------------------- 1 | export function initGlobalError(Monitor) { 2 | Monitor.prototype._catchGlobalError = function () { 3 | if (typeof window === 'undefined') return 4 | const config = this._config 5 | // 获取全局错误 6 | window.addEventListener('error', (error) => { 7 | console.log('window error and before proccess: ', error) 8 | //执行钩子函数 9 | beforeProccess(this, config) 10 | //正式处理错误 11 | this._proccessError('windowError', error) 12 | //执行钩子函数 13 | afterProccess(this, config) 14 | }, true) 15 | 16 | //获取 Promise 没 reject 的错误 17 | window.addEventListener('unhandledrejection', (error) => { 18 | // 这个事件对象有两个特殊的属性: 19 | console.log('unhandledrejection: ', error) 20 | beforeProccess(this, config) 21 | //正式处理错误 22 | this._proccessError('unhandledrejection', error) 23 | //执行钩子函数 24 | afterProccess(this, config) 25 | }) 26 | 27 | //获取 Vue 的全局错误 28 | let that = this 29 | if (config.vueConstrutor !== null) { 30 | if (typeof config.vueConstrutor === 'function') { 31 | const Vue = config.vueConstrutor 32 | /** 33 | * @param {*} err handle error 34 | * @param {*} vm 35 | * @param {*} info `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子 36 | */ 37 | Vue.config.errorHandler = function (err, vm, info) { 38 | //执行钩子函数 39 | beforeProccess(that, config) 40 | that._proccessError('vueError', err, vm, info) 41 | afterProccess(that, config) 42 | } 43 | 44 | /** 45 | * @param {*} msg 46 | * @param {*} vm 47 | * @param {*} trace 是组件的继承关系追踪 48 | */ 49 | Vue.config.warnHandler = function (msg, vm, trace) { 50 | if (config.reportWarn) { 51 | beforeProccess(that, config) 52 | that._proccessError('vueWarn', msg, vm, trace) 53 | afterProccess(that, config) 54 | } 55 | } 56 | } else { 57 | console.error(`[monitor error]: vueConstrutor must be Function Vue`) 58 | } 59 | } 60 | } 61 | } 62 | 63 | function beforeProccess(instance, config) { 64 | if (config.beforeProccess && typeof config.beforeProccess === 'function') { 65 | instance.beforeProccess(error) 66 | } 67 | } 68 | 69 | function afterProccess(instance, config) { 70 | if (config.afterProccess && typeof config.afterProccess === 'function') { 71 | instance.afterProccess(error) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/monitor/globalEvent/index.js: -------------------------------------------------------------------------------- 1 | export function initListener() { 2 | if(!window) return 3 | window.addEventListener('unload', () => { 4 | 5 | }) 6 | } -------------------------------------------------------------------------------- /src/monitor/hook/index.js: -------------------------------------------------------------------------------- 1 | export function beforeProccess(error) { 2 | this._config.beforeProccess(error) 3 | } 4 | 5 | export function afterProccess(error) { 6 | this._config.afterProccess(error) 7 | } 8 | -------------------------------------------------------------------------------- /src/monitor/index.js: -------------------------------------------------------------------------------- 1 | import { initMixin } from './mixins/index' 2 | import { initGlobalError } from './errors/global-error' 3 | import listen from './listener' 4 | 5 | function Monitor(options, addReportOptions) { 6 | if ( 7 | Object.prototype.toString.call(options) !== '[object Object]' || 8 | JSON.stringify(options) === '{}' 9 | ) { 10 | throw new Error( 11 | '[Monitor init error]: options is not an object type or options is an empty object' 12 | ) 13 | } 14 | this._initConfig(options, addReportOptions) 15 | this._initMethod() 16 | this._initIndexedDb() 17 | this._catchGlobalError() 18 | } 19 | 20 | initMixin(Monitor) 21 | initGlobalError(Monitor) 22 | listen() // 开启监听,捕获信息 23 | 24 | export default Monitor 25 | -------------------------------------------------------------------------------- /src/monitor/indexedDb/index.js: -------------------------------------------------------------------------------- 1 | export function initIndexedDb() { 2 | if (!window.indexedDB) { 3 | this._config.offLine = false 4 | return console.info( 5 | 'your browser is not support indexedDb, offlineLog is not work' 6 | ) 7 | } 8 | let that = this 9 | const monitorVersion = 1 10 | const indexedDbRequest = window.indexedDB.open('monitor', monitorVersion) 11 | this.indexedDbRequest = indexedDbRequest 12 | indexedDbRequest.onerror = function (error) { 13 | that._config.offLine = false 14 | return console.error(`open indexedDb fail, error: ${JSON.stringify(error)}`) 15 | } 16 | 17 | indexedDbRequest.onsuccess = function (event) { 18 | that.db = event.target.result 19 | that.db.onerror = function (dbError) { 20 | console.error( 21 | `Generic error handler for all errors targeted at this database‘s requests; error: ${JSON.stringify( 22 | dbError 23 | )}` 24 | ) 25 | } 26 | } 27 | 28 | //当打开的db版本号高于之前的就会触发该函数 29 | indexedDbRequest.onupgradeneeded = function (event) { 30 | const db = event.target.result 31 | if (!db.objectStoreNames.contains('monitor-logs')) { 32 | db.createObjectStore('monitor-logs', { autoIncrement: true }) 33 | } 34 | } 35 | 36 | // 下面对数据库的操作 37 | this.insertToDb = function (log) { 38 | const transaction = this.db.transaction('monitor-logs', 'readwrite') 39 | const store = transaction.objectStore('monitor-logs') 40 | store.add(log) 41 | } 42 | 43 | this.addLog = function (log) { 44 | if (!this.db) return 45 | this.insertToDb(log) 46 | } 47 | 48 | this.addLogs = function (logs) { 49 | if (!this.db) { 50 | return 51 | } 52 | for (var i = 0; i < logs.length; i++) { 53 | this.addLog(logs[i]) 54 | } 55 | } 56 | 57 | this.getLogs = function (opt) { 58 | if (!this.db) return 59 | const transaction = this.db.transaction('monitor-logs', 'readwrite') 60 | const store = transaction.objectStore('monitor-logs') 61 | const request = store.openCursor() 62 | const result = [] 63 | request.onsuccess = function (event) { 64 | const cursor = event.target.result 65 | if (cursor) { 66 | if (cursor.value.time > opt.start) { 67 | result.push(cursor.value) 68 | } 69 | //# cursor.continue 70 | cursor['continue']() 71 | } 72 | return result 73 | } 74 | 75 | request.onerror = function (error) { 76 | console.error(`getlogs error: ${JSON.stringify(error)}`) 77 | } 78 | } 79 | 80 | this.clearDb = function (daysToMaintain) { 81 | if (!daysToMaintain) { 82 | const transaction = this.db.transaction('monitor-logs', 'readwrite') 83 | const store = transaction.objectStore('monitor-logs') 84 | return store.clear() 85 | } 86 | const range = Date.now() - (daysToMaintain || 2) * 24 * 3600 * 1000 87 | const request = store.openCursor() 88 | request.onsuccess = function (event) { 89 | const cursor = event.target.result 90 | if (cursor && (cursor.value.time < range || !cursor.value.time)) { 91 | store['delete'](cursor.primaryKey) 92 | cursor['continue']() 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/monitor/listener/event.js: -------------------------------------------------------------------------------- 1 | // 监听点击、双击事件 2 | function clickListener (event) { 3 | 4 | } 5 | // 监听焦点事件 6 | function focusListener (event) { 7 | 8 | } 9 | // 动态添加监听事件 10 | const listen = (event, cb, canRemove) => { 11 | if (window.addEventListener) { 12 | // IE模式 13 | window.addEventListener(event, function listener (ev) { 14 | if (canRemove) { 15 | window.removeEventListener(event, listener, true) 16 | } 17 | cb.call(window, ev) 18 | }, true) 19 | } else if (window.attachEvent) { 20 | // 不支持Mozilla系列,IE8及以下 21 | window.attachEvent(`on${event}`, function listener (ev) { 22 | if (canRemove) { 23 | window.detachEvent(`on${event}`, listener) 24 | } 25 | cb.call(window, ev) 26 | }) 27 | } 28 | } 29 | export default function () { 30 | // 监听事件 31 | listen('click', clickListener) 32 | listen('focus', focusListener) 33 | } -------------------------------------------------------------------------------- /src/monitor/listener/fetch.js: -------------------------------------------------------------------------------- 1 | export default function CatchFetch () { 2 | 3 | } -------------------------------------------------------------------------------- /src/monitor/listener/index.js: -------------------------------------------------------------------------------- 1 | import CatchFetch from './fetch' 2 | import EventListener from './event' 3 | 4 | const listeners = { 5 | CatchFetch, 6 | EventListener 7 | } 8 | 9 | export default function listen () { 10 | Object.entries(listeners).forEach(([name, fn]) => { 11 | fn() 12 | }) 13 | } -------------------------------------------------------------------------------- /src/monitor/mixins/index.js: -------------------------------------------------------------------------------- 1 | import { beforeProccess, afterProccess } from '../hook/index' 2 | import { processError } from '../processError/index' 3 | import { initIndexedDb } from '../indexedDb/index' 4 | import { getOsInfo, getBrowser } from '../utils/browser' 5 | import { mergeOption } from '../utils/proccessVariables' 6 | 7 | export function initMixin(Monitor) { 8 | Monitor.prototype._initConfig = function (options, addReportOptions) { 9 | this._options = options 10 | this._config = { 11 | reportUrl: options.reportUrl || '', 12 | reportType: options.reportType || 'normal', // normal - 使用创建image标签的形式 get方法上传, 'navigator - 使用navigator的方法上报,只支持post方法 13 | ignore: options.ignore || [], 14 | delay: options.delay || '', //延迟上报的时间,以ms为单位 15 | logsList: [], 16 | offLine: false, 17 | reportWarn: false 18 | } 19 | this._errorOptions = { 20 | id: new Date().getTime(), //timestamp 作为 errorID 21 | uid: localStorage.getItem('monitor_uid') || options.uid || 'MonitorUser', //用户标识 22 | osInfo: getOsInfo(), 23 | browser: getBrowser() 24 | } 25 | if ( 26 | options && 27 | Object.prototype.toString.call(options) === '[object Object]' 28 | ) { 29 | { 30 | const increase = mergeOption(this._config, options) 31 | this._config = increase 32 | } 33 | } 34 | if ( 35 | addReportOptions && 36 | Object.prototype.toString.call(addReportOptions) === '[object Object]' 37 | ) { 38 | { 39 | const increase = mergeOption(this._errorOptions, addReportOptions) 40 | this._errorOptions = increase 41 | } 42 | } 43 | //初始化Monitor时触发回调函数 44 | if (this._config.callback && typeof this._config.callback === 'function') { 45 | this._config.callback() 46 | } 47 | } 48 | 49 | Monitor.prototype._initMethod = function () { 50 | this.beforeProccess = beforeProccess 51 | this.afterProccess = afterProccess 52 | this._initIndexedDb = initIndexedDb 53 | this._proccessError = processError 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/monitor/processError/index.js: -------------------------------------------------------------------------------- 1 | import { splicingUrl } from '../utils/proccessVariables' 2 | const hrefRegex = 3 | /^(?:(http|https|ftp):\/\/)?((?:[\w-]+\.)+[a-z0-9]+)((?:\/[^/?#]*)+)?(\?[^#]+)?(#.+)?$/i 4 | 5 | export function processError(type, error, ...arg) { 6 | const config = this._config 7 | const errConfig = this._errorOptions 8 | if (type === 'windowError') { 9 | proccessWindowError(this, error, config, errConfig) 10 | } 11 | if (type === 'vueError') { 12 | proccessVueError(this, error, config, errConfig) 13 | } 14 | if (type === 'vueWarn') { 15 | proccessVueWarn(this, error, config, errConfig, ...arg) 16 | } 17 | if (type === 'unhandledrejection') { 18 | proccessUnhandledrejection(this, error, config, errConfig) 19 | } 20 | } 21 | 22 | function proccessWindowError(instance, error, config, errConfig) { 23 | const catchErrorOptions = dealWithErrorAndReturnOptions(error, errConfig) 24 | console.log('window error: ', catchErrorOptions) 25 | instance._config.logsList.push(catchErrorOptions) 26 | _proccessAndReportError(config, catchErrorOptions) 27 | } 28 | 29 | function proccessVueError(instance, error, config, errConfig) { 30 | const catchErrorOptions = dealWithErrorAndReturnOptions(error, errConfig) 31 | instance._config.logsList.push(catchErrorOptions) 32 | _proccessAndReportError(config, catchErrorOptions) 33 | } 34 | 35 | function proccessVueWarn(instance, error, config, errConfig, ...arg) { 36 | const catchErrorOptions = dealWithErrorAndReturnOptions(error, errConfig) 37 | catchErrorOptions.error.stack = arg[1] 38 | .replace(/\n/g, '') 39 | .replace(/\s/g, '') 40 | .split('at') 41 | .join('@') 42 | instance._config.logsList.push(catchErrorOptions) 43 | _proccessAndReportError(config, catchErrorOptions) 44 | } 45 | 46 | function proccessUnhandledrejection(instance, error, config, errConfig) { 47 | const catchErrorOptions = dealWithErrorAndReturnOptions(error, errConfig) 48 | catchErrorOptions.errorMessage = error.reason 49 | catchErrorOptions.lever = 'p0' 50 | instance._config.logsList.push(catchErrorOptions) 51 | _proccessAndReportError(config, catchErrorOptions) 52 | } 53 | 54 | function _proccessAndReportError(config, catchErrorOptions) { 55 | const _config = config 56 | if (!_config.reportUrl) return 57 | if (!hrefRegex.test(_config.reportUrl)) { 58 | return console.error( 59 | '[monitor config error]: reportUrl does not match the format of the link' 60 | ) 61 | } 62 | // image 为GET, 而sendBeacon 为post 63 | if (_config.reportType === 'normal') { 64 | if (window.Image) { 65 | reportTypeByImage(_config, catchErrorOptions) 66 | } 67 | } else { 68 | if (window.navigator) { 69 | reportTypeByNavigator(_config, catchErrorOptions) 70 | } 71 | } 72 | } 73 | 74 | function dealWithErrorAndReturnOptions(error, errConfig) { 75 | const errorMessage = error 76 | const stack = errorMessage.error 77 | ? errorMessage.error.stack 78 | .replace(/\n/gi, '') 79 | .split(/\bat\b/) 80 | .slice(0, 9) 81 | .join('@') 82 | .replace(/\?[^:]+/gi, '') 83 | : errorMessage.name + ' ' + errorMessage.message + '' 84 | const catchErrorOptions = { 85 | ...errConfig, 86 | lever: errorMessage.message ? 'p0' : 'p4', 87 | errorMessage: errorMessage.message || '', 88 | row: errorMessage.lineno || 0, 89 | col: errorMessage.colno || 0, 90 | error: { 91 | message: errorMessage.message || JSON.stringify(errorMessage), 92 | stack 93 | }, 94 | host: errorMessage.currentTarget 95 | ? errorMessage.currentTarget.location.host 96 | : window.location.host, 97 | href: errorMessage.currentTarget 98 | ? errorMessage.currentTarget.location.href 99 | : window.location.href, 100 | origin: errorMessage.currentTarget 101 | ? errorMessage.currentTarget.location.origin 102 | : window.location.origin 103 | } 104 | return catchErrorOptions 105 | } 106 | 107 | function reportTypeByImage(_config, catchErrorOptions) { 108 | if (!_config.delay) { 109 | const _image = new Image() 110 | _image.src = _config.reportUrl + splicingUrl(catchErrorOptions) 111 | } else { 112 | if (typeof _config.delay !== 'number') return 113 | setTimeout(() => { 114 | const _image = new Image() 115 | _image.src = _config.reportUrl + splicingUrl(catchErrorOptions) 116 | }, _config.delay) 117 | } 118 | } 119 | 120 | function reportTypeByNavigator(_config, catchErrorOptions) { 121 | if (!_config.delay) { 122 | window.navigator.sendBeacon( 123 | _config.reportUrl, 124 | JSON.stringify(catchErrorOptions) 125 | ) 126 | } else { 127 | setTimeout(() => { 128 | window.navigator.sendBeacon( 129 | _config.reportUrl, 130 | JSON.stringify(catchErrorOptions) 131 | ) 132 | }, _config.delay) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/monitor/utils/browser.js: -------------------------------------------------------------------------------- 1 | export function getOsInfo() { 2 | const userAgent = navigator.userAgent.toLowerCase() 3 | let name = 'Unknown' 4 | let version = 'Unknown' 5 | if (userAgent.indexOf('win') > -1) { 6 | name = 'Windows' 7 | if (userAgent.indexOf('windows nt 5.0') > -1) { 8 | version = 'Windows 2000' 9 | } else if ( 10 | userAgent.indexOf('windows nt 5.1') > -1 || 11 | userAgent.indexOf('windows nt 5.2') > -1 12 | ) { 13 | version = 'Windows XP' 14 | } else if (userAgent.indexOf('windows nt 6.0') > -1) { 15 | version = 'Windows Vista' 16 | } else if ( 17 | userAgent.indexOf('windows nt 6.1') > -1 || 18 | userAgent.indexOf('windows 7') > -1 19 | ) { 20 | version = 'Windows 7' 21 | } else if ( 22 | userAgent.indexOf('windows nt 6.2') > -1 || 23 | userAgent.indexOf('windows 8') > -1 24 | ) { 25 | version = 'Windows 8' 26 | } else if (userAgent.indexOf('windows nt 6.3') > -1) { 27 | version = 'Windows 8.1' 28 | } else if ( 29 | userAgent.indexOf('windows nt 6.2') > -1 || 30 | userAgent.indexOf('windows nt 10.0') > -1 31 | ) { 32 | version = 'Windows 10' 33 | } else { 34 | version = 'Unknown' 35 | } 36 | } else if (userAgent.indexOf('iphone') > -1) { 37 | name = 'iPhone' 38 | } else if (userAgent.indexOf('mac') > -1) { 39 | name = 'Mac' 40 | } else if ( 41 | userAgent.indexOf('x11') > -1 || 42 | userAgent.indexOf('unix') > -1 || 43 | userAgent.indexOf('sunname') > -1 || 44 | userAgent.indexOf('bsd') > -1 45 | ) { 46 | name = 'Unix' 47 | } else if (userAgent.indexOf('linux') > -1) { 48 | if (userAgent.indexOf('android') > -1) { 49 | name = 'Android' 50 | } else { 51 | name = 'Linux' 52 | } 53 | } else { 54 | name = 'Unknown' 55 | } 56 | return `${name}-${version}` 57 | } 58 | 59 | export function getBrowser() { 60 | var UserAgent = navigator.userAgent.toLowerCase() 61 | var browserInfo = {} 62 | var browserArray = { 63 | IE: window.ActiveXObject || 'ActiveXObject' in window, // IE 64 | Chrome: 65 | UserAgent.indexOf('chrome') > -1 && UserAgent.indexOf('safari') > -1, // Chrome浏览器 66 | Firefox: UserAgent.indexOf('firefox') > -1, // 火狐浏览器 67 | Opera: UserAgent.indexOf('opera') > -1, // Opera浏览器 68 | Safari: 69 | UserAgent.indexOf('safari') > -1 && UserAgent.indexOf('chrome') == -1, // safari浏览器 70 | Edge: UserAgent.indexOf('edge') > -1, // Edge浏览器 71 | QQBrowser: /qqbrowser/.test(UserAgent), // qq浏览器 72 | WeixinBrowser: /MicroMessenger/i.test(UserAgent) // 微信浏览器 73 | } 74 | for (var i in browserArray) { 75 | if (browserArray[i]) { 76 | var versions = '' 77 | if (i == 'IE') { 78 | versions = UserAgent.match(/(msie\s|trident.*rv:)([\w.]+)/)[2] 79 | } else if (i == 'Chrome') { 80 | for (var mt in navigator.mimeTypes) { 81 | if ( 82 | navigator.mimeTypes[mt]['type'] == 'application/360softmgrplugin' 83 | ) { 84 | i = '360' 85 | } 86 | } 87 | versions = UserAgent.match(/chrome\/([\d.]+)/)[1] 88 | } else if (i == 'Firefox') { 89 | versions = UserAgent.match(/firefox\/([\d.]+)/)[1] 90 | } else if (i == 'Opera') { 91 | versions = UserAgent.match(/opera\/([\d.]+)/)[1] 92 | } else if (i == 'Safari') { 93 | versions = UserAgent.match(/version\/([\d.]+)/)[1] 94 | } else if (i == 'Edge') { 95 | versions = UserAgent.match(/edge\/([\d.]+)/)[1] 96 | } else if (i == 'QQBrowser') { 97 | versions = UserAgent.match(/qqbrowser\/([\d.]+)/)[1] 98 | } 99 | browserInfo.type = i 100 | browserInfo.versions = parseInt(versions) 101 | } 102 | } 103 | return browserInfo 104 | } 105 | -------------------------------------------------------------------------------- /src/monitor/utils/proccessUtils.js: -------------------------------------------------------------------------------- 1 | export function reportErrorByOptions(type, config) { 2 | const _config = config 3 | if (type === 'normal') { 4 | if (!_config.delay) { 5 | const _image = new Image() 6 | _image.src = _config.reportUrl 7 | } else { 8 | if (typeof _config.delay !== 'number') return 9 | setTimeout(() => { 10 | const _image = new Image() 11 | _image.src = _config.reportUrl 12 | }, _config.delay) 13 | } 14 | } else { 15 | window.navigator.sendBeacon( 16 | _config.reportUrl, 17 | JSON.stringify(catchErrorOptions) 18 | ) 19 | } 20 | } 21 | 22 | export function reportErrorByCloseBrower(config, errorMsg) { 23 | const _config = config 24 | if(_config.logsList.length > 0 && window && window.fetch) { 25 | const url = _config.reportUrl 26 | const data = JSON.stringify({ 27 | time: performance.now(), 28 | errorMsg 29 | }) 30 | fetch(url, { 31 | method: 'POST', 32 | body: data, 33 | headers: { 34 | 'Content-Type': 'application/json' 35 | }, 36 | keepalive: true, 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/monitor/utils/proccessVariables.js: -------------------------------------------------------------------------------- 1 | export function mergeOption(oldOption, newOption) { 2 | let copy = JSON.parse(JSON.stringify(oldOption)) 3 | if ( 4 | Object.prototype.toString.call(newOption) !== '[object Object]' || 5 | JSON.stringify(newOption) === '{}' 6 | ) { 7 | return copy 8 | } 9 | const keys = Object.keys(newOption) 10 | for (let i = 0; i < keys.length; i++) { 11 | copy[keys[i]] = newOption[keys[i]] 12 | } 13 | return copy 14 | } 15 | 16 | export function splicingUrl(objParams) { 17 | if (Object.prototype.toString.call(objParams) !== '[object Object]') return 18 | const keys = Object.keys(objParams) 19 | let url = '?' 20 | keys.forEach((key) => { 21 | if (Object.prototype.toString.call(objParams[key]) === '[object Object]') { 22 | url += key + '=' + JSON.stringify(objParams[key]) + '&' 23 | } else { 24 | url += key + '=' + objParams[key] + '&' 25 | } 26 | }) 27 | url = url.substring(url.length - 1, 0) 28 | return url 29 | } 30 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | 4 | Vue.use(VueRouter) 5 | 6 | const routes = [ 7 | { 8 | path: '/', 9 | name: 'Monitor', 10 | component: () => import(/* webpackChunkName: "monitor" */ '../views/monitor/index.vue') 11 | } 12 | ] 13 | 14 | const router = new VueRouter({ 15 | routes 16 | }) 17 | 18 | export default router 19 | -------------------------------------------------------------------------------- /src/views/monitor/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | --------------------------------------------------------------------------------