监控系统在现在的项目中是必不可少的,它具有性能监控、错误监控以及数据上报等功能。通过对的错误监控、用户正常行为的收集,从而指定对应的优化方案、营销策略以及项目的迭代。 试想,如果没有这些监控系统,那么线上的运行的项目出问题了,定位是比较困难的,这时候修复起来就比较劳神了。我记录下我所了解的,常见的方式。
1.错误监控
我们对于错误的处理,在js
中很常见的错误捕获try catch
、promise中的catch
等等。在window对象上有一个方法onerror
,可以拿到当前报错的信息,我们就可以将这个信息拿到然后提交到我们自己的监控系统,还有个很重要的原因,不处理这些错误,导致页面挂了停止加载,那损失可就大了。对于抛出的错误,它有几个属性
// 定义一个错误对象
const defaults = {
msg: '', // 错误的具体信息
url: '', // 错误所在的url
line: '', // 错误所在的行
col: '', // 错误所在的列
nowTime: '' // 时间
}
然后实现window.onerror
方法
window.onerror = function (msg, url, line, col, error) {
col = col || (window.event && window.event.errorCharacter) || 0
defaults.url = url
defaults.line = line
defaults.col = col
defaults.nowTime = new Date().getTime()
if (error && error.stack) {
// 如果浏览器有错误堆栈信息,直接使用
defaults.msg = error.stack.toString()
} else if (arguments.callee) {
// 通过callee拿堆栈信息
let ext = []
let fn = arguments.callee.caller
// 最多拿到三层
let floor = 3
while (fn && (--floor > 0)) {
ext.push(fn.toString())
if (fn === fn.caller) {
break
}
fn = fn.caller
}
ext = ext.join(',')
defaults.msg = error && error.stack && error.stack.toString()
}
let str = ''
// 格式化这些错误信息
for (const i in defaults) {
if (!defaults[i]) {
defaults[i] = 'null'
}
str += '&' + i + '=' + defaults[i].toString()
}
// 确定错误是由哪位用户引起的,这一步有些时候可以不用
let userinfo = // 拿到用户信息
if (typeof userinfo === 'object') {
userinfo = JSON.stringify(userinfo)
}
if (userinfo) userinfo = encodeURIComponent(userinfo)
str = encodeURIComponent(str.replace('&', '').replace('\n', '').replace(/\s/g, ''))
// 避免出现跨域错误
new Image().src = 'api地址?msg=' + str + '&userinfo=' + userinfo
}
这种方式的缺陷在于,没办法处理promise
中未处理的错误,对于promise
,推荐使用unhandledrejection
事情处理
window.addEventListener('unhandledrejection', event => {
let error = event.reason && event.reason.message
if (!error) {
error = typeof event.reason === 'object' ? JSON.stringify(event.reason) : event.reason
}
(new Image()).src = 'api地址?msg=' + encodeURIComponent(error)
})
2.埋点
埋点是一种常见的统计用户行为的方式或工具。通过在页面设置埋点,来统计用户对于某个页面、广告、功能的使用频率、喜好,然后通过对埋点的数据统计,确定好页面或功能的迭代。简单的做法,设置一个全局方法用来提交埋点点击的行为,在需要的地方调用这个方法。
// 定义一个全局方法
// 传入的point为埋点数据
function clickPoint(point = {}) {
const { id, name } = point
cosnt userInfo = // 通过对应的操作拿到用户信息
// 基础参数
const basicParams = {
id: id || '',
name: name || ''
}
// 用户信息
const userParams = {
name: userInfo.name || ''
......
}
// 合并参数
const params = {
...basicParams,
...userParams
}
let str = ''
for (const i in params) {
if (!params[i]) {
params[i] = 'null'
}
str += '&' + i + '=' + params[i].toString()
}
new Image().src = 'api地址?msg=' + str
}
通常这样来统计页面上某个功能、某个广告的点击量,获取对应的统计数据。还有一种比较常见的方式,为需要添加埋点的html
标签上加上某个约定好的自定义属性,如<div data-link="111"></div>
,这个111
就是就是实现生成好或者规划好的埋点。然后给这种属性添加全局的点击事件,点击后同样的将对应的数据提交到自己的后台系统去。有些时候为了方便查阅或者直观的观看统计数据,还可以在页面渲染的时候,为含有data-link
属性的标签生成对应的一些图标或者文字标签,使用absolute
定位显示在对应的元素上,当然这种方式在你添加买点埋点属性的时候,需要给这个元素设置相应的position css
属性;当然也可能设置一些热力图啊等其它形式,反正都是实现同一个效果的。
3.白屏时间
这个之前说过,就不说废话了,白屏时间的统计是非常重要的。比如有些时候,只有某个用户的某个设备出现了页面加载过长,页面没内容,发现这个问题后,通过判断错误日志和白屏时间才能更快的定位问题。
window.logInfo = {}
window.logInfo.openTime = window.performance && window.performance.timing.navigationStart || 0
window.logInfo.whiteScreenTime = +new Date() - window.logInfo.openTime
window.logInfo.mobile = mobileType()
// 使用 DOMContentLoaded 统计页面有内容的时间
document.addEventListener('DOMContentLoaded', function () {
window.logInfo.readyTime = +new Date() - window.logInfo.openTime
})
window.onload = function () {
window.logInfo.allloadTime = +new Date() - window.logInfo.openTime
window.logInfo.nowTime = new Date().getTime()
let timname = {
whiteScreenTime: '白屏时间',
readyTime: '用户可操作时间',
allloadTime: '总下载时间',
mobile: '使用设备',
nowTime: '时间',
}
let logStr = ''
for (const i in timname) {
if (i === 'mobile') {
logStr += '&' + i + '=' + window.logInfo[i]
} else {
logStr += '&' + i + '=' + window.logInfo[i]
}
}
// 这里如果需要的话,还可以把用户的一些信息收集出来
(new Image()).src = 'api地址?msg=' + logStr
}
function mobileType() {
const u = navigator.userAgent
// 移动终端浏览器版本信息
const type = {
ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), // ios终端
iPad: u.indexOf('iPad') > -1, // 是否iPad
android: u.indexOf('Android') > -1 || u.indexOf('Linux') > -1, // android终端或者uc浏览器
iPhone: u.indexOf('iPhone') > -1 || u.indexOf('Mac') > -1, // 是否为iPhone或者QQHD浏览器
trident: u.indexOf('Trident') > -1, // IE内核
presto: u.indexOf('Presto') > -1, // opera内核
webKit: u.indexOf('AppleWebKit') > -1, // 苹果、谷歌内核
gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') === -1, // 火狐内核
mobile: !!u.match(/AppleWebKit.*Mobile/i) || !!u.match(/MIDP|SymbianOS|NOKIA|SAMSUNG|LG|NEC|TCL|Alcatel|BIRD|DBTEL|Dopod|PHILIPS|HAIER|LENOVO|MOT-|Nokia|SonyEricsson|SIE-|Amoi|ZTE/), // 是否为移动终端
webApp: u.indexOf('Safari') === -1 // 是否web应该程序,没有头部与底部
}
const lists = Object.keys(type)
for (const i = 0; i < lists.length; i++) {
if (type[lists[i]]) {
return lists[i]
}
}
}