关于前端:搭建前端监控采集用户行为的-N-种姿势

7次阅读

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

大家好,我是杨胜利。

上一篇咱们具体介绍了前端如何采集异样数据。采集异样数据是为了随时监测线上我的项目的运行状况,发现问题及时修复。在很多场景下,除了异样监控有用,收集用户的行为数据同样有意义。

怎么定义行为数据?顾名思义,就是用户在应用产品过程中产生的行为轨迹。比方去过哪几个页面,点过哪几个按钮,甚至在某个页面停留了多长时间,某个按钮点击了多少次,如果有需要都能够记录下来。

然而记录行为数据是一个和业务严密关联的事件,不可能把每个用户每一步操作都极其具体的记录下来,这样会产生极其宏大的数据,很显然不事实。

正当的做法是,依据产品的理论状况评估,哪个模块哪个按钮须要重点记录,则能够采集的具体一些;哪些模块不须要重点关注,则简略记录一下根本信息。

依据这个逻辑,咱们能够把行为数据分为两类:

  1. 通用数据
  2. 特定数据

上面别离介绍这两类数据该如何收集。

通用数据

在一个产品中,用户最根本的行为就是切换页面。用户应用了哪些性能,也能从切换页面中体现进去。因而通用数据个别是在页面切换时产生,示意某个用户拜访了某个页面。

页面切换对应到前端就是路由切换,能够通过监听路由变动来拿到新页面的数据。Vue 在全局路由守卫中监听路由变动,任意路由切换都能执行这里的回调函数。

// Vue3 路由写法
const router = createRouter({...})
router.beforeEach(to => {
  // to 代表新页面的路由对象
  recordBehaviors(to)
})

React 在组件的 useEffect 中实现雷同的性能。不过要留神一点,监听所有路由变动,则须要所有路由都通过这个组件,监听才有成果。具体的办法是配置路由时加 * 配置:

import HomePage from '@/pages/Home'
<Route path="*" component={HomePage} />,

而后在这个组件的的 useEffect 中监听路由变动:

// HomePage.jsx
const {pathname} = useLocation();
useEffect(() => {
  // 路由切换这个函数触发
  recordBehaviors(pathname);
}, [pathname]);

下面代码中,在路由切换时都调用了 recordBehaviors() 办法并传入了参数。Vue 传的是一个路由对象,React 传的是路由地址,接下来就能够在这个函数内收集数据了。

明确了在哪里收集数据,咱们还要晓得收集哪些数据。收集行为数据最根本的字段如下:

  • app:利用的名称 / 标识
  • env:应用环境,个别是开发,测试,生产
  • version:利用的版本号
  • user_id:以后用户 ID
  • user_name:以后用户名
  • page_route:页面路由
  • page_title:页面名称
  • start_at:进入工夫
  • end_at:来到工夫

下面的字段中,利用标识、环境、版本号统称利用字段,用于标记数据的起源。其余字段次要分为 用户 页面 工夫 三类,通过这三类数据就能够简略的判断出一件事:谁到过哪个页面,并停留了多长时间。

利用字段的配置和获取形式咱们在上一节 搭建前端监控,如何采集异样数据?中讲过,就不做多余介绍了,获取字段的形式都是通用的。

上面介绍其余的几类数据如何获取。

获取用户信息

古代前端利用存储用户信息的形式根本都是一样的,localStorage 存一份,状态治理里存一份。因而获取用户信息从这两处的任意一处取得即可。这里简略介绍下如何从状态治理中获取。

最简略的办法,在函数 recordBehaviors() 所处的 js 文件中,间接导入用户状态:

// 从状态治理里中导出用户数据
import {UserStore} from '@/stores';
let {user_id, user_name} = UserStore;

这里的 @/stores 指向我我的项目中的文件 src/stores/index.ts,示意状态治理的入口文件,应用时替换成本人我的项目的理论地位。理论状况中还会有用户数据为空的问题,这里须要独自解决一下,不便咱们在后续的数据查看中能看出别离:

