相关学习推荐:javascript视频教程
最近杂七杂八的事情比较多,难得抽出时间来弥补一下之前的系列,欠大家的埋点系列现在开始走起来
前端开发攻城狮开开心心的 coding,非常自豪的进行了业务、UI 分离开发,各种设计模式、算法优化轮番上阵,代码写的 Perfect(劳资代码天下第一),没有 BUG,程序完美,兼容性 No.1,代码能打能抗质量高。下班轻松打卡,回家看娃。
实际上,开发环境与生产环境并不能等同,并且测试的过程再完善,依然会有漏测的情况存在。考虑到用户使用客户端环境、网络环境等等一系列的不确定因素存在。
所以在开发过程中一定要记得三大原则(我胡诌的)
埋点就像城市中的摄像头,从产品的角度考虑,它可以监控到用户在我们产品里的行为轨迹,为产品的迭代、项目的稳定提供依据,WHO、WHEN、WHERE、HOW、WHAT 是埋点采集数据的基础维度。
对前端开发而言,可以监控页面资源加载性能,异常等等,提供了页面体验和健康指数,为后续性能优化提供依据,及时上报异常和发生场景。从而能够及时修正问题,提高项目质量等。
埋点可以大概分为三类:
代码埋点 | 可视化埋点 | 无痕埋点 | |
---|---|---|---|
典型场景 | 无痕埋点无法覆盖到,比如需要业务数据 | 简单规范的页面场景 | 简单规范的页面场景, |
优势 | 业务数据明确 | 开发成本低,运营人员可直接进行相关埋点配置 | 无需配置,数据可回溯 |
不足 | 数据不可回溯,开发成本高 | 不能关联业务数据,数据不可回溯 | 数据量较大,不能关联业务数据 |
大部分情况,我们可以通过无痕埋点收集到所有的信息数据,再配合可视化埋点,能够具体定位到某一个点位,这样大部分的埋点信息都据此分析出来。
在特殊情况下,可以多加上业务代码手动埋点,处理一下特别的场景(大部分情况是走强业务与正常的点击,刷新事件无关需要上报的信息)
上面的数据通过 3 个维度来定义埋点事件
根据上述的维度,我们可以简单设计如下的架构
根据上图的架构,再进行下面的具体代码开发
在浏览器中现在主要有 2 种请求方式,一个是 XMLHttpRequest, 一个是 Fetch。
function NewXHR() { var realXHR: any = new OldXHR(); // 代理模式里面有提到过 realXHR.id = guid() const oldSend = realXHR.send; realXHR.send = function (body) { oldSend.call(this, body) //记录埋点 } realXHR.addEventListener('load', function () { //记录埋点 }, false); realXHR.addEventListener('abort', function () { //记录埋点 }, false); realXHR.addEventListener('error', function () { //记录埋点 }, false); realXHR.addEventListener('timeout', function () { //记录埋点 }, false); return realXHR; }复制代码
const oldFetch = window.fetch; function newFetch(url, init) { const fetchObj = { url: url, method: method, body: body, } ajaxEventTrigger.call(fetchObj, AJAX_START); return oldFetch.apply(this, arguments).then(function (response) { if (response.ok) { //记录埋点 } else { //上报错误 } return response }).catch(function (error) { fetchObj.error = error //记录埋点 throw error }) }复制代码
在进入页面时,我们通过算法生成一个唯一 session id,作为这次埋点行为的全局 id,上报用户 id,设备指纹,设备信息。在用户未登录的情况下,通过设备指纹来计算 UV,通过 session id计算 PV。
异常就是干扰程序的正常流程的不寻常事故
在JS中可以通过 window.onerror和window.addEventListener('error', callback) 捕捉运行时异常,一般使用window.onerror,它兼容性更好。
window.onerror = function(message, url, lineno, columnNo, error) { const lowCashMessage = message.toLowerCase() if(lowCashMessage.indexOf('script error') > -1) { return } const detail = { url: url filename: filename, columnNo: columnNo, lineno: lineno, stack: error.stack, message: message } //记录埋点}复制代码
在这里我们过滤了 Script Error, 它产生的原因主要是页面中加载的第三方跨域脚本报错,比如托管在第三方 CDN 中的 js 脚本。这类问题比较难以排查。解决的方法有:
<script scr="crgt.js"></script> //加载crgt脚本,window.crgt = {getUser: () => string} try{ window.crgt.getUser(); }catch(error) { throw error // 输出正确的错误堆栈 }复制代码
js 在异步异常时无法通过 onerror 方法捕获 ,在 Promise 对象在 reject 时,同时并没有进行处理时 会抛出一个 unhandledrejection 的错误,并不会被上述的方法所捕获,所以需要添加单独的处理事件。
window.addEventListener("unhandledrejection", event => { throw event.reason });复制代码
在浏览器中,可以通过 window.addEventListener('error', callback) 的方式监听资源加载异常,比如 js 或者 css 脚本文件丢失。
window.addEventListener('error', (event) => { if (event.target instanceof HTMLElement) { const target = parseDom(event.target, ['src']); const detail = { target: target, path: parseXPath(target), } // 记录埋点 } }, true)复制代码
通过 addEventListener click 监听 click 事件
window.addEventListener('click', (event) => { //记录埋点}, true)复制代码
在这里通过组件的 displaName 来定位元素的位置,displaName 表示组件的文件目录,比如 src/components/Form.js 文件导出的组件 FormItem 通过 babel plugin 自动添加属性 @components/Form.FormItem,或者使用者主动给组件添加 static 属性 displayName。
window.addEventListener('hashchange', event => { const { oldURL, newURL } = event; const oldURLObj = url.parseUrl(oldURL); const newURLObj = url.parseUrl(newURL); const from = oldURLObj.hash && url.parseHash(oldURLObj.hash); const to = newURLObj.hash && url.parseHash(newURLObj.hash); if(!from && !to ) return; // 记录埋点})复制代码
通过 addEventListener beforeunload 监听离开页面事件
window.addEventListener('beforeunload', (event) => { //记录埋点})复制代码
class Observable { constructor(observer) { observer(this.emit) } emit = (data) => { this.listeners.forEach(listener => { listener(data) }) } listeners = []; subscribe = (listener) => { this.listeners.push(listeners); return () => { const index = this.listeners.indexOf(listener); if(index === -1) { return false } this.listeners.splice(index, 1); return true; } } }复制代码
const clickObservable = new Observable((emit) => { window.addEventListener('click', emit) })复制代码
然而在处理 ajax,需要将多种数据组合在一起,需要进行 merg 操作,则显得没有那么优雅,也很难适应后续复杂的数据流的操作。
const ajaxErrorObservable = new Observable((emit) => { window.addEventListener(AJAX_ERROR, emit) })const ajaxSuccessObservable = new Observable((emit) => { window.addEventListener(AJAX_SUCCESS, emit) })const ajaxTimeoutObservable = new Observable((emit) => { window.addEventListener(AJAX_TIMEOUT, emit) })复制代码
可以选择 RxJS 来优化代码
export const ajaxError$ = fromEvent(window, 'AJAX_ERROR', true)export const ajaxSuccess$ = fromEvent(window, 'AJAX_SUCCESS', true)export const ajaxTimeout$ = fromEvent(window, 'AJAX_TIMEOUT', true)复制代码
ajaxError$.pipe( merge(ajaxSuccess$, ajaxTimeout$), map(data=> (data) => ({category: 'ajax', data; data})) subscribe(data => console.log(data))复制代码
通过 merge, map 两个操作符完成对数据的合并和处理。
自建埋点系统是一个需要前后端一起合作的事情,如果人力不足的情况下,建议使用第三方分析插件,例如 Sentry 就能足够满足大部分日常使用
但还是建议多了解,在第三方插件出现不能满足业务需求的时候,可以顶上。
想了解更多编程学习,敬请关注php培训栏目!
以上就是初探埋点系统的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号