可视化埋点上报方案

STAR 法则描述

1. 背景

上报一直是开发时候比较难受的一个痛点吧,代码埋点的方案往往会在业务逻辑里掺杂很多上报的逻辑,就会非常影响代码的阅读,以及代码的整洁,会有一个很深的耦合。而且经过多次迭代之后代码可能就会变得非常混乱。

2. 任务

这个方案的目标是实现一个可视化的埋点上报方案,让业务方能够自己配置上报规则,然后在业务代码中不需要写上报逻辑,只需要在配置中写好规则,然后插件会自动去匹配规则,然后执行上报逻辑。

3. 行动

可视化埋点上报方案有一个基础依赖,就是要有一个业务基础库的支撑,整体是一个插件式的结构,埋点上报作为插件注入到基础库中,然后基础库会暴露一些 hook 钩子,插件中会对这些钩子做一些上报的逻辑。

4. 结果

使用这套方案之后,一个非常大的改观就是,代码中业务逻辑和上报逻辑实现了解耦,上报的逻辑是作为一份类似 json 配置文件共存在项目结构中的,后续迭代只需要修改上报规则即可,而不需要具体深入业务逻辑,从效率上也有一个很大的提高。

技术细节

一般对于上报来讲,分为曝光和点击两种情况,曝光最终都是使用 IntersectionObserver 这个浏览器接口实现的,点击事件则是使用 addEventListener(‘click’, …)实现的。从类型来看,可以分为全局事件(比如分享事件)、DOM 节点类型、Vue 组件类型这三种。所以就是要分别实现这三种情况的曝光和点击。

上报方案的前提是在插件式的业务框架基础上做的,这个后面三种类型分析的时候会再介绍。

1. 全局事件

主要是依赖于刚才提到的插件式业务框架,比如说分享事件,业务框架会在分享逻辑中暴露出一个 hook,然后埋点方案作为一个插件,提供回调方法,以便在分享触发的时候调用这个回调,然后就是具体的上报逻辑。

比如弹窗的事件上报,在弹窗事件中暴露出 hook,然后使用弹窗的时候给一个 name,规则中可以根据比如 rule.path === name 这种条件来判断是否是想要上报的弹窗。

2. DOM 节点

对于 DOM 节点的曝光,主要是基于浏览器的一个接口,MutationObserver 这个接口实现的,这个接口会监听页面创建的 DOM 节点。如果节点创建,然后再对节点进行规则匹配判断,对于满足条件的 DOM 创建 IntersectionObserver 来处理曝光事件。click 事件则是使用 addEventListener 来捕获判断(冒泡的话代码逻辑中可能会有停止的情况)点击的 DOM 节点和规则中写的 DOM 是否匹配,匹配的话就执行后续的上报。DOM 节点如果想要上报一些和业务关联比较紧密的数据只能把数据写在 html 标签上,比如 data-…这种自定义数据才能获取到数据,因此还是会有一些耦合。

场景一:一个 DOM 节点中包含不想触发点击上报的节点

可以在规则配置中加上对应的节点,然后规则匹配的逻辑中会先用 querySelectorAll 去找到所有不想匹配的节点,然后用 node.contains 去判断点击的 DOM 是否包含在不想触发节点的后代节点中,如果在里面,就会直接 return 掉,不触发后续的逻辑,就实现了对应区域不触发上报的逻辑。

场景二:想确定点击的 DOM 是第几个

可以先用 querySelectorAll 去查对应的 DOM 列表,然后去这个列表中去和点击的 DOM 比较,获得对应的下标,实现获取点击到第几个。

3. Vue 组件

对于 Vue 组件主要是依赖于业务框架对于 Vue 生命周期的封装,这里则是在组件 created()的时候回去匹配规则,如果满足的情况下,和上面类似创建 IntersectionObserver.

点击事件也是类似,如果匹配到了规则,那么会把当前组件的信息存入一个数组中,addEventListener 监听到点击时间的时候,会从里面匹配,找到后会把组件的 vm 实例的返回,因此上报的时候也能上报一些非常细节的参数。

场景一:想在一个 Vue 组件中只监听某个 DOM 节点的点击

可以在配置中添加一个参数,比如 target,然后去 Vue 实例上的 $el 去用 querySelecorAll 去找对应的 DOM 节点,然后用 node.contains 来判断是否是后代节点,以次来实现 Vue 组件中具体 DOM 的点击监听。如果说不想监听的情况与此相反即可。

1
2
3
4
5
6
<wg-column[eevee-tracker-id="v_if_isAdmin"]:nth-child(0)/>
return {
name: nameResult[1], // wg-column
id: nameResult[2], // eevee-tracker-id
index: nameResult[3] ? Number(nameResult[3]) : undefined, // nth-child(0)
};

上报规则中的自定义 function

上面简单介绍了一下整体实现方案,那对于上报规则我觉得有一个比较有意思的实现。比如有这种场景,vm.data 中有一个数组用来存标签,上报的时候数据同学希望上报成用下划线连接的字符串。对于这种场景,就需要上报规则去提供一个能对数据进行灵活处理的一个功能。

最开始想的是传入一个回调方法,但是不利于后面可视化方案的实现,假如传字符串然后 eval 执行的话也不太安全,而且 eval 很难做优化,所以性能上可能会有影响。所以这里就思考说提供一个类似 Lisp 语言的表达方式,比如对于 array 的 join 方法,可以提供 function: join($value, '_')类似这种,然后插件中会去进行编译原理那一套过程,词法分析成 token,然后语法分析将 token 转化成 ast 结构。然后遍历这个结构,找到对应的方法名称后会去调对应的方法。