
行为日志采集
- 页面浏览(Page Views)
- 用户点击(Clicks)
- 键盘输入(Keyboard Inputs)
- 页面停留时间(Time on Page)
- 滚动行为(Scrolling Behavior)
- 表单填写情况(Form Interactions)
错误日志采集
语法错误
- 缺少括号、大括号或方括号 [() {} []](javascript:void(0))
- 缺少分号或逗号
; , - 字符串引号不匹配
- 关键字拼写错误
同步错误

异步错误
异步错误的话我们可以用window.onerror来进行处理,这个方法比try catch要强大很多

promise错误
在promise中使用catch可以捕获到异步的错误,但是如果没有写catch去捕获错误的话
window,onerror也捕获不到的,所以写promise的时候最好要写上catch,或者可以在全局
加上unhandledrejection的监听,用来监听没有被捕获的promise错误。

resource错误
资源加载错误指的是比如一些资源文件获取失败,可能是服务器挂掉了等原因造成的,出现这种
情况就比较严重了,所以需要能够及时的处理,网路错误一般用window.addEventListener来
捕获。

全局错误捕获
js
import { lazyReport } from './report';
/**
* 全局错误捕获
*/
export function errorTrackerReport() {
// -------- js error ---------
const originOnError = window.onerror;
window.onerror = function (msg, url, row, col, error) {
// 处理原有的onerror
if (originOnError) {
originOnError.call(window, msg, url, row, col, error);
}
// 错误上报
lazyReport('error', {
message: msg,
file: url,
row,
col,
error,
errorType: 'jsError'
});
}
// ------ promise error --------
window.addEventListener('unhandledrejection', (error) => {
lazyReport('error', {
message: error.reason,
error,
errorType: 'promiseError'
});
});
// ------- resource error --------
window.addEventListener('error', (error) => {
let target = error.target;
let isElementTarget = target instanceof HTMLScriptElement || target instanceof HTMLLinkElement || target instanceof HTMLImageElement;
if (!isElementTarget) {
return; // js error不再处理
}
lazyReport('error', {
message: "加载 " + target.tagName + " 资源错误",
file: target.src,
errorType: 'resourceError'
});
}, true)
}
/**
* 手动捕获错误
*/
export function errorCaptcher(error, msg) {
// 上报错误
lazyReport('error', {
message: msg,
error: error,
errorType: 'catchError'
});
}
路由监听
js
import { lazyReport } from './report';
/**
* history路由监听
*/
export function historyPageTrackerReport() {
let beforeTime = Date.now(); // 进入页面的时间
let beforePage = ''; // 上一个页面
// 获取在某个页面的停留时间
function getStayTime() {
let curTime = Date.now();
let stayTime = curTime - beforeTime;
beforeTime = curTime;
return stayTime;
}
/**
* 重写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;
};
};
// history.pushState
window.addEventListener('pushState', function () {
listener()
});
// history.replaceState
window.addEventListener('replaceState', function () {
listener()
});
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;
}
// 页面load监听
window.addEventListener('load', function () {
// beforePage = location.href;
listener()
});
// unload监听
window.addEventListener('unload', function () {
listener()
});
// history.go()、history.back()、history.forward() 监听
window.addEventListener('popstate', function () {
listener()
});
}
/**
* hash路由监听
*/
export function hashPageTrackerReport() {
let beforeTime = Date.now(); // 进入页面的时间
let beforePage = ''; // 上一个页面
function getStayTime() {
let curTime = Date.now();
let stayTime = curTime - beforeTime;
beforeTime = curTime;
return stayTime;
}
function listener() {
const stayTime = getStayTime();
const currentPage = window.location.href;
lazyReport('visit', {
stayTime,
page: beforePage,
})
beforePage = currentPage;
}
// hash路由监听
window.addEventListener('hashchange', function () {
listener()
});
// 页面load监听
window.addEventListener('load', function () {
listener()
});
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');
// history.pushState
window.addEventListener('pushState', function () {
listener()
});
}
上报
js
import { getCache, addCache, clearCache } from './cache';
let timer = null;
/**
* 上报
* @param {*} type
* @param {*} params
*/
export function lazyReport(type, params) {
const appId = window['_monitor_app_id_'];
const userId = window['_monitor_user_id_'];
const delay = window['_monitor_delay_'];
const logParams = {
appId, // 项目的appId
userId, // 用户id
type, // error/action/visit/user
data: params, // 上报的数据
currentTime: new Date().getTime(), // 时间戳
currentPage: window.location.href, // 当前页面
ua: navigator.userAgent, // ua信息
};
let logParamsString = JSON.stringify(logParams);
addCache(logParamsString);
const data = getCache();
if (delay === 0) { // delay=0相当于不做延迟上报
report(data);
return;
}
if (data.length > 10) {
report(data);
clearTimeout(timer);
return;
}
clearTimeout(timer);
timer = setTimeout(() => {
report(data)
}, delay);
}
export function report(data) {
const url = window['_monitor_report_url_'];
// ------- fetch方式上报 -------
// 跨域问题
// fetch(url, {
// method: 'POST',
// body: JSON.stringify(data),
// headers: {
// 'Content-Type': 'application/json',
// },
// }).then(res => {
// console.log(res);
// }).catch(err => {
// console.error(err);
// })
// ------- navigator/img方式上报 -------
// 不会有跨域问题
if (navigator.sendBeacon) { // 支持sendBeacon的浏览器
navigator.sendBeacon(url, JSON.stringify(data));
} else { // 不支持sendBeacon的浏览器
let oImage = new Image();
oImage.src = `${url}?logs=${data}`;
}
clearCache();
}