前端代码埋点折腾📝

11334

背景

当前项目所用的是代码埋点,由于历史原因这种侵入式埋点竟还同时存在两套(其实算三套)分别是apm和通过公司接口曝光exposure上报统计(历史原因还有trackOsrRecord同样是接口上报统计)。代码埋点虽然能精细化的去上报数据,但随着侵入式埋点的增加,存在了一下问题

  • 业务需求频繁迭代,埋点代码误删,导致线上统计失精;
  • 重复埋点,不同产品来一波埋点需求,搞着搞着存在很多重复埋点;
  • 项目中的埋点字段意义不清晰,不知道打的点干嘛用的;
  • 耦合业务,雪上加霜的是项目存在几种埋点方法。
  • 多项目使用,每个项目引入写得不一样,不仅失去复用性,还增加代码阅读难度。

如:

// 埋点之一:apm
this.$apm.addCountReocord('record', 'action', {
  name: 'complete_task_from_card'
})
// 埋点之二:exposure接口上报
this.eventReport({
  action: 'join_position',
  channel: null,
  data: null,
  key: 'event_iphone',
  module: '',
  path: '',
  trigger: 'click'
})
// 埋点之二的同胞兄弟,浏览记录addRecord上报
function trackRecord(params) {
  //...其他params过滤操作,end
  await addRecord(params) // addRecord是一个接口上报
},

采取措施

鉴于埋点是不可缺少的一块监控内容,为了更好的规范管理埋点使用,需要对项目中的代码埋点做了以下优化:

  • 统一埋点方法:抽离埋点方法,npm包引入使用,可复用多项目;
  • 埋点语义化:埋点方法调用强制注明埋点的作用(或详细信息);
  • 生成路由与埋点映射:通过 webpack plugin生成页面路由与埋点的关系,可直观看到整个项目埋点分布(还可具体到代码哪行哪列)。

以上仍然采用的是命令式的埋点,有兴趣的伙伴还可以在项目中声明式的埋点使得埋点更加整洁

// 预期调用
this.$trackReport({
  key: 'view_iphone_detail',
  msg: 'apm_iphone页_点击查看detail'
})

统一埋点方法调用

这里没有那么多弯弯绕绕,就是把多种埋点方法根据类型type做一个策略,通过参数type以及params参数调用不同的埋点方法。
例如,通过npm包track暴露trackReport是策略方法,接收params打点参数,config埋点需要的配置如token uid
getDefaultTrackConfi其实就是获取默认的config,使用中可以根据不同的想去做覆盖,如覆盖exposure接口上报的接口url。为全局方便调用最后挂载到Vue prototype原型链中。

// 初始化埋点方法,并挂载到原型链中
import Vue from 'vue'
import { trackReport, getDefaultTrackConfig } from 'track'
/**
 * 描述: 埋点事件对应的传参,这里的对象的key就是调用时的key做匹配。
 * this.$trackReport({
 * 	key: 'view_iphone_detail',
 * 	msg: 'apm_iphone页_点击查看detail'
 * })
 */
const eventMapParams = {
  view_iphone: () => ({
    type: 'apm',
    params: {
      name: 'iphone',
      type: 'view',
      spans: { name: 'view_iphone_list' }
    }
  }),
  view_iphone_detail: (that) => ({
    // that为当前调用组件的上下文this,以获取动态的数据
    type: 'apm',
    params: {
      name: 'iphone_detail',
      type: 'view_detail',
      spans: { money: that.money }
    }
  })
}

export const initTrack = ({ token, userInfo }) => {
  const config = getDefaultTrackConfig({ token, userInfo })
  Vue.prototype.$trackReport = function(info) {
    // 根据传入的信息info匹配params
    const { key } = info || {}
    const params = eventMapParams[key](this)
    trackReport(params, config)
  }
}

这里特别说明的是eventMapParams获取当前埋点事件的params(暂时没想到其他办法去管理params......),毕竟埋点上报的参数各异,统一放在这里还是比较直观好管理的,总比散落在业务组件里好吧~~

npm 包开发,发布

整体项目如此:
image.png
初始化以及打包配置如下

