埋点方案

85 阅读5分钟

埋点现状

  • 乱且无规范,所有埋点事件在一个文件里,属于什么业务需要看一层层调用
  • 参数多且多层传递,不好维护和修改

比如:

若该埋点需要增加一个字段,至少需要修改这三个地方,比较耗时;且必须花费一些时间去理解每一层做了什么;徒增开发成本

目的:

增强埋点与业务的联系,便于管理

减少参数传递,减少代码重复,维护埋点代码质量

埋点方案

规范:公共协议 Report,定义了上报事件必需的属性,另外定义了函数来获取最终上报的数据结构

/// 上报平台类型
enum ReportPlatform {
    case sensor     // 神策
    case zeus       // 宙斯 绝大部分埋点两个平台同时上报
    case appsFlyer    // 部分旧埋点有上报,但新事件没有
}

protocol Report {
    /// 事件名
    var eventName :String { get }
    /// 事件属性
    var properties: [String : Any] { get }
    /// 上报平台
    var platform: [ReportPlatform] {get}
    /// 获取上报数据结构
    func getReportableData() -> [String : Any]
}

基础上报模型 ReportBaseModel,遵循Report协议,所有埋点事件的上报数据模型都组装为它的子类;该模型提供默认的上报平台,实现获取上报数据结构函数

struct ReportBaseModel: Report {
    /// 上报数据结构key:事件名
    static let eventIdKey = "eventId"
    /// 上报数据结构key:事件属性
    static let eventInfoKey = "data"
    
    /// 子类应重写事件名
    /// 每个埋点事件创建单独的上报模型,提供唯一的事件名
    var eventName :String {
        ""
    }
    
    /// 子类应重写事件上报的字段
    var properties: [String : Any] {
        ["" : ""]
    }
    
    /// 默认上报神策,宙斯
    var platform: [ReportPlatform] {
        [.sensor, .zeus]
    }
    
    /// 获取上报数据结构
    /// - eventId : 事件名
    /// - data: 事件的属性/字段
    /// - time: 上报时间
    /// - Returns: 最终上报到平台的字典结构
    func getReportableData() -> [String : Any] {
        let reportData: [String : Any] = [Self.eventIdKey: self.eventName,
                                          Self.eventInfoKey : self.properties,
                                          "time" : "\(Int(Date().timeIntervalSince1970))"]
        /// 检查字典转Json是否成功,提前暴露properties类型问题
        DSReporter.checkProperties(properties: self.properties)
        return reportData
    }
}

统一上报实现

通过model的platform判断需要上报到哪些平台

class DSReporter {
	/// 检查字典转Json,测试环境直接报错;可以知道哪个埋点事件报的错,从而找出属性值赋值错误
	static func checkProperties(properties: [String : Any]) {
#if DEBUG
		try? _ = JSONSerialization.data(withJSONObject: properties, options: .prettyPrinted)
#endif
	}

	/// 根据模型的上报平台,上报事件
	static func reportEvent(_ model: Report) {
		let reportData: [String : Any] = model.getReportableData()

	/// 上报神策
		if model.platform.contains(.sensor) {
			DSReportClient.shared.submitReport(reportData)
		}

	/// 上报宙斯
		if model.platform.contains(.zeus) {
			DSReportClient.shared.submitSensorReport(reportData)
		}

	/// 上报AppsFyler
		if model.platform.contains(.appsFlyer) {
			AppsFlyerLib.shared().logEvent(model.eventName, withValues: nil)
		}
	}
}

埋点事件上报

每个埋点事件创建独有的上报数据模型,该模型继承ReportBaseModel,在模型内部维护事件名eventName,处理并组装成事件属性properties;然后在业务通过DSReporter.reportEvent(model)上报事件

  • 普通埋点【单处业务调用】,在业务模块处建立Report文件夹,该业务的相关埋点事件都放在该文件夹中;为每个埋点事件创建继承ReportBaseModel的上报数据模型

    /// 定义上报数据模型,继承ReportBaseModel // DSTabSwitchReportModel.swift class DSTabSwitchReportModel: ReportBaseModel { var tabInfo: DiscoverTab = DiscoverTab() /// 0 默认选中 1 用户主动选中 var isClick: Bool = false /// 重写事件名 override var eventName: String { return "tabSwitch" } /// 重写事件属性字段 override var properties: [String : Any] { let tabTypeValue = tabInfo.type == .tabTypH5 ? "H5" : "regular" let type = isClick ? 1 : 0 let params: [String : Any] = [ "tab_id" : tabInfo.id, "tab_name" : tabInfo.name, "tab_type" : tabTypeValue, "type" : type, ] return params } }

    /// 业务上报处 func segmentedView(_ segmentedView: JXSegmentedView, didSelectedItemAt index: Int) { guard let homeModel = homeModel else { return } let tab = homeModel.tabList[index] let tabSwitchReportModel = DSTabSwitchReportModel() tabSwitchReportModel.tabInfo = tab tabSwitchReportModel.isClick = isUserClick DSReporter.reportEvent(tabSwitchReportModel) // 调用通用上报 }

  • 其中有些业务的多个埋点事件属性有较多相同的属性,提取成这些埋点事件的公共基类,这样无需为每一个类似的埋点属性进行数据处理和构建字典,可大大减少重复代码

