目前,埋点一般分为,代码埋点,零代码,服务端埋点
代码埋点的话一般在实际业务中是最为常用的,但是有一个弊端在于,容易错埋 埋点: 一种收集用户特定信息工具
用于分析,模块使用情况,是否如何业务需求,也能辅助排查用户异常问题,通过行为栈,就能有效复现步骤,
一般分类为
行为监控,数据监控,错误监控
数据监控
- 关注,pv,uv 访问来源,行为栈,操作系统,浏览器等信息。
对于数据这块发送
- xmlRequest/fetch 传统,发起 ajax 请求,兼容性好,可能阻塞渲染。
- sendbeacon,方便,兼容性差
- img 发送。支持跨域,发生数据有限。
行为埋点
- 代码埋点
- 可视化埋点
- 服务端埋点
- 全埋点
行为监控sdk
用户信息
export interface PageInformation {
host: string;
hostname: string;
href: string;
protocol: string;
origin: string;
port: string;
pathname: string;
search: string;
hash: string;
// 网页标题
title: string;
// 浏览器的语种 (eg:zh) ; 这里截取前两位,有需要也可以不截取
language: string;
// 用户 userAgent 信息
userAgent?: string;
// 屏幕宽高 (eg:1920x1080) 屏幕宽高意为整个显示屏的宽高
winScreen: string;
// 文档宽高 (eg:1388x937) 文档宽高意为当前页面显示的实际宽高(有的同学喜欢半屏显示)
docScreen: string;
}
// 获取用户 信息
getPageInfo = (): PageInformation => {
const { host, hostname, href, protocol, origin, port, pathname, search, hash } = window.location;
const { width, height } = window.screen;
const { language, userAgent } = navigator;
return {
host,
hostname,
href,
protocol,
origin,
port,
pathname,
search,
hash,
title: document.title,
language: language.substr(0, 2),
userAgent,
winScreen: `${width}x${height}`,
docScreen: `${document.documentElement.clientWidth || document.body.clientWidth}x${document.documentElement.clientHeight || document.body.clientHeight
}`,
};
};
用户行为记录栈
- 虽然错误监控,能够捕获,对应错误信息,但是具体何种操作逻辑导致的,最好还是能记录用户操作逻辑,方便问题排查。
- demo示例
class Stack {
constructor(options: behaviorRecordsOptions) {
this.maxBehaviorRecords = options.maxBehaviorRecords || 5;
this.stack = []
}
private maxBehaviorRecords: number;
private stack: Array<behaviorStack>
push = (item) => {
if (this.stack.length >= this.maxBehaviorRecords) {
this.stack.shift()
}
this.stack.push(item)
}
pop = () => {
return this.stack.pop()
}
shift = () => {
this.stack.shift()
}
getStack = () => {
return this.stack.length
}
clear = () => {
this.stack = []
}
}
}
路由切换
一般常见的路由其实就是一个是hash 路由,history路由 hash路由,常见就是会去触发hashChange事件,但是会先触发popState因此统一监听 但是对于history路由,对于replaceState 和 pushState 不会触发popState ,因此为了统一,重新构建一个自定义事件 主体思路,得先去统一创建自定义事件,并进行监听所有路由触发事件,并统一执行对应回调,返回统一格式的信息
proxyRouter = (type: RouterType) => {
let newEvent = new Event(type);
window.dispatchEvent(newEvent)
}
proxyHistory = (handler: Function): void => {
// 添加对 replaceState 的监听
window.addEventListener('replaceState', (e) => handler(e), true);
// 添加对 pushState 的监听
window.addEventListener('pushState', (e) => handler(e), true);
};
// 重写pushState 和 replaceState 因为监听不到
wrHistory = (type) => {
let origin = window.history[type]
if (type === 'pushState') {
let event = new Event('pushState')
window.dispatchEvent(event)
}
if (type === 'replaceState') {
let event = new Event('replaceState')
window.dispatchEvent(event)
}
return origin.apply(this, type)
}
rewriteHistory = () => {
window.history.pushState = this.wrHistory('pushState')
window.history.replaceState = this.wrHistory('replaceState')
}
// 监听路由变化
watchHashRouteChange = (handle: Function) => {
window.addEventListener('hashchange', () => {
handle()
})
}
watchHistoryRouteChange = (handle: Function) => {
window.addEventListener('popstate', () => {
handle()
})
}
initRouter = () => {
const handle = () => {
const metrics = {
type: 'router',
url: window.location.href,
time: new Date().getTime(),
}
this.metrics.set(metricsName.RCR, metrics)
// 记录到行为记录追踪
const behavior = {
name: metricsName.RCR,
data: metrics,
...this.getExtends(),
} as behaviorStack;
this.breadcrumbs.push(behavior);
}
this.watchHashRouteChange(handle)
this.watchHashRouteChange(handle)
}
劫持http
一般依赖于xmlrequest 和fetch,那么就劫持这两个方法,来进行
proxyXMLHttpRequest = (sendHandle: Function, loadHandle: Function) => {
// 进行了备份
let oXmlHttpRequest = window.XMLHttpRequest;
window.XMLHttpRequest = function () {
const xhr = new oXmlHttpRequest();
let metris = {} as httpMetrics;
xhr.open = (method, url) => {
metris.method = method;
metris.url = url;
xhr.open.call(xhr, method, url, true);
};
xhr.send = (body) => {
metris.body = body;
xhr.send.call(xhr, body);
// setRequestHeader 设置请求header,用来传输关键参数等
// xhr.setRequestHeader('xxx-id', 'VQVE-QEBQ');
};
//xml, 请求,结束加载后,。执行
window.addEventListener("loadend", () => {
metris = {
...metris,
status: xhr.status,
statusText: xhr.statusText,
response: xhr.response,
};
if (typeof loadHandle === "function") {
loadHandle(metris);
}
});
};
};