import {UserStore} from '@/stores';

// 收集行为函数
const recordBehaviors = ()=> {
  let report_date = {...}
  if(UserStore) {let { user_id, user_name} = UserStore
    report_date.user_id = user_id || 0
    report_date.user_name = user_name || '未命名'
  } else {
    report_date.user_id = user_id || -1
    report_date.user_name = user_name || '未获取'
  }
}

下面代码中,首先判断了状态治理中是否有用户数据,如果有则获取,没有则指定默认值。这里指定默认值的细节要留神,不是轻易指定的,比方 user_id 的默认值有如下意义:

  • user_id 为 0:示意有用户数据,但没有 user_id 字段或该字段为空
  • user_id 为 -1:示意没有用户数据,因此 user_id 字段获取不到

用户数据是常常容易出错的中央,因为波及到登录状态和权限等简单问题。指定了上述默认值后,就能够从收集到的行为数据中判断出某个页面用户状态是否失常。

获取页面信息

后面咱们在监听路由变动的中央调用了 recordBehaviors 函数并传入了参数,页面信息能够从参数中拿到,咱们先看在 Vue 中怎么获取:

// 路由配置
{
  path: '/test',
  meta: {title: '测试页面'},
  component: () => import('@/views/test/Index.vue')
}

// 获取配置
const recordBehaviors = (to)=> {
  let page_route = to.path
  let page_title = to.meta.title
}

Vue 中比较简单,能够间接从参数中拿到页面数据。相比之下,React 的参数只是一个路由地址,想拿到页面名称还须要做独自解决。

个别在设计权限时,咱们会在服务端会保护一套路由数据,蕴含路由地址和名称。路由数据在登录后获取,存在状态治理中,那么有了 pathname 就能够从路由数据中找到对应的路由名称。

// React 中
import {RouteStore} from '@/stores';

const recordBehaviors = (pathname) => {let { routers} = RouteStore; // 取出路由数据
  let route = routers.find((row) => (row.path = pathname));
  if (route) {
    let page_route = route.path;
    let page_title = route.title;
  }
};

这样,页面信息的 page_route、page_title 两个字段也拿到了。

设置工夫

行为数据中用两个字段 start_atend_at 别离示意用户进入页面和来到页面的工夫。这两个字段十分重要,咱们在后续应用数据的时候能够判断出很多信息,比方:

  • 某个用户在某个页面停留了多久?
  • 某个段时间内,某个用户停留在哪几个页面?
  • 某个时间段内,哪个页面的用户停留时间最长?
  • 某个页面,哪些用户的使用率最高?

还有很多信息,都能依据这两个工夫字段判断。开始工夫很好办,函数触发时间接获取以后工夫:

var start_at = new Date();

完结工夫这里须要思考的状况比拟多。首先要确定数据什么时候上报?用户进入页面后上报,还是来到页面时上报?

如果进入页面时上报,能够保障行为数据肯定会被记录,不会失落,但此时 end_at 字段必然为空。这样的话,就须要在来到页面时再调接口,将这条记录的 end_time 更新,这种形式的实现比拟麻烦一些:

// 进入页面时调用
const recordBehaviors = () => {let report_date = {...} // 此时 end_at 为空
  http.post('/behaviors/insert', report_date).then(res=> {
    let id = res.id // 数据 id
    localStorage.setItem('CURRENT_BEHAVIOR_ID', id)
  })
}

// 来到页面时调用:const updateBehaviors = ()=> {let id = localStorage.getItem('CURRENT_BEHAVIOR_ID')
  let end_at = new Date()
  http.post('/behaviors/update/'+id, end_at) // 依据 id 更新完结工夫
  localStorage.removeItem('CURRENT_BEHAVIOR_ID')
}

下面代码中,进入页面先上报数据,并保留下 id,来到页面再依据 id 更新这条数据的完结工夫。

