hc-web-log-mon
Version:
基于 JS 跨平台插件,为前端项目提供【 行为、性能、异常、请求、资源、路由、曝光、录屏 】监控手段
488 lines (463 loc) • 19.1 kB
HTML
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>example-vanilla</title>
</head>
<body style="height: 2000px">
<script type="module" src="/main.ts"></script>
<!-- <script type="module" src="/index.ts"></script> -->
<div id="app"></div>
<!-- Event 点击事件捕捉 -->
<div>
<div>Event 点击事件捕捉</div>
<div
style="border: 1px solid green; height: 200px; padding: 10px"
class="sss xxx 111 vvv"
data-warden-title="xxx"
data-warden-bigTitle="bigTitle"
width="100%"
id="222"
sdfasd
>
<div
style="border: 1px solid red; height: 100px; padding: 10px"
data-warden-test="test"
data-warden-title="titletitle"
data-warden-bing="bing"
data-warden-event-id="ddd"
>
<!-- <img src="../../public/face.jpg" alt="" /> -->
<div style="border: 1px solid rgb(71, 1, 236)">asdasdasd</div>
</div>
<div data-warden-id="我是ID">
<button value="xxxxxx" ref="bun">1111</button>
</div>
</div>
</div>
<br />
<!-- Error 捕捉 -->
<div>
<div>Error 捕捉</div>
<button id="codeErr">代码错误</button>
<!-- <button id="codeErr" onclick="codeError()">代码错误</button> -->
<button id="promiseError" onclick="promiseError()">promiseError</button>
<button id="consoleErr" onclick="consoleErr()">手动输出错误</button>
</div>
<script>
// ---------------- Error 捕捉 ----------------
document.getElementById("codeErr")?.addEventListener("click", () => {
codeError();
});
function codeError() {
const a = {};
a.split("/");
}
function promiseError() {
const promiseWrap = () =>
new Promise((resolve, reject) => {
reject("promise reject");
});
promiseWrap().then((res) => {
console.log("res", res);
});
}
function consoleErr() {
console.error("consoleErr1", "consoleErr1.1", "consoleErr1.2");
// console.error(111);
// console.error(new Error("谢谢谢谢谢"));
}
</script>
<br />
<!-- 批量 Error -->
<div>
<div>批量 Error 捕捉</div>
<button onclick="batchErrorA(10)">立即触发代码错误-10条</button>
<button onclick="batchErrorA(100)">立即触发代码错误-100条</button>
<br />
<br />
<button onclick="batchErrorAT(20)">异步触发代码错误-20条</button>
<button onclick="batchErrorAT(100)">异步触发代码错误-100条</button>
<br />
<br />
<button onclick="batchErrorB(10)">
立即触发[reject-10条 + 代码错误-10条 + console.error-10条]
</button>
<button onclick="batchErrorB(20)">
立即触发[reject-20条 + 代码错误-20条 + console.error-20条]
</button>
<br />
<br />
<button onclick="batchErrorC(10)">
异步触发[reject-10条 + 代码错误-10条 + console.error-10条]
</button>
<br />
<br />
<button onclick="batchErrorD()">异步触发无限错误</button>
</div>
<script>
function batchErrorA(num) {
for (let x = 1; x <= num; x++) {
document.getElementById("codeErr").click();
}
}
function batchErrorAT(num) {
for (let x = 1; x <= num; x++) {
setTimeout(() => {
document.getElementById("codeErr").click();
}, x * 300);
}
}
function batchErrorB(num) {
for (let x = 1; x <= num; x++) {
document.getElementById("codeErr").click();
consoleErr();
promiseError();
}
}
function batchErrorC(num) {
for (let x = 1; x <= num; x++) {
setTimeout(() => {
batchErrorB(1);
}, x * 300);
}
}
function batchErrorD() {
setInterval(() => {
document.getElementById("codeErr").click();
}, 200);
}
// function groupArray(arr, ...keys) {
// const groups = new Map();
// for (const obj of arr) {
// const keyArr = keys.filter(k => obj[k]).map(k => obj[k]);
// // if (keyArr.length !== keys.length) return undefined
// const key = keyArr.join(':')
// console.log('key', key);
// if (!groups.has(key)) {
// groups.set(key, []);
// }
// groups.get(key).push(obj);
// }
// return Array.from(groups.values());
// }
// // 示例代码
// const arr = [
// { name: 'apple', category2: 'fruit', price: 1.5 },
// { name: 'apple', category: 'fruit', price: 1 },
// { name: 'carrot', category: 'vegetable', price: 2 },
// { name: 'carrot', category: 'vegetable', price: 1.8 },
// { name: 'orange', category: 'fruit', price: 2 },
// ];
// const groups = groupArray(arr, 'name', 'category');
// console.log(groups);
</script>
<br />
<!-- Http 事件捕捉 -->
<div>
<div>Http 事件捕捉</div>
<button id="normalReq" onclick="onClickXhrNormal()">xhr正常请求</button>
<button id="exceptionReq" onclick="onClickXhrError()">xhr异常请求</button>
<button id="normalFetch" onclick="onClickNativeFetch()">
Fetch正常请求
</button>
<button id="exceptionFetch" onclick="onClickNativeErrorFetch()">
Fetch异常请求
</button>
</div>
<script>
// ---------------- Http 事件捕捉 ----------------
function onClickXhrNormal() {
const xhr = new XMLHttpRequest();
xhr.open("get", "/normal");
xhr.setRequestHeader("content-type", "application/json");
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
console.log("请求成功", xhr.responseText.slice(0, 10));
}
};
}
function onClickXhrError() {
const xhr = new XMLHttpRequest();
xhr.open("get", "/exception");
xhr.setRequestHeader("content-type", "application/json");
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
console.log("请求成功", xhr.responseText.slice(0, 10));
}
};
}
function onClickNativeFetch() {
fetch("/normal/post", {
method: "POST",
body: JSON.stringify({ test: "测试请求体" }),
mode: "cors",
headers: {
"Content-Type": "application/json",
},
})
.then((res) => res.text())
.then((res) => {
console.log("res----res", res);
})
.catch((err) => console.log("err----err", err));
}
function onClickNativeErrorFetch() {
fetch("/exception/post", {
method: "POST",
body: JSON.stringify({ test: "测试请求体" }),
mode: "cors",
headers: {
"Content-Type": "application/json",
},
})
.then((res) => res.text())
.then(
(res) => {
console.log("res", res);
},
(err) => {
console.log("err", err);
}
);
}
</script>
<br />
<!-- Pv 事件捕捉 -->
<div>
<div>Pv 事件捕捉</div>
<!-- 这种页面内的可以捕捉 -->
<!-- <a href="/#/edit">hash跳转</a> -->
<button onclick="hashChange()">hash跳转</button>
<button onclick="historyPushState()">history push跳转</button>
<button onclick="historyReplaceState()">history replace跳转</button>
<!-- <a href="/vue2/index.html">跳到Vue2页面</a> -->
<!-- <a href="/vue3/index.html">跳到Vue3页面</a> -->
<!-- 这种无法捕捉 -->
<a href="http://www.baidu.com" target="_blank">跳到百度</a>
</div>
<script>
// ---------------- Pv 事件捕捉 ----------------
function hashChange() {
// window.open('https://www.baidu.com') // 这种无法捕捉
// window.open('/#/edit') // 这种可以捕捉到,不过也是在新开的地方进来才能捕捉,并不是跳走阶段捕捉
window.location.href = "/#/edit";
}
function historyPushState() {
// window.history.pushState({}, "测试", `123`);
window.history.pushState(
{},
"测试",
`${parseInt(Math.random() * 1000)}`
);
}
function historyReplaceState() {
window.history.replaceState(
{},
"测试",
`${parseInt(Math.random() * 1000)}`
);
}
</script>
<br />
<!-- 异步加载资源 -->
<div>
<div class="badge badge-pill badge-primary">Performance 异步加载资源</div>
<button onclick="performanceAddScript()">插入正确Script</button>
<button onclick="performanceAddScriptError()">插入错误Script</button>
<br />
<button onclick="performanceAddLink()">插入Link</button>
<button onclick="performanceAddImg()">插入Img</button>
<button onclick="performanceAddLocalImg()">
插入Base64图片(不涉及请求所以无需采集)
</button>
<br />
<button onclick="performanceAddXHR()">发送XMLHTTPRequest</button>
<button onclick="performanceAddErrXHR()">发送错误XMLHTTPRequest</button>
<div id="performance-img-div" />
<button onclick="performanceAddFetch()">发送fetch</button>
<button onclick="performanceAddFetchError()">发送错误fetch</button>
</div>
<script>
function performanceAddScript() {
const script = document.createElement("script");
script.src = "https://cdn.jsdelivr.net/npm/lodash";
document.head.appendChild(script);
}
function performanceAddScriptError() {
const script = document.createElement("script");
script.src = "https://cdn.jsdelivr.net/npm/lodash22";
document.head.appendChild(script);
}
function performanceAddLink() {
const link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
link.href = "https://ahooks.js.org/useExternal/bootstrap-badge.css";
document.head.appendChild(link);
}
function performanceAddImg() {
const img = document.createElement("img");
img.src = `https://cdn.staticaly.com/gh/M-cheng-web/image-provider@main/blog/Annapurna-Ranges-2560x1440.5r9m9t5vg1g0.webp`;
const div = document.getElementById("performance-img-div");
div.style = "width: 400px; height: 400px; overflow: hidden;";
div.appendChild(img);
}
function performanceAddLocalImg() {
const img = document.createElement("img");
img.src =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAWCAMAAAD+dOxOAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAACEFBMVEU3R09BSE3/aAD/VyIAAAA3R083R083R083R083R083R083R083R083R083R083R083R083R083R083R083R083R083R083R083R083R083R083R083R083R083R083R083R083R083R083R083R083R083R080R1A0R1A1R08yR1AwRlA1R081R1A1R081R081R080R1A2R082R08vRlH4ViT/Whr/Whn/Wxb/WB7/WB//VyL/Wxj/XRD/Xg//XBX/WRv/XRH/XBX/WB7/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/VyL/ViH/VR//VSD/ViD+az39pYn8qY79qI78qpD+h2H+glv71cr9mHj9lnb9l3f+fFP+dEn8w7H/WiX/Uhv/Uxz+aDj7zr/9imX+fVT+flX+cET/WCP/XCj9qI38vqr8u6f8u6b8x7f8x7b/XSr/WCT/WSX+f1f8wK3/Wib+flb+hV//VB78tqD/VB/9iWX8vqv/Ux39m3z8rJP8xbT9oIP9jWn7z8L9jmr/YzL+f1j9o4f9mHn+cUQAAABiXhJiAAAAcXRSTlMAAAAAAAoJDQgdJREGARBWhZNAlNBUd3NLmmxr0dU9Pq0L6N3DtwIvOEYaFUk+V1lMLYNbAwIJCgkLCwsJCAgJCggIAji7xcRD9C/nH9sTzAkDp5N9aFT8QfYw7iDi4wZbpdf+5a9eAhlFgNaqYycFG/l+tLAAAAABYktHRASPaNlRAAAACXBIWXMAAAB4AAAAeACd9VpgAAAAB3RJTUUH5QISCDAI1QMDnAAAAUpJREFUGNNjYGBlY+fg5OJm5+HlY2NnAAJ+AUEhYRFRMXEJSX4pkIC0jKycvIKioJKyirSqvBoDo7qGppa2DpOunr6BoZGxCYOpmbmFpZWFtY2tnb2Do5Mzg4urmzscuLm6MHh4FiIBTw8GL+/ComIoKCn09mLw8S0tK6+oBIGq6kJfHwY//+Ka2rp6EGhoLPT3YwhwLWlqbmkFgbaSQtcAhsCgovaOzq5uIOjpLQwKZGAOLuzrnzBx4sRJk6dMLQxmZmAOAdo2bfr06YUzZs4qDAEKhBaWzJ5TUlg4t3NeYWEoUCAsfP6ChX3FcxctXlIcHgYUiIgsal86c9nyJStWFkdGAAWiogsLV61eM2/tupbC6CigQExsIcjxhUCHF8bFAAXiExKTIkEeS05JTYsHCjAzp2dkZrlm5+Tm5QM5DCzMYGBdEA+mWQDainMpZaFBiAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMC0wNy0xOVQwMzozOToxNSswMDowMFrQJhgAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTktMDEtMDhUMjA6MDc6NTUrMDA6MDAY1ESzAAAAIHRFWHRzb2Z0d2FyZQBodHRwczovL2ltYWdlbWFnaWNrLm9yZ7zPHZ0AAABjdEVYdHN2Zzpjb21tZW50ACBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIM5IkAsAAAAYdEVYdFRodW1iOjpEb2N1bWVudDo6UGFnZXMAMaf/uy8AAAAYdEVYdFRodW1iOjpJbWFnZTo6SGVpZ2h0ADcwNHxxE5sAAAAXdEVYdFRodW1iOjpJbWFnZTo6V2lkdGgANTAxnG5jJwAAABl0RVh0VGh1bWI6Ok1pbWV0eXBlAGltYWdlL3BuZz+yVk4AAAAXdEVYdFRodW1iOjpNVGltZQAxNTQ2OTc4MDc1s6i/GQAAABJ0RVh0VGh1bWI6OlNpemUAMjkxNjhCM09TOQAAAFp0RVh0VGh1bWI6OlVSSQBmaWxlOi8vL2RhdGEvd3d3cm9vdC93d3cuZWFzeWljb24ubmV0L2Nkbi1pbWcuZWFzeWljb24uY24vZmlsZXMvMTIxLzEyMTAxOTAucG5no98usQAAAABJRU5ErkJggg==";
const div = document.getElementById("performance-img-div");
div.style = "width: 100px; height: 100px; overflow: hidden;";
div.appendChild(img);
}
function performanceAddXHR() {
const xhr = new XMLHttpRequest();
xhr.open("get", `/index.html?t=${Date.now()}`);
xhr.send();
}
function performanceAddErrXHR() {
const xhr = new XMLHttpRequest();
xhr.open("post", "/not-found");
xhr.send(JSON.stringify({}));
}
function performanceAddFetch() {
window.fetch(`/index.html?t=${Date.now()}`, {
method: "GET",
header: {
"Content-Type": "application/json;charset=UTF-8",
},
});
}
function performanceAddFetchError() {
window.fetch(`/index.html22?t=${Date.now()}`, {
method: "GET",
header: {
"Content-Type": "application/json;charset=UTF-8",
},
});
}
</script>
<br />
<!-- 延迟加载sdk -->
<div>
<div>延迟加载sdk</div>
<button onclick="delayInit()">延迟加载sdk</button>
</div>
<script type="module">
import { init } from "hc-web-log-core";
window.delayInit = function () {
init({
// dsn: 'https://cdn.staticaly.com/gh/M-cheng-web/image-provider@main/blog/Annapurna-Ranges-2560x1440.5r9m9t5vg1g0.webp',
dsn: "http://1.15.224.10:22/trackweb/tra",
appName: "cxh",
debug: true,
pv: true,
performance: true,
error: true,
event: true,
cacheMaxLength: 10,
cacheWatingTime: 1000,
});
};
</script>
<br />
<!-- 手动发送缓存事件 -->
<div>
<div>手动发送缓存事件</div>
<button onclick="sendLocal()">点击发送</button>
</div>
<script type="module">
import { sendLocal, setLocalizationOverFlow } from "hc-web-log-core";
window.setLocalizationOverFlow = function () {
setLocalizationOverFlow((data) => {
console.log(
"本地化存储超过最大容量,此data为最后一次数据,请及时清空本地存储空间并保存此data(可以调用sendLocal发送后再把此data数据手动存储在localstorage)",
data
);
});
};
window.setLocalizationOverFlow();
window.sendLocal = function () {
sendLocal();
};
</script>
<br />
<!-- 曝光采集 -->
<div>
<div>曝光采集</div>
<button onclick="intersectionDisconnect()">取消所有采集曝光</button>
<button onclick="intersectionObserver('target')">采集图片的曝光</button>
<button onclick="intersectionUnobserve('target')">取消采集曝光</button>
<div id="target">
<img
src="https://aecpm.alicdn.com/simba/img/TB183NQapLM8KJjSZFBSutJHVXa.jpg"
/>
</div>
<br />
<button onclick="intersectionObserver('target2')">采集图片的曝光</button>
<button onclick="intersectionUnobserve('target2')">取消采集曝光</button>
<div id="target2">
<img
src="https://aecpm.alicdn.com/simba/img/TB183NQapLM8KJjSZFBSutJHVXa.jpg"
/>
</div>
</div>
<script type="module">
import {
intersectionObserver,
intersectionUnobserve,
intersectionDisconnect,
} from "hc-web-log-core";
window.intersectionObserver = function (str) {
const target = document.querySelector(`#${str}`);
intersectionObserver({
target,
threshold: 0.5, // 曝光的临界点 (0.5表示移入窗口一半算做开始曝光、移出窗口一半算结束曝光)
params: { name: 1111, targetName: str }, // 附带的额外参数
});
};
window.intersectionUnobserve = function (str) {
const target = document.querySelector(`#${str}`);
intersectionUnobserve(target);
};
window.intersectionDisconnect = function () {
intersectionDisconnect();
};
</script>
<script>
// const target = document.querySelector("#target");
// const io = new IntersectionObserver(
// (entries) => {
// entries.forEach((entry) => {
// // 如果该元素是可见,就执行某些操作
// if (entry.isIntersecting) {
// console.log("目标元素已经曝光");
// } else {
// console.log("目标元素已经离开");
// }
// });
// },
// {
// root: null,
// threshold: 1, // 阀值设为1,当只有比例达到1时才触发回调函数
// }
// );
// // 开始观察目标元素
// io.observe(target);
</script>
<br />
<script>
// window.addEventListener("offline", () => {
// console.log("已断网");
// });
// window.addEventListener("online", () => {
// console.log("网络已连接");
// });
</script>
</body>
</html>