前言
在云在前端公众号中发送监控
领取源码地址和npm包地址
一、数据埋点
1. 埋点的目的
收集用户行为,反馈页面功能、活动效果,指明产品优化方向
2. 常用属性
属性 | 描述 |
---|---|
uuid | 用户id |
date | 访问日期 |
pv | 页面浏览量 |
uv | 用户访问量 |
duration | 停留时间 |
preformance | 性能信息 |
error | 报错信息 |
device | 设备信息 |
二、数据采集
1. 行为监控
1. 用户点击
js
export default function behavior() {
["click"].forEach(function (eventType) {
let timer: NodeJS.Timeout;
document.addEventListener(
eventType,
(e) => {
clearTimeout(timer);
timer = setTimeout(() => {
const target = e.target;
//目前只处理button标签的点击事件
if (target instanceof HTMLButtonElement) {
emit(eventType, target.textContent);
}
}, 300);
},
true
);
});
}
2. 页面跳转(PV)
(1)hash路由
js
function Hash() {
window.addEventListener("hashchange", function () {
emit("hashchange");
});
}
(2)history路由
重写跳转方法,设置拦截器进行监听
js
function History() {
const historyPushState = window.history.pushState;
const historyReplaceState = window.history.replaceState;
window.history.pushState = function () {
historyPushState.apply(window.history, arguments);
emit("historychange");
};
window.history.replaceState = function () {
historyReplaceState.apply(window.history, arguments);
emit("historychange");
};
window.addEventListener("popstate", function () {
emit("historychange");
});
}
3. 页面停留时长
记录一个初始时间,用户离开页面时用当前时间减去初始时间,就是用户停留时长
js
let visitTime = Date.now();
export function emit(type, data) {
const date = Date.now();
//...
if (type === "hashchange" || type === "historychange") {
//停留时间 = 跳转时间 - 访问时间
Object.assign(info, { duration: date - visitTime });
visitTime = date;
}
//...
}
4. UV
如果是游客,先判断localStorage里是否有id值,没有则为游客生成唯一id,并存储到localStorage中。下一次游客再访问时,直接取存在localStorage中的值。
js
export class BaseInfo {
constructor() {
//...
if (!localStorage.getItem(UUID)) {
this.uuid = uuidv4(); //唯一id;
localStorage.setItem(UUID, this.uuid); //如果不存在uuid,则进行存储
}else{
this.uuid = localStorage.getItem(UUID)
}
}
}
2. 异常监控
1. JS错误
js
function JSError() {
// 错误信息 出错文件 行号 列号 Error对象
window.onerror = (msg, url, line, column, error) => {
emit("js_error", { msg, url, line, column, error });
};
}
2. 资源加载错误
js
function resourceError() {
window.addEventListener(
"error",
function (e) {
const target = e.target;
if (!target) return;
if (target.src || target.href) {
const url = target.src || target.href;
emit("resource_error", url);
}
},
true
);
}
3. 手动抛出的错误
js
//重写console.error方法
function consoleError() {
var oldError = window.console.error;
window.console.error = function (errorMsg) {
emit("console_error", errorMsg);
oldError.apply(window.console, arguments);
};
}
4. promise错误
js
// 当Promise被reject且没有reject处理器的时候,会触发unhandledrejection事件;
function promiseError() {
window.addEventListener("unhandledrejection", function (e) {
emit("promise_error", e.error.stack);
});
}
5. Vue错误
js
//全局捕获Vue错误
app.config.errorHandler = (err, instance, info) => {
// 处理错误,例如:报告给一个服务
emit('vue_error',info)
}
6. React错误
使用错误边界,在componentDidCatch中捕获错误
js
// 定义错误边界
class ErrorBoundary extends React.Component {
state = { error: null }
static getDerivedStateFromError(error) {
return { error }
}
componentDidCatch(error, errorInfo) {
// 错误捕获
emit('react_error', errorInfo)
}
render() {
if (this.state.error) {
return <h2>Something went wrong.</h2>
}
return this.props.children
}
}
...
<ErrorBoundary>
<BuggyCounter />
</ErrorBoundary>
3. 性能监控
1. FP
首次渲染时间
js
function fp() {
const entryHandler = (list) => {
for (const entry of list.getEntries()) {
if (entry.name === "first-paint") {
observer.disconnect();
emit("fp", entry.startTime);
}
}
};
const observer = new PerformanceObserver(entryHandler);
// buffered:true表示观察缓存数据
observer.observe({ type: "paint", buffered: true });
}
2. DCL
DOM加载完成时间
js
function dcl() {
window.addEventListener("DOMContentLoaded", function (e) {
emit("DOMContentLoaded", e.timeStamp);
});
}
3. load
图片、样式等外链资源加载完成时间
js
function load() {
window.addEventListener("load", function (e) {
emit("load", e.timeStamp);
});
}
4. fps
监控requestAnimationFrame在一秒内的执行次数,得到FPS的值,如果存在连续3个小于20的FPS,说明页面存在卡顿
js
let count = 0;
let frames = 0;
let lastTimestamp = performance.now();
//timestamp开始执行函数的时间戳
export default function updateFPS(timestamp) {
frames++;
const deltaTime = timestamp - lastTimestamp;
if (deltaTime >= 1000) {
const fps = Math.round(frames / (deltaTime / 1000));
if (fps < 20) {
count++;
if (count >= 3) {
//连续3次小于20的fps进行数据上报
emit("fps", '卡顿');
count = 0;
}
} else {
count--;
if (count < 0) count = 0;
}
frames = 0;
lastTimestamp = timestamp;
}
requestAnimationFrame(updateFPS);
}
三、数据上报
1. 上报方法
1. sendBeacon
-
在浏览器空闲的时候发送
-
在页面卸载时,也会异步发送数据
js
navigator.sendBeacon(url, JSON.stringify(data)); //发送数据
2. XMLHttpRequest
如果浏览器不支持sendBeacon,则使用XMLHttpRequest进行兜底
js
let xhr = new XMLHttpRequest();
xhr.open("POST", url);
xhr.send(JSON.stringify(data));
2. 上报时机
1. 达到缓存上限时上传
2. 达到最大缓存时间上传
js
clearTimeout(timer);
events.length >= max
? send()
: (timer = setTimeout(() => {
send();
}, 60000)); //如果1分钟内没达到最大缓存数,主动上传
3. 页面关闭或刷新时上传
js
window.addEventListener("beforeunload", send, true);
最后
- 这里只展示前端监控的一些要点与原理,具体的还是得根据自身的业务去拓展
- GitHub上的示例代码由于不断更新,会与文中的略有不同,但大体思路还是一致,可做参考
- 文章中如果有什么不对的,或者你有新的思路和建议,可以在评论区留言
文章首发在云在前端公众号,未经许可禁止转载!