公司系统要求加跟踪功能,用于统计每个页面功能的使用情况。因此,结合网上资料和以往使用埋点系统的经验,认真研究研究。研究和分类中常见的三种埋点类型。埋点通过JavaScript代码主动向服务器上报所需信息。优点:可以准确上报需要的数据,比较适合埋点少的情况。缺点:代码分散在整个项目中,不易维护和管理。而埋点只能由开发人员手动完成。可视化埋点需要另外一个可视化埋点圈选择系统来圈出需要埋点的DOM元素。然后通过在系统中集成SDK,主动上报这些区域的埋点信息。其实就是另一种代码埋点。优点:有圈选制度,可以让产品和运维同学自己决定埋在哪里。缺点:适用范围有限,如内网系统、移动端混合页面等,难以使用外部视觉嵌入点。无埋点其实叫全埋点,即全局监控系统事件,上报所有用户行为。优点:行为数据记录全面,无需添加或维护嵌入式代码。缺点:上报数据量大,对服务器造成一定压力。并且无法准确上报某项功能的特停数据。埋点目标数据监控:让产品运维同学通过埋点了解项目当前的具体情况,从而有针对性地对项目进行优化。异常监控:从开发的角度,收集项目中出现的JS报错、接口报错等异常情况。发现问题,解决问题,优化项目。性能监控:采集项目运行过程中的各种性能指标,如白屏时间、首屏加载时间、界面请求时间等。埋点SDK实现猜想以我之前工作中使用的埋点系统GrowingIO为例。我们可以通过其SDK文档来验证上述理论。它通过在全局引入JS代码来集成,它会在window全局对象下添加一个gio函数来处理各种隐藏行为。由于埋点系统会服务于很多项目,所以需要在初始化的时候加上gio('init','yourprojectId',{})。它需要在需要圈出的DOM元素上添加data-growing-container属性。这其实就是HTML元素的dataset属性,可以用来在元素上读写自定义数据属性。有了圆圈标记,就可以指向埋点事件被拦截时打到哪里了。它通过gio('track',eventId,eventLevelVariables);实现主动跟踪行为;功能,这自然是必不可少的。总是需要埋点不能自动完成的。它的非埋点记录记录了所有元素的点击和浏览,应该是对元素的点击和视觉事件的全局监控。它的可视化圈选使用XPath来唯一定位一个元素,所以可视化圈选实际上是保存目标DOM的xPath,在埋点的时候获取指定DOM元素的点击和访问。(xpath的使用参见JavaScript中XPath的使用介绍-XPath|MDN)我选择的埋点方案由于项目的埋点只需要记录一些指定的行为,所以完整的埋点方案被我pass了。同时做埋点的圈选也不需要另外写一个页面。最后我选择了最简单粗暴的主动埋点。主动埋1.0一开始其实很简单,通过JavaScript代码编写埋点代码实现。定义埋点工具对象。//logger.jsexportdefault{...,track(data){constconfigInfo=this.getConfigInfo()//一些公共的配置信息,比如username,token,time,url等returnfetch.post('/api/v1/web/log',{...data,...configInfo,})},}将记录器对象绑定到Vue的原型。//main.jsVue.prototype.$logger=记录器在需要的地方主动埋点。下载
遇到的问题其实和主动埋点一样,只是随着埋点代码的逐渐增加(数量从一开始的20个增加到203个……)。看代码的时候很不舒服。描述一个场景:你需要检查同事代码中的埋点。既然不知道他的代码,就需要一点点的找。全局搜索埋点代码$logger.track(),得到n个包含埋点代码的函数。然后逐一跟踪这些包含埋藏代码的函数(有时是函数内嵌函数)的触发位置,最后找到绑定该函数的DOM元素。只有这样才能确定一个元素有埋点行为。DeclarativevsImperative面对上面的场景,我在想有没有办法省去一个一个检查函数的步骤,让主动代码埋点更直观。这里还有一点不得不提:声明式代码和命令式代码的区别。声明式代码:如HTML、XML、CSS等,其特点是可读性更强,描述更直观生动。命令式代码:如JavaScript,其特点是更符合行为步骤的思维模式,适合处理一些逻辑功能。举几个例子,比如画一幅画,陈述式的描述是“我要画一幅画,上面有草,有树,有天空”;而命令式的描述是“我要画一幅画,要画,先画草,再画大树,最后加上蓝天”。再比如,vue中有一个createElement函数,可以在vue的render函数中命令式的创建DOM元素。createElement('anchored-heading',{props:{level:1,},},[createElement('span','Hello'),'world!'],)但是这种命令式的写法在可读性上有很大的区别。Vue官方也发现了这个问题,于是引入了JSX来弥补这个缺陷。importAnchoredHeadingfrom'./AnchoredHeading.vue'newVue({el:'#demo',render:function(h){return(你好世界!)},})JSX显然更具声明性。然后回头回顾一下主动埋点的目的:通过代码主动上报指定DOM元素的行为事件。所以我个人觉得还是用declarative的写法比较好。Activeburying2.0justdoit,我尝试把imperativeburying改成declarativeburying。首先在入口文件main.js中引入全局注册逻辑。//事件名称constCOMPONENT_MAP={1:'图表切换',2:'下载按钮',}//修复点击子元素不上报埋点信息的问题functionbindDataset(el,value){el.dataset.loggerId=value//递归绑定dataset到所有子集el.children.forEach((child)=>{bindDataset(child,value)})}//全局注册指令,将datasetVue添加到需要嵌入的DOM中.directive('logger',{bind:function(el,binding){const{value}=bindingbindDataset(el,value)},})//全局监听组件点击事件,加入防抖避免快速重复点击document.addEventListener('click',throttle((e)=>{if(e.target.dataset.loggerId){this.$logger.track({component_id:e.target.dataset.loggerId,component_name:COMPONENT_MAP[e.target.dataset.loggerId],})}},2000),)在上面的代码中,我通过vue指令将埋点信息绑定到目标DOM的dateset上。然后通过拦截全局点击事件获取目标元素的点击行为,并上报埋点信息。由于没找到在Vue组件上直接操作DOM的方法(ref不算,写一大堆ref='xxx'不划算),于是想到了Vue命令。当点击一个DOM元素时,如果元素中有子节点,全局的点击事件只能捕获子节点的事件,所以我偷懒了,把所有的子节点都加到了数据集中。(组件中没有太多的子元素,偷懒了。)我个人觉得以上两个问题都不是最好的解决方法。如果你有好的解决方案,欢迎讨论!用法如下,可读性强很多。
这样以后看被埋代码的时候,只要简单的搜索一下,就可以很容易的看到有哪些DOM元素v-loggerglobally或者嵌入vue组件。无需反复检查各种事件。最后折腾了很多,主要是想解决主动埋代码太恶心的问题。然后顺便复习一些知识点。声明式编程和命令式编程埋点相关知识数据集xpath参考资料growingIOjsSDK文档数据集|MDNJavaScript中使用XPath的介绍-XPath|MDN)https://juejin.cn/post/6844903650603565063https://juejin.cn/post/7163046672874864676https://juejin.cn/post/7085679511290773534https://time.geekbang.org/column/article/140196