专栏名称: 美团技术团队
10000+工程师,如何支撑中国领先的生活服务电子商务平台?数亿消费者、数百万商户、2000多个行业、几千亿交易额背后是哪些技术在支撑?这里是美团、大众点评、美团外卖、美团配送、美团优选等技术团队的对外窗口。
目录
相关文章推荐
51好读  ›  专栏  ›  美团技术团队

美团点评前端无痕埋点实践

美团技术团队  · 公众号  · 架构  · 2017-03-02 19:38

正文

请到「今天看啥」查看全文


然而,新的问题又出现了。


如果引用的第三方库中重写了UI控件,上述方法是不生效的,也就是说我们需要一种替换UI控件类的父类方法。可是在运行时,我们没有找到可行的替换UI控件类的父类方法。因此,我们尝试在编译时修改父类,并开发了一个Gradle插件。事实上,这样做并不存在运行时效率的问题,只是会牺牲一些编译速度。这样开发者只需要运行这个插件,就可以实现自动将UI控件的父类替换为我们重写的UI控件了。

apply plugin: 'com.meituan.judasplugin'


采用了声明式埋点后,只需要在控件初始化时声明一下需要的埋点就可以了。我们不必再侵入程序的各种响应函数,降低了埋点的难度。

GAHelper.bindClick(view, bid, lab);


iOS


在iOS中,利用Objective-C关联属性和类别的语法特性,我们无需重写UI控件,就能实现声明式打点。对于UIControl,可以在声明埋点时添加新的action,并在事件发生时自动填写埋点代码。

- (void)nvja_setAnalyticsParams:(NVJAMGEParameter *)params mgeType:(SAKStatisticsEventMGEType)type
{
    if (self.wmja_clickParams == nil && type == SAKStatisticsEventClick) {
        [self addTarget:self action:@selector(wmja_controlDidTapped:) forControlEvents:UIControlEventTouchUpInside];
    }
    [super nvja_setAnalyticsParams:params mgeType:type];
}


对于UITableView,可以通过重写UITableViewDelegate,利用消息传递机制拦截事件,并在事件回调方法中自动填写埋点代码。

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    SEL selector = [anInvocation selector];
    if (self.originalDelegate && [self.originalDelegate respondsToSelector:selector]) {
        [anInvocation invokeWithTarget:self.originalDelegate];
    }
    SEL nvjaSelector = [self nvjaSelector:selector];
    if ([super respondsToSelector:nvjaSelector]) {
        [anInvocation setSelector:nvjaSelector];
        [anInvocation invokeWithTarget:self];
    }
}


同样的,采用了声明式埋点后,埋点代码得到了简化。

NVJAMGEParameter *parameter = [[NVJAMGEParameter alloc] init];
parameter.bid = @"bid";
parameter.lab = @{@"poi_id":@"1"};
button.nvja_clickParams = parameter;


声明式埋点能够替代所有的代码埋点,并且能解决早期遇到的移植成本高等问题。但是其本质上还是一种代码埋点,只是埋点的代码减少了,并且不再侵入业务逻辑了。如果要满足动态部署与修复埋点的需求,就需要彻底消灭写死在前端的埋点代码。


无痕埋点


我们注意到,之所以声明式埋点还需要写死代码,主要有两个原因:第一是需要声明埋点控件的唯一事件标识,即bid;第二是有的业务字段需要在前端埋点时携带,而这些字段是在运行时才可获知的值。


对于第一点,我们可以尝试在前后端使用一致的规则自动生成事件标识,这样后端就可以配置前端的埋点行为,从而做到自动化埋点。对于第二点,可以尝试通过某种方式将业务数据自动与埋点数据关联,这种关联可以发生在前端,也可以发生在后端。


事件标识


为了自动生成事件标识,我们需要获取每个控件自身的ID、类名以及位于所属父组件的Index等特征信息,并逐级向上遍历找到根节点。根节点一般是手动标记的,如果没有标记则默认是视图层次树的顶层节点。最后,将遍历产生的路径上所有节点的特征信息组合在一起,就是这个事件的标识。考虑到在实际布局中有可能存在一些动态插入的控件,我们允许父组件的Index有一定的误差。


配置后台需要维护自动生成的事件标识和bid的映射关系,并且可以下发给前端一个配置文件。当前端控件事件触发时,自动和配置文件匹配就可以拿到对应的bid了。需要注意的是,配置后台维护事件标识的工作可不是一件轻松的事情,主要的复杂性在于不同版本之间布局变更导致的事件标识变更,这就是为什么还需要手动标记根节点的原因。所以,一般我们会选取不易变更的视图节点。



数据关联


为了实现业务数据与埋点数据的自动关联,我们起初尝试了前后端日志关联的方式。即在前端请求后端API的时机,由后端将业务数据写入日志,最后在数据清洗时将相对应的前后端日志合并。这种方式带来的问题是后端改造成本较高,并且数据清洗的开销较大,因此并不能广泛应用。但是在一些特殊场景下,例如某些业务数据只有后端可以获知,而前端不能获知时,这种关联是必要的。


更常见的数据关联发生在前端数据之间。当页面跳转时,通过传递规范的跳转URI Scheme,将业务数据传递给下个页面,并且自动填入这个页面的PV事件中。而该页面内产生的所有其他事件,都会携带与PV事件相同的业务数据。


这样,通过自动产生事件标识并进行数据关联,我们就能够实现“无痕埋点”了,并且埋点节点可以通过配置文件动态下发,从而具备了动态部署与修复埋点的能力。但需要注意的是,这种“无痕埋点”并不能解决所有问题,当业务字段无法通过数据关联获取时(这种情况比较常见),仍然需要开发者代码埋点或声明式埋点指定业务字段。就目前实践阶段的数据来看,业务中大约70%左右的埋点需求可以通过无痕埋点解决,而对于另外30%的埋点需求,仍然需要使用声明式埋点和代码埋点。







请到「今天看啥」查看全文