请求
-
head请求,不关注响应体。避免响应体传输资源损耗。
-
http2多路复用
性能监控
window.performance
详见zhuanlan.zhihu.com/p/30329705
设备基本信息
window.location
navigator.userAgent
window.screen
页面路由监控
// 派发出新的 Event
const wr = (type: keyof History) => {
const orig = history[type];
return function (this: unknown) {
const rv = orig.apply(this, arguments);
const e = new Event(type);
window.dispatchEvent(e);
return rv;
};
};
// 添加 pushState replaceState 事件
export const wrHistory = (): void => {
history.pushState = wr('pushState');
history.replaceState = wr('replaceState');
};
wrHistory()
// 为 pushState 以及 replaceState 方法添加 Event 事件
export const proxyHistory = (handler: Function): void => {
console.log('proxyHistory');
// 添加对 replaceState 的监听
window.addEventListener('replaceState', (e) => handler(e), true);
// 添加对 pushState 的监听
window.addEventListener('pushState', (e) => handler(e), true);
};
export const proxyHash = (handler: Function): void => {
// 添加对 hashchange 的监听
// hash 变化除了触发 hashchange ,也会触发 popstate 事件,而且会先触发 popstate 事件,我们可以统一监听 popstate
// 这里可以考虑是否需要监听 hashchange,或者只监听 hashchange
window.addEventListener('hashchange', (e) => handler(e), true);
// 添加对 popstate 的监听
// 浏览器回退、前进行为触发的 可以自己判断是否要添加监听
window.addEventListener('popstate', (e) => handler(e), true);
};
const handler = (e:any) => {
console.log(e);
console.log('触发了');
}
proxyHistory(handler);
window.addEventListener('popstate', (e) => handler(e), true);
请求监控
XMLhttpRequest
// 调用 proxyXmlHttp 即可完成全局监听 XMLHttpRequest
export const proxyXmlHttp = (sendHandler: Function | null | undefined, loadHandler: Function) => {
if ('XMLHttpRequest' in window && typeof window.XMLHttpRequest === 'function') {
const oXMLHttpRequest = window.XMLHttpRequest;
if (!(window as any).oXMLHttpRequest) {
// oXMLHttpRequest 为原生的 XMLHttpRequest,可以用以 SDK 进行数据上报,区分业务
(window as any).oXMLHttpRequest = oXMLHttpRequest;
}
(window as any).XMLHttpRequest = () => {
// 覆写 window.XMLHttpRequest
const xhr = new oXMLHttpRequest();
const { open, send } = xhr;
let metrics = {} as any;
xhr.open = (method, url) => {
metrics.method = method;
metrics.url = url;
open.call(xhr, method, url, true);
};
xhr.send = (body) => {
metrics.body = body || '';
metrics.requestTime = new Date().getTime();
// sendHandler 可以在发送 Ajax 请求之前,挂载一些信息,比如 header 请求头
// setRequestHeader 设置请求header,用来传输关键参数等
// xhr.setRequestHeader('xxx-id', 'VQVE-QEBQ');
if (typeof sendHandler === 'function') sendHandler(xhr);
send.call(xhr, body);
};
xhr.addEventListener('loadend', () => {
const { status, statusText, response } = xhr;
metrics = {
...metrics,
status,
statusText,
response,
responseTime: new Date().getTime(),
};
if (typeof loadHandler === 'function') loadHandler(metrics);
// xhr.status 状态码
});
return xhr;
};
}
};
const sendHandler = (data) => {
data.header = 111111;
console.log(data, 'sendHandler');
}
const loadHandler = (data) => {
console.log(data, 'loadHandler');
}
proxyXmlHttp(sendHandler, loadHandler);
Fetch
// 调用 proxyFetch 即可完成全局监听 fetch
export const proxyFetch = (sendHandler: Function | null | undefined, loadHandler: Function) => {
if ('fetch' in window && typeof window.fetch === 'function') {
const oFetch = window.fetch;
if (!(window as any).oFetch) {
(window as any).oFetch = oFetch;
}
(window as any).fetch = async (input: any, init: RequestInit) => {
// init 是用户手动传入的 fetch 请求互数据,包括了 method、body、headers,要做统一拦截数据修改,直接改init即可
if (typeof sendHandler === 'function') sendHandler(init);
let metrics = {} as httpMetrics;
metrics.method = init?.method || '';
metrics.url = (input && typeof input !== 'string' ? input?.url : input) || ''; // 请求的url
metrics.body = init?.body || '';
metrics.requestTime = new Date().getTime();
return oFetch.call(window, input, init).then(async (response) => {
// clone 出一个新的 response,再用其做.text(),避免 body stream already read 问题
const res = response.clone();
metrics = {
...metrics,
status: res.status,
statusText: res.statusText,
response: await res.text(),
responseTime: new Date().getTime(),
};
if (typeof loadHandler === 'function') loadHandler(metrics);
return response;
});
};
}
};
获取用户留存时间
const routeList:any = [];
const routeTemplate = {
userId: '', // 用户信息等
// 除了userId以外,还可以附带一些其余的用户特征到这里面
url: '',
startTime: 0,
dulation: 0,
endTime: 0,
};
function recordPrevPageInfo() {
console.log('recordNextPage()');
// 记录前一个页面的页面停留时间
const time = new Date().getTime();
routeList[routeList.length - 1].endTime = time;
routeList[routeList.length - 1].dulation = time - routeList[routeList.length - 1].startTime;
// 推一个新的页面停留记录
routeList.push({
...routeTemplate,
...{ url: window.location.pathname, startTime: time, dulation: 0, endTime: 0 },
});
console.log(routeList, 'routeList列表');
}
// 在每一次导航变动的时候调用recordPrevPageInfo
// 初始化上报
window.addEventListener('load', () => {
const time = new Date().getTime();
routeList.push({
...routeTemplate,
...{ url: window.location.pathname, startTime: time, dulation: 0, endTime: 0 },
});
});
// 关闭上报
window.addEventListener('beforeunload', () => {
const time = new Date().getTime();
routeList[routeList.length - 1].endTime = time;
routeList[routeList.length - 1].dulation = time - routeList[routeList.length - 1].startTime;
localStorage.setItem('routeList', JSON.stringify(routeList))
// 上报
});
错误异常处理
异常分类
js error
Promise error
http error
cors error
vue报错
Vue.config.errorHandler = function (err, vm, info) {
console.log(err, vm, info);
}
app.config.errorHandler = (err, vm, info) => {
// 处理错误
// `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
}
react报错
import { ErrorBoundary } from 'react-error-boundary'
待更新...