Web 前端埋点的总结
前端埋点的重要性
这个不说了,作为大数据平台的采集环节,上报数据的准确性、全面性,其重要性不言而喻。
几个问题
- 埋点有那些方式?
- 如何发送上报数据?
- 通用埋点SDK如何写?
- 手机App嵌入h5时,如何同步会话?
- 如何确保上报数据准确?
埋点有那些方式
- 可视化埋点. 基于其固化的技术环境,最易实现,如可视化拖拽页面的cms平台中。
- 声明式埋点. 如基于dom自定义属性的绑定,或基于其它模板技术的节点属性绑定, 或基于配置文件的运行时解析。
- ‘无侵入’埋点. 在SDK中全局地绑定ui组件的事件,如全局绑定onclick, touchstart, touchend, onscroll等事件. 但这种方式容易产生大量的埋点,给服务器带来压力,且其属于宽泛的埋点,统计上不具备针对性。 另外由于前端运行环境天生的耦合性,这些全局事件很容易被重写,拦截,导致埋点失效。 实际中往往是有限地使用它.
- 手动埋. 最灵活,缺点是工作量大,且散落在业务代码中,不易维护。 实际中由于需要携带业务埋点数据,且编程模式的不确定性(可能需要埋点在复杂的多个异步流程中),当自动化埋点无法达到要求时,手动埋不可避免。 优化的处理是:根据当前使用的技术,尝试做埋点切面或使用包装器模式,把埋点逻辑解耦出来.
总之:Web埋点很难使用单一技术解决方案来解决,埋点方法很多种,需要根据当前应用场景,技术栈环境,业务要求等综合因素决定。
如何发送上报数据
基本上惯例的做法是使用img标签单条发送一个http get请求。 数据长度限制基本不用担心,保守的8k,基本够用了。 具体参考:What is the maximum length of a URL in different browsers? 考虑到一些场景埋点上报并发较大,可使用批量埋点,具体做法是: 在localstorage中维护一个埋点队列,调用者的单条发送先暂存在队列里,然后周期性地将队列中的几个点一并上报。
批量埋点带来几个问题:
- http get地址长度限制。 8k基本上发10个左右的点够用了,如果担心超长,发送策略上可采用‘周期性 + 埋点个数‘综合的方式,如:每10s或超过8个点时发送一次。 实在不行也可模拟http post上报.
- 埋点时间不对了。 单个埋点上报时,即取用服务器的当前时间。但如果批量的话,服务器就不知道每个点所对应的真实时间了。 此时在每个点中添加 time_offset 字段,在批量上报前,根据当前时间与埋点数据加入到队列的时间,算出该点在队列中的存在时长,然后服务器端可由此计算出每个点的真实上报时间.
- 上报延迟了,如果此时浏览器退出,埋点数据可能会漏报. 这个好像无解,本地埋点队列的数据不会丢,用户下次pv时也会上报,但埋点会话同步及数据平台的统计周期,都有问题. 所以关键性埋点最好强制即时上报。SDK也应该普通埋点与即时埋点上报的api.
通用埋点SDK如何写?
- 轻量小巧,不依赖其它库,方便引用。
- 针对不同技术环境,提供多种埋点SDK,如: a. jQuery小型项目使用的SDK,基于dom自定义属性实现. b. vue框架下使用的SDK,基于vue自定义指令实现.
- 最大限度地实现自动化埋点,且提供埋点流程各环节的切入方式。 使即可最懒惰的一行代码埋点,也可后期修改发送的埋点数据的各个field。
- SDK自己做好容错处理, 使埋点上报开销甚至埋点crash不会影响业务.
- 简洁的api
手机App嵌入h5时,如何同步会话?
由于h5嵌入在app webview内,此时h5的埋点参数应该使用app的埋点参数,如: device_id, spm, os(操作系统), lat/lng(app定位更准确)等. 即需要app在调用h5时,将自身上下文参数传递过来,供后续h5上报埋点使用(不存在h5反向传参的需求).
有两种方式
通过url传递
即在url中通过query params传递,以query name的特殊前缀指明该参数是app传过来的埋点数据,如下所示:
string url = "https://meicai.com/cms?id=1&__app__device_id=DSFSDF&__app__os=android&__app__spm=2.2.3.1&...";
webView = (WebView) findViewById(R.id.webView);
webView.loadUrl(url);
然后在埋点SDK中统一处理, 如下:
let appParam
window.addEventListener('load', function(){
let appParams = getQuerysObject('__app__') || {};
localStorate.set('__app_params__', JSON.stringify(appParams)); //缓存起来,后续埋点使用.
})
let mxticker = {
tick(data){
//...
let appParams = localStorate.get('__app_params__');
if(appParams){
//优先使用app传过来的参数.
data = Object.assign(data, JSON.parse(appParams));
}
this.send(data);
}
}
h5调用app提供本地方法,主动获取。
let mxticker = {
tick(data){
//...
if(this.isInApp){
let appParams = {};
if(this.isInAndorid){
appParams = androidBridge.getTickerParams();
}
if(this.isInIos){
appParams = iosBridge.getTickerParams();
}
data = Object.assign(data, appParams);
}
this.send(data);
}
}
如何确保上报数据的准确性
准确地获取用户交互轨迹的上下文信息,并成功上报Server。 对于前者只需编码正确,以及注意一下异步编程模式下的信息获取即可; 对于后者,本质上这是个如何把握数据上报时机的问题,由于浏览器环境所限制,无法100%保证在一些交互环节都能成功上报,如页面退出时。
如果感觉前端上报有一定漏报风险的话,部分关键点可放到上层接口内,如,php的services层.
埋点上报数据的发送应该是稀疏串行的,要避免并发大量的埋点请求,否则可能会阻塞浏览器其它的计算或资源下载任务,影响用户体验,并且也增加了服务器压力。 可批量异步上报, 埋点请求不阻塞正常业务;
更好的可维护性
主要是手动埋点,试想一下,如果埋点代码与其它逻辑耦合在一块的话,随着项目的功能迭代及代码重构,可能哪一天报表上的折线突然出现了一个深谷,然后排查了很长时间发现是不小心埋点上报逻辑误删了或未执行到 😦
并且这些问题测试同学也很难覆盖到.
这时需要根据当前技术环境,把埋点逻辑‘关联’进业务代码 或 把埋点的特定逻辑写在同一个文件,在业务代码中仅使用固定的调用代码。 最后使埋点的添加 由配置文件驱动。