比如:

  • 通用埋点【多处业务调用】

    • 通用上报文件夹,通用埋点事件上报模型都放在该文件夹中,每个通用埋点事件创建上报模型,相关类型放在对应上报模型文件中

书籍曝光、上报、点击

按钮点击

浮窗曝光

比如:

  • 参数只有一个或者没有的埋点事件,在埋点上报模型中提供快捷上报方法,简化上报

比如按钮点击:

新旧方案埋点对比分析

同一个埋点事件新旧埋点方式实现对比

旧埋点方案

// 原上报方式如下:传递三层参数,第一层业务上报处,第二层处理参数,第三层处理成上报字典中的data字段
/// 业务上报处
    func segmentedView(_ segmentedView: JXSegmentedView, didSelectedItemAt index: Int) {
          guard let homeModel = homeModel else { return  }
          currentTabId = homeModel.tabList[index].id
          let tab = homeModel.tabList[index]
          DSReportClient.reportTabSwitch(tabIndex: index, tabId: currentTabId, tabName: tab.name, tabType: tab.type, isShowImage: false, isClick: isUserClick)
    }
// 处理参数: DSReportClient.swift
/// 发现页tab-探索页 tab切换 埋点
    static func reportTabSwitch(tabIndex: Int, tabId: Int32, tabName: String, tabType: DiscoverTabType, isShowImage: Bool, isClick: Bool = false) {
        let type = isClick ? 1 : 0
        let tabTypeValue = tabType == .tabTypH5 ? "H5" : "regular"
        let eventObj = DSReportConst.TabSwitchEvent(tabIndex: tabIndex, tabId: tabId, tabName: tabName, tabType: tabTypeValue, is_image_show: isShowImage, type: type)// 处理数据处 
        DSReportClient.shared.submitReport(eventObj) // 上报处
    }

/// 返回上报字段:DSReportConst.swift 
/// 发现页tab-探索页 tab切换
    static func TabSwitchEvent(tabIndex: Int, tabId: Int32, tabName: String, tabType: String, is_image_show: Bool, type: Int) -> [String: Any] {
        let data: [String: Any] = [
            "tab_id"       : tabId,
            "tab_name"     : tabName,
            "tab_type"     : tabType,
            "is_image_show": is_image_show,
            "type"         : type,
        ]
        return baseEvent(id: "tabSwitch", data: data)// 
    }

新埋点方案

/// 定义上报数据模型
    class DSTabSwitchReportModel: BaseReportModel {
      var tabInfo: DiscoverTab = DiscoverTab()
      /// 0 默认选中 1 用户主动选中
      var isClick: Bool = false
    
      override var eventName: String {
          return "tabSwitch"
      }
      
      override var properties: [String : Any] {
          let tabTypeValue = tabInfo.type == .tabTypH5 ? "H5" : "regular"
          let type = isClick ? 1 : 0
          let params: [String : Any] = [
              "tab_id"       : tabInfo.id,
              "tab_name"     : tabInfo.name,
              "tab_type"     : tabTypeValue,
              "type"         : type,
          ]
        return params
    }
}

/// 业务上报处
    func segmentedView(_ segmentedView: JXSegmentedView, didSelectedItemAt index: Int) {
          guard let homeModel = homeModel else { return  }
          currentTabId = homeModel.tabList[index].id
          let tab = homeModel.tabList[index]
          // 上报tab切换
          let model = DSTabSwitchReportModel()
          model.tabInfo = tab
          model.isClick = isUserClick
          DSReporter.reportEvent(model)
    }
  • 无需多层传递参数
  • 不需为每个埋点事件写相同的属性处理代码
  • 埋点事件的事件名,事件属性在模型内部统一管理,外部无需关心