UNPKG

hc-web-log-mon

Version:

基于 JS 跨平台插件,为前端项目提供【 行为、性能、异常、请求、资源、路由、曝光、录屏 】监控手段

488 lines (463 loc) 19.1 kB
<!DOCTYPE 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>