关于埋点:Vue-项目声明式主动埋点

31次阅读

共计 4188 个字符,预计需要花费 11 分钟才能阅读完成。

公司零碎需要加上埋点性能,用来统计各页面性能的应用状况。于是,联合网上材料以及之前应用埋点零碎的经验,认真钻研钻研。

调研

埋点分类

常见的埋点类型有三种

  • 代码埋点

    • 通过 JavaScript 代码被动将所须要的信息上报给服务器。
    • 长处:能够准确的上报所需的数据,对于大量埋点需要较为适合。
    • 毛病:代码遍布我的项目各处,不好保护治理。且埋点只能通过开发人员手动实现。
  • 可视化埋点

    • 须要另外一个可视化埋点圈选零碎来圈选须要埋点的 DOM 元素。而后通过在零碎中集成 SDK 来被动上报这些区域的埋点信息。其实算是另一种意义上的代码埋点。
    • 长处:有圈选零碎能够让产品、运维同学自行决定埋点区域。
    • 毛病:适用范围无限,如内网零碎、挪动端 Hybrid 页面这些就很难用内部的可视化埋点来做。
  • 无埋点

    • 其实也叫全量埋点,即全局监听系统事件,把用户所有行为都进行上报。
    • 长处:行为数据记录全面,无需减少或保护埋点代码。
    • 毛病:上报数据量大,对服务器有肯定压力。且无奈准确上报某一性能的特停数据。

埋点指标

  • 数据监控:通过埋点让产品运维同学晓得我的项目以后的具体情况,从而有针对性的去优化我的项目。
  • 异样监控:从开发角度去收集我的项目中产生的 JS 报错、接口报错等异常情况。发现问题、解决问题、优化我的项目。
  • 性能监控:收集我的项目运行中的各种性能指标,如白屏工夫、首屏加载工夫、接口申请工夫等等。

埋点 SDK 实现猜测

以我之前工作中用到过的埋点零碎 GrowingIO 为例。咱们能够通过它的 SDK 文档 来验证下面的实践。

  • 它通过全局引入 JS 代码的形式来进行集成,它会在 window 全局对象下加上一个 gio 函数解决各种埋点行为。
  • 因为埋点零碎会为很多我的项目服务,所以须要初始化的时候加上 gio('init', 'your projectId', {})
  • 它要求在须要圈选的 DOM 元素上 data-growing-container 属性,这其实是 HTML 元素的 dataset 属性,能够用来对元素进行自定义数据属性的读写操作。有了圈选标记,埋点事件拦挡的时候就能够指哪打哪了。
  • 它通过 gio('track', eventId, eventLevelVariables); 函数实现了被动埋点行为,这个天然是必不可少的。总有埋点需要是主动埋点做不了的。
  • 它的无埋点记录的是所有元素的点击量和浏览量,应该是全局监听了元素的点击和可视事件。
  • 它的可视化圈选是通过 XPath 来惟一定位一个元素的,那么可视化圈选其实就是将指标 DOM 的 xPath 保存起来,在埋点的时候去获取指定 DOM 元素的点击量和访问量。(对于 xpath 的应用能够看 Introduction to using XPath in JavaScript – XPath | MDN)

我的埋点

计划抉择

因为我的项目的埋点只须要记录一些指定的行为,所以全埋点计划被我 PASS 了。同时也没有必要另外写一个页面去做埋点的圈选,最终,抉择了最简略粗犷地被动埋点。

被动埋点 1.0

一开始埋点其实很简略,通过在 JavaScript 代码中写埋点代码来进行实现。

定义一个埋点工具对象。

// logger.js
export default {
  ...,
  track(data) {const configInfo = this.getConfigInfo() // 一些公共配置信息,如用户名、token、工夫、url 等
    return fetch.post('/api/v1/web/log', {
      ...data,
      ...configInfo,
    })
  },
}

将 logger 对象绑到 Vue 的原型中。

// main.js
Vue.prototype.$logger = logger

在须要的中央被动埋点。

<template>
  <div>
    <el-button @click="download">download</el-button>
  </div>
</template>

<script>
  export default {
    name: 'demo',
    methods: {download() {window.open('file url')
        this.trackLogger()},
      trackLogger() {
        this.$logger.track({
          component_id: '2',
          component_name: '下载按钮',
        })
      },
    },
  }
</script>

<style lang="scss" scoped></style>

遇到的问题