如果在来到页面时上报,那么就要保障来到页背后上报接口曾经触发,否则会导致数据失落。在满足这个前提条件下,上报逻辑会变成这样:

// 进入页面时调用
const recordBehaviors = () => {let report_date = {...} // 此时 end_at 为空
  localStorage.setItem('CURRENT_BEHAVIOR', JSON.stringify(report_date));
}

// 来到页面时调用
const reportBehaviors = () => {let end_at = new Date()
  let report_str = localStorage.getItem('CURRENT_BEHAVIOR')
  if(report_str) {let report_date = JSON.parse(report_str)
    report_date.end_at = end_at
    http.post('/behaviors/insert', report_date)
  } else {console.log('无行为数据')
  }
}

比照一下这两种计划,第一种的弊病是接口须要调两次,这会使接口申请量倍增。第二种计划只调用一次,然而须要特地留神可靠性解决,总体来说第二种计划更好些。

特定数据

除了通用数据,大部分状况咱们还要在具体的页面中收集某些特定的行为。比方某个要害的按钮有没有点击,点了多少次;或者某个要害区域用户有没有看到,看到(曝光)了多少次等等。

收集数据还有一个更业余的叫法 ———— 埋点。直观了解是,哪里须要上报数据,就埋一个上报函数进去。

通用数据针对所有页面主动收集,特定数据就须要依据每个页面的理论需要手动增加。以一个按钮为例:

<button onClick={onClick}> 点击 </button>;
const onClick = (e) => {// console.log(e);
  repoerEvents(e);
};

下面代码中,咱们想记录这个按钮的点击状况,所以做了一个简略的埋点 ———— 在按钮点击事件中调用 repoerEvents() 办法,这个办法外部会收集数据并上报。

这是最原始的埋点形式,间接将上报办法放到事件函数中。repoerEvents() 办法接管一个事件对象参数,在参数中获取须要上报的事件数据。

特定数据与通用数据的许多字段是一样的,收集特定数据须要的根本字段如下:

  • app:利用的名称 / 标识
  • env:应用环境,个别是开发,测试,生产
  • version:利用的版本号
  • user_id:以后用户 ID
  • user_name:以后用户名
  • page_route:页面路由
  • page_title:页面名称
  • created_at:触发工夫
  • event_type:事件类型
  • action_tag:行为标识
  • action_label:行为形容

这些根本字段中,前 7 个字段与后面通用数据的获取齐全一样,这里就不赘述了。实际上特定数据须要获取的专有字段只有 3 个:

  • event_type:事件类型
  • action_tag:行为标识
  • action_label:行为形容

这三个字段也非常容易获取。event_type 示意事件触发的类型,比方点击、滚动、拖动等,能够在事件对象中拿到。action_tag 和 action_label 是必须指定的属性,示意本次埋点的标识和文字描述,用于在后续的数据处理时不便查阅和统计。

理解了采集特定数据是怎么回事,接下来咱们用代码实现。

手动埋点上报

假如要为登录按钮做埋点,依照下面的数据采集形式,咱们书写代码如下:

<button data-tag="user_login" data-label="用户登录" onClick={onClick}>
  登录
</button>;
const onClick = (e) => {// console.log(e);
  repoerEvents(e);
};

代码中,咱们通过元素的自定义属性传递了 tag 和 label 两个标识,用于在上报函数中获取。

上报函数 repoerEvents() 代码逻辑如下:

// 埋点上报函数
const repoerEvents = (e)=> {let report_date = {...}
  let {tag, label} = e.target.dataset
  if(!tag || !label) {return new Error('上报元素属性缺失')
  }
  report_date.event_type = e.type
  report_date.action_tag = tag
  report_date.action_label = label

  // 上报数据
  http.post('/events/insert', report_date)
}

这样就实现了一个根本的特定数据埋点上报性能。

全局主动上报

