背景 TD第二届黑客马拉松赛题其中一个就是进行前端错误和性能监控,之前一直想过做这么一件事情,当线上发生代码错误或者资源引用错误时不是通过用户反馈得知,而是开发人员能在第一时间知晓,这次终于有机会进行尝试,与另外两名前端同事在10月20日进行了一天一夜的代码长跑。
设计方案
前端监控一般分为错误监控和性能监控,而我们此次也是主要从这两方面进行监控。
集成方式考虑到有非侵入式集成(SDK)和侵入式集成,分析两者利弊后决定采用非侵入式集成,希望可以像JQuery一样,通过一行代码直接引入,在项目中直接使用,最后的理想状态就是:一行代码,开箱即用。
类型
优点
缺点
非侵入式
主动监测,指标齐全
无法监控复杂应用、监控的数据可能较少,无法捕获已经try-catch的数据
侵入式
可以监控复杂应用,进行细致监控
需要侵入源代码
监控sdk收到的信息通过指定接口(性能和错误接口分开)上报给后台,后台使用MongoDB进行存储和数据的处理,前台页面进行数据的直观显示。
监控数据收集方式 错误处理
我们可以通过try/catch方式进行错误的捕获,但是考虑到无侵入式的原则,最后选择使用全局的error事件进行捕获错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var handleWindowError = function (_window, config ) { _oldWindowError = _window.onerror; _window.onerror = function (msg, url, line, col, error ) { var eventId = `${url} ${line} ${col} ` config.sendError({ title: msg, msg: { resourceUrl: url, rowNum: line, colNum: col, info: error, filename: url, eventId: eventId, }, category: 'js' , level: 'error' }) if (_oldWindowError && isFunction(_oldWindowError)) { windowError && windowError.apply(window , arguments ); } } }
通过监听全局’unhandledrejection’事件可以捕获未处理的 reject 。
1 2 3 4 5 6 7 8 9 10 11 12 13 var handleRejectPromise = function (_window, config ) { _window.addEventListener('unhandledrejection' , function (event ) { if (event) { var reason = event.reason; config.sendError({ title: '不捕获Promise异步错误' , msg: reason, category: 'js' , level: 'error' }); } }, true ); }
当页面中如【img src=’./404.png’】 引入不存在的资源时,会出现未找到资源的错误,如果在【script src=’./null.js’】引入未找到的js将会造成页面严重错误。 因此可以通过监控监听到的错误是否含有html标签,并且标签中是否有src或者href属性进行资源请求的监控。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var handleResourceError = function (_window, config ) { _window.addEventListener('error' , function (event ) { if (event) { var target = event.target || event.srcElement; var isElementTarget = target instanceof HTMLScriptElement || target instanceof HTMLLinkElement || target instanceof HTMLImageElement; if (!isElementTarget) return ; var url = target.src || target.href; config.sendError({ title: target.nodeName, msg: { url: url, eventId: url, }, category: 'resource' , level: 'error' , }); } }, true ); }
通过监控原生XMLHttpRequest属性进行ajax请求的监控。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 var handleAjaxError = function (_window, config ) { var protocol = _window.location.protocol; if (protocol === 'file:' ) return ; _handleFetchError(_window, config); if (!_window.XMLHttpRequest) { return ; } var xmlhttp = _window.XMLHttpRequest; var _oldSend = xmlhttp.prototype.send; var _handleEvent = function (event ) { if (event && event.currentTarget && event.currentTarget.status !== 200 ) { config.sendError({ title: event.target.responseURL, msg: { response: event.target.response, responseURL: event.target.responseURL, status: event.target.status, statusText: event.target.statusText, eventId: event.target.responseURL, }, category: 'ajax' , level: 'error' }); } } }
通过监控浏览器控制台输出的错误日志进行错误的捕获。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var handleConsoleError = function (_window, config ) { if (!_window.console || !_window.console.error) return ; var _oldConsoleError = _window.console.error; _window.console.error = function ( ) { config.sendError({ title: 'consoleError' , msg: JSON .stringify(arguments ), category: 'js' , level: 'error' }); _oldConsoleError && _oldConsoleError.apply(_window, arguments ); }; }
vue中有指定的钩子函数errorHandler进行错误的监控,可以通过监控errorHandler收集的错误进行错误的监控。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 var handleVueError = function (_window, config ) { var vue = _window.Vue; if (!vue || !vue.config) return ; var _oldVueError = vue.config.errorHandler; Vue.config.errorHandler = function VueErrorHandler (error, vm, info ) { var metaData = {}; if (Object .prototype.toString.call(vm) === '[object Object]' ) { metaData.componentName = vm._isVue ? vm.$options.name || vm.$options._componentTag : vm.name; metaData.propsData = vm.$options.propsData; } config.sendError({ title: 'vue Error' , msg: { meta: metaData, info, }, category: 'js' , level: 'error' }); if (_oldVueError && isFunction(_oldVueError)) { _oldOnError.call(this , error, vm, info); } }; }
性能监控
主要监测当前页面
页面完全加载时间
HTTP请求响应完成时间
脚本加载时间
DOM加载完成时间
onload事件时间 …
主要通过window.performance进行页面性能监控。
致敬