其实被动埋点应该就是如此,但随着埋点代码的逐步增多(曾经从起初的 20 条减少到 203 条了……)。看代码的时候就十分好受了。形容一个场景:

  • 须要查看共事代码中的埋点状况,因为不分明他的代码,就须要一点点找了。
  • 全局搜寻埋点代码 $logger.track(),失去 n 个蕴含有埋点代码的函数。
  • 再一一跟踪这些蕴含埋点代码的函数的触发地位(有时候还会是函数嵌函数),最终找到绑定函数的 DOM 元素。
  • 如此才算是确定了一个元素领有埋点行为。

申明式 vs 命令式

面对下面的场景,我在想有没有方法可能省去一一查函数的步骤,让被动埋点代码更加直观呢。这里就得提到另外一个点了:申明式代码与命令式代码的区别了。

  • 申明式代码:如 HTML、XML、CSS,它的特点是可读性更强,形容的时候更合乎直觉、更形象。
  • 命令式代码:如 JavaScript,它的特点是更合乎行为步骤的思考模式,适宜解决一些逻辑性强的性能。

举几个栗子

比方画一幅画,用申明式的形式来形容是“我要画一幅画,它有青草、大树和天空”;而用命令式的形式形容是 ” 我要画一幅画,首先须要画青草,而后再画大树,最初加上蓝色的天空。”

还有一个例子,在 vue 中有一个 createElement 函数,它能够在 vue 的 render 函数中命令式的创立 DOM 元素。

createElement(
  'anchored-heading',
  {
    props: {level: 1,},
  },
  [createElement('span', 'Hello'), 'world!'],
)

但这种命令式的写法可读性很差。vue 官网也发现了这个问题,于是引进了 JSX 来补救这个缺点。

import AnchoredHeading from './AnchoredHeading.vue'

new Vue({
  el: '#demo',
  render: function (h) {
    return (<AnchoredHeading level={1}>
        <span>Hello</span> world!
      </AnchoredHeading>
    )
  },
})

JSX 的写法显著就更偏差于申明式。

那么回过来温习下被动埋点的目标:通过代码被动上报 指定 DOM 元素 的行为事件。所以个人感觉用申明式写法会更好一些。

被动埋点 2.0

说干就干,我试着将命令式埋点改为申明式埋点。

首先在入口文件 main.js 中引入全局注册逻辑。

// 事件名称
const COMPONENT_MAP = {
  1: '图表切换',
  2: '下载按钮',
}

// 修复点击子元素不上报埋点信息的问题
function bindDataset(el, value) {
  el.dataset.loggerId = value
  // 递归绑定 dataset 到所有子集上
  el.children.forEach((child) => {bindDataset(child, value)
  })
}

// 全局注册指令,在须要埋点的 DOM 上加上 dataset
Vue.directive('logger', {bind: function (el, binding) {const { value} = binding
    bindDataset(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 下面。而后通过 全局 click 事件拦挡 来取得指标元素的点击行为,并上报埋点信息。

  • 因为没有找到如何间接在 Vue 组件上间接操作 DOM 的形式(ref 不算,那个须要写很多的 ref=’xxx’ 很不划算),所以想到了 Vue 指令。
  • 在点击 DOM 元素的时候,如果元素中有子节点那么全局 click 事件只能捕捉到子节点的事件,于是我偷懒将子节点都加上了 dataset。(组件的子元素不会太多,偷个懒了)

以上遇到的两个问题个人感觉不是最佳计划,如果有好的解决方案欢送探讨呀!

应用形式如下,可读性上强了不少。

<div class="filter-wrap" @click="setFilterPopupVisible(true)" v-logger="1">
  <img class="filter-icon" :src="filterIconUrl" />
  <img
    class="filter-icon-checked"
    :src="filterSelectedIconUrl"
    v-show="isFilterActive"
  />
</div>

如此,当前在看埋点代码的时候只有全局搜寻 v-logger 就能够很不便的看到有哪些 DOM 元素或者 vue 组件是进行了埋点的了。不须要重复去查各种事件了。

最初

折腾了一圈,次要就是想解决看被动埋点代码太恶心的问题。而后顺便温习一些知识点。

  • 申明式编程和命令式编程
  • 埋点相干常识
  • dataset
  • xpath

参考资料

  • growing IO js SDK 文档
  • dataset | MDN
  • Introduction to using XPath in JavaScript – XPath | MDN)
  • https://juejin.cn/post/6844903650603565063
  • https://juejin.cn/post/7163046672874864676
  • https://juejin.cn/post/7085679511290773534
  • https://time.geekbang.org/column/article/140196

正文完
 0