错误统计
记录我们代码发布到线上各种奇奇怪怪的错误
行为日志埋点
记录用户行为,比如:分析用户浏览时间比较长的页面有哪些,常常点击的有哪些,可以做 相应的推荐
PV/UV统计
记录用户访问页面的次数
PV:访问的操作的次数,UV:访问页面的用户多少
错误监控,即当代码发生错误时,比如,同步错误,异步错误,promise错误,资源加载错误时,我们需要捕获到错误,然后上报给后端。
上报到后端简单,发送请求即可。那如何捕获到错误,我们下面进行讨论:
语法错误
语法错误一般在可发阶段就可以发现,比如常见的单词拼写错误,中英文符号错误等。注意:语法错误是无法被try catch捕获的,因为在开发阶段就能发现,所以一般不会发布到线上环境。
try {
let name = 'heima; // 少一个单引号
console.log(name);
} catch (error) {
console.log('----捕获到了语法错误-----');
}
同步错误
同步错误指的是在js同步执行过程中的错误,比如变量未定义,是可以被try catch给捕获到的
try {
const name = 'heima';
console.log(nam);
} catch (error) {
console.log('------同步错误-------')
}
异步错误
异步错误指的是在setTimeout等函数中发生的错误,是无法被try catch捕获到的
try {
setTimeout(() => {
undefined.map();
}, 0);
} catch (error) {
console.log('-----异步错误-----')
}
异步错误的话我们可以用window.onerror来进行处理,这个方法比try catch要强大很多
/**
* @param {String} msg 错误描述
* @param {String} url 报错文件
* @param {Number} row 行号
* @param {Number} col 列号
* @param {Object} error 错误Error对象
*/
window.onerror = function (msg, url, row, col, error) {
console.log('出错了!!!');
console.log(msg);
console.log(url);
console.log(row);
console.log(col);
console.log(error);
};
promise错误
在 promise
中使用 catch
可以捕获到异步的错误,但是如果没有写 catch
去捕获错误的话 window.onerror
也捕获不到的,所以写 promise
的时候最好要写上 catch
,或者可以在全局加上 unhandledrejection
的监听,用来监听没有被捕获的promise错误。
window.addEventListener("unhandledrejection", function(error){
console.log('捕获到异常:', error);
}, true);
资源加载错误
资源加载错误指的是比如一些资源文件获取失败,可能是服务器挂掉了等原因造成的,出现这种情况就比较严重了,所以需要能够及时的处理,网路错误一般用 window.addEventListener
来捕获。
window.addEventListener('error', (error) => {
console.log(error);
}, true);
埋点就是需要记录用户的某些行为,比如点击按钮,我们需要记录用户点击了哪个按钮,然后进行上报。这样做的作用是,我们得到的数据可以进行一些分析。PS:淘宝首页分类,需要知道哪个分类点击次数最多,即哪个分类最火。
手动埋点就是手动的去出发上报函数
// 方式1
<button
onClick={() => {
// 业务代码
tracker('click', '用户去支付');
// tracker('visit', '访问新页面');
// tracker('submit', '提交表单');
}}
>手动埋点</button>
// 方式2
<button
data-target="支付按钮"
onClick={() => {
// 业务代码
}}
>手动上报</button>
无痕埋点是为了解决手动埋点的缺点,实现一种不用侵入业务代码就能在应用中添加埋点监控的埋点方式。
<button onClick={() => {
// 业务代码
}}>自动埋点</button>
// 自动埋点实现
function autoTracker () {
// 添加全局click监听
document.body.addEventListener('click', function (e) {
const clickedDom = e.target;
// 获取data-target属性值
let target = clickedDom?.getAttribute('data-target');
if (target) {
// 如果设置data-target属性就上报对应的值--手动埋点
tracker('click', target);
} else {
// 如果没有设置data-target属性就上报被点击元素的html路径
const path = getPathTo(clickedDom);
tracker('click', path);
}
}, false);
};
history路由是由
window.histroy
api来实现的:
- istory.back(); // 返回上一页,和浏览器回退功能一样
- history.forward(); // 前进一页,和浏览器前进功能一样
- history.go(); // 跳转到历史记录中的某一页,
- history.pushState(); // 添加新的历史记录
- history.replaceState(); // 修改当前的记录项
/**
* 重写pushState和replaceState方法
* @param {*} name
* @returns
*/
const createHistoryEvent = function (name) {
// 拿到原来的处理方法
const origin = window.history[name];
return function(event) {
if (name === 'replaceState') {
const { current } = event;
const pathName = location.pathname;
if (current === pathName) {
let res = origin.apply(this, arguments);
return res;
}
}
let res = origin.apply(this, arguments);
let e = new Event(name);
e.arguments = arguments;
window.dispatchEvent(e);
return res;
};
};
window.history.pushState = createHistoryEvent('pushState');
window.history.replaceState = createHistoryEvent('replaceState');
function listener() {
const stayTime = getStayTime(); // 停留时间
const currentPage = window.location.href; // 页面路径
lazyReport('visit', {
stayTime,
page: beforePage,
})
beforePage = currentPage;
}
// history.go()、history.back()、history.forward() 监听
window.addEventListener('popstate', function () {
listener()
});
// history.pushState
window.addEventListener('pushState', function () {
listener()
});
// history.replaceState
window.addEventListener('replaceState', function () {
listener()
});
history
路由无法监听到pushState和replaceState
,所以我们冲洗了一个方法,并用windows.dispatch
创建了一个自定义监听事件。
hashchange
的监听,所以我们只需要在全局加上一个监听函数,在监听函数中实现采集并上报就可以了。但是在react和vue中,对于hash路由的跳转并不是通过 hashchange
的监听实现的,而是通过 pushState
实现,所以,还需要加上对 pushState
的监听才可以。export function hashPageTrackerReport() {
let beforeTime = Date.now(); // 进入页面的时间
let beforePage = ''; // 上一个页面
// 上报
function listener() {
const stayTime = getStayTime();
const currentPage = window.location.href;
lazyReport('visit', {
stayTime,
page: beforePage,
})
beforePage = currentPage;
}
// hash路由监听
window.addEventListener('hashchange', function () {
listener()
});
}
在SDK初始化的时候,上报即可
UV统计的是一天内访问该网站的用户数
/**
* 初始化配置
* @param {*} options
*/
function init(options) {
... // 加载配置
report('user', '加载应用'); // uv统计
}
对于无痕埋点来说,一次点击就进行一次上报,会对服务器造成很大的压力,所以我们需要合并一下请求
// cache.js
const cache = [];
export function getCache() {
return cache;
}
export function addCache(data) {
cache.push(data);
}
// lazyReport.js
export function lazyReport(type, params) {
// ....
const data = getCache();
if (delay === 0) { // delay=0相当于不做延迟上报
report(data);
return;
}
if (data.length > 10) { // 数据达到10条上报
report(data);
clearTimeout(timer);
return;
}
clearTimeout(timer);
timer = setTimeout(() => { // 合并上报
report(data);
}, delay);
}