安装TypeScript

强烈建议用TypeScript做强类型检查以及非常友好的提示。
比如,通过 /** */ 形式的注释可以给 TS 类型做标记提示,编辑器会有更好的提示
image.png
tsconfig.json文件中指定了用来编译这个项目的根文件和编译选项,更多搓ts官方文档

// yarn安装
yarn add tslib typescript -D
// 生成ts配置文件,tsconfig.json可用来配置编译成的js版本、tslint检查等
npx tsc --init

Rollup打包配置

众所周知,rollup有着更好的tree-shaking对应开发类库属实好用的,当然使用webpack也可以。具体看这篇好文

  • rollup 是在编译打包过程中分析程序流,得益于于 ES6 静态模块(exports 和 imports 不能在运行时修改),我们在打包时就可以确定哪些代码时我们需要的。
  • webpack 本身在打包时只能标记未使用的代码而不移除,而识别代码未使用标记并完成 tree-shaking 的 其实是 UglifyJS、babili、terser 这类压缩代码的工具。简单来说,就是压缩工具读取 webpack 打包结果,在压缩之前移除 bundle 中未使用的代码

安装:

yarn add rollup -D

新建rollup.config.js文件,配置如下:

// 识别并转译ts
import typescript from '@rollup/plugin-typescript'
// 解析引入npm包,以允许我们加载第三方模块
import resolve from 'rollup-plugin-node-resolve'
// ES6 +的JavaScript解析器,mangler和压缩器工具包
import { terser } from 'rollup-plugin-terser'

import pkg from './package.json'

// 末尾可加当前的版本记录
const footer = `
if(typeof window !== 'undefined') {
  window._TrackVersion = '${pkg.version}'
}`
export default {
  input: './src/index.ts',
  output: [
    {
      file: pkg.main,
      format: 'umd',
      footer,
      name: 'Track',
      sourcemap: true,
      plugins: [terser()]
    }
  ],
  context: 'window',
  plugins: [typescript(), resolve()]
}

调试以及发布

npm link
参阅 npm config

在包根目录内执行npm link / yarn link, 此时会在全局文件夹{prefix}/lib/node_modules/中 创建一个符号链接,该链接链接到 npm link 执行命令的包。

npm link packageName
查看项目的node_modules就可以看到link进来的包了。

packageName 是取自包的 package.json 中 name 字段 执行 npm link packageName 命令,将会创建一个从全局安装的 packageName 到当前文件内的 node_modules 下的符号链接

npm unlink 解除link

项目中执行 npm unlink --no-save package && npm install 包中执行 npm unlink

npm login && npm publish 发布

npm login 登录npm的账号和密码
npm publish 发布到npm

发布的话可以使用Github Actions自动化去发布,减少手动操作。可参考:
NPM_TOKEN是在github里面配置的,具体戳(感兴趣的小伙伴可以看我之前写的Github Actions文章)

name: dry
on:
  pull_request:
    branches:
      - main # 监听main分支,有pr进来触发workFlow,push的话就分支有更新触发
jobs:
  build:
    runs-on: ubuntu-18.04
    steps:
      - uses: actions/checkout@v2	   # checkout代码
      - uses: actions/setup-node@v1  # 集成node
      - uses: actions/cache@v2 			 # cache
        with:
          path: ~/.npm
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-
      - name: install and test
        run: |
          npm install
          npm run test
      - run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
      - name: publish
        if: ${{contains(github.ref, 'refs/tags/')}} # 检查git tag
        run: |
          npm run build
          npm publish --access public

webpack plugin生成路由-埋点映射

通过 webpack plugin生成页面路由与埋点的关系,可直观看到整个项目埋点分布(还可具体到代码哪行哪列),最终效果如下:
image.png
这里就不展开了,附上生图:

image.png
plugin跑在子进程中,生成的json可用于渲染的界面上,这样就可直观看到整个项目埋点分布啦,以后可以快乐地跟产品🤺击剑了。

总结

以上就是在整理公司埋点时的一些记录,能力有限,如果大伙有更好的实现办法去整理这种代码埋点的话,欢迎👏🏻留言。