当初咱们回过头来梳理一下这个上报流程,尽管基本功能实现了,然而还有些不合理之处,比方:

  • 必须为元素指定事件处理函数
  • 必须为元素增加自定义属性
  • 在原有事件处理函数中手动增加埋点,侵入性高

首先咱们的埋点形式是基于事件的,也就是说,不论元素自身是否须要事件处理,咱们都要给他加上,并在函数外部调用 repoerEvents() 办法。如果一个我的项目须要埋点的中央十分多,这种形式的接入老本就会十分高。

参考之前做异样监控的逻辑,咱们换一个思路:是否全局监听事件主动上报呢?

思考一下,如果要做全局监听事件,那么只能监听须要埋点的元素的事件。那么如何判断哪些元素须要埋点呢?

下面咱们为埋点的元素指定了 data-tagdata-label 两个自定义属性,那是不是依据这两个自定义属性判断就能够?咱们来试验一下:

window.addEventListener('click', (event) => {let { tag, label, trigger} = event.target.dataset;
  if (tag && label && trigger == 'click') {
    // 阐明该元素须要埋点
    repoerEvents(event);
  }
});

下面代码还多判断了一个自定义属性 dataset.trigger,示意元素在哪种事件触发时须要上报。全局监听事件须要这个标识,这样可防止事件抵触。

增加全局监听后,收集某个元素的特定数据就简略了,办法如下:

<button data-tag="form_save" data-label="表单保留" data-trigger="click">
  保留
</button>

试验证实,上述全局解决的形式是可行的,这样的话就不须要在每一个元素上增加或批改事件处理函数了,只须要在元素中增加三个自定义属性 data-tagdata-labeldata-trigger 就能主动实现数据埋点上报。

组件上报

下面全局监听事件上报的形式曾经比手动埋点高效了许多,当初咱们再换一个场景。

个别状况下当埋点性能成熟之后,会封装成一个 SDK 供其余我的项目应用。如果咱们将采集数据依照 SDK 的思路实现,让开发者在全局监听事件,是不是一个好的形式呢?

显然是不太敌对的。如果是一个 SDK,那么最好的形式是将所有内容聚合成一个组件,在组件内实现上报的所有性能,而不是让使用者在我的项目中增加监听事件。

封装组件的话,那么组件的性能最好是将要增加埋点的元素包裹,这样自定义元素也就不须要指定了,而转为组件的属性,而后在组件内实现事件监听。

以 React 为例,咱们看一下如何将下面的采集性能封装为组件:

import {useEffect, useRef} from 'react';

const CusReport = (props) => {const dom = useRef(null);
  const handelEvent = () => {console.log(props); // {tag:xx, label:xx, trigger:xx}
    repoerEvents(props);
  };
  useEffect(() => {if (dom.current instanceof HTMLElement) {dom.current.addEventListener(props.trigger, handelEvent);
    }
  }, []);
  return (<span ref={dom} className="custom-report">
      {props.children}
    </span>
  );
};

export default CusReport;

组件应用形式如下:

<CusReport tag="test" label="功能测试" trigger="click">
  <button> 测试 </button>
</CusReport>

这样就比拟优雅了,不须要批改指标元素,只有把组件包裹在指标元素之外即可。

总结

本文介绍了搭建前端监控如何采集行为数据,将数据分为 通用数据 特定数据 两个大类别离解决。同时也介绍了多种上报数据的形式,不同的场景能够抉择不同的形式。

其中的数据局部只介绍了实现性能的根底字段,理论状况中能够依据本人的业务需要增加。

许多小伙伴留言这套前端监控是否开源,必定是要开源的,不过内容比拟多我还在做,等到根本欠缺了我会发一个版本,感激小伙伴们的关注。

本系列文章如下,欢送一键三连:

  • 为什么前端不能没有监控零碎?
  • 前端监控的搭建步骤,别再一头雾水了!
  • 搭建前端监控,如何采集异样数据?

更多干货请关注公众号 程序员胜利,增加作者微信进前端群。

正文完
 0