如何用elastic APM实现用户行为轨迹监控(User Journey Monitoring)
对于各大APM厂商来说,通过用户轨迹监控(User Journey Monitoring)来获取用户访问网站或者APP时的轨迹已经成为了标配,通过该功能,我们可以了解用户的轨迹信息,比如:哪个页面最吸引用户访问,用户在哪个页面上停留的时间最长,哪个页面客户点击最少,停留的时间最少,一般是从哪个页面到哪个页面等等。
elastic APM 目前只提供了一个RUM功能(Real User Monitoring),并没有完整的推出User Journey Monitoring,那基于目前的工具,我们该如何实现User Journey Monitoring呢?
这篇文章,我们以Vue为例子,讲解一下该如何做到UJM功能。
思路
要实现用户的轨迹追踪,必须实现几个基本的要素:
- 知晓用户发生轨迹切换的事件 - 登录,跳转,离开等
- 当事件发生后,需要记录相关的指标 - 用户名,停留时间,跳转前的页面地址等
- 页面需能与后端服务器通信,以记录各种用户行为轨迹相关的数据
当然,还有其他要求,比如能够远程打开关闭监控,监控接口防火墙,不能影响用户真实体验等,我们在这里不做讨论。
因为我们讨论的是elastic APM,自然,它就承担了记录用户行为轨迹相关的数据的责任,通过javascript agent
,我们可以将用户的轨迹映射为APM中的transaction
,然后发送到APM server。
在实现剩余的两个要素之前,我们通常要先解决以下问题:
- 你的应用是一个单页应用还是一个多页应用?
- 你是否使用了web应用框架?是否了解web对应应用框架的组件生命周期?
单页应用VS多页应用
为什么我们要讨论单页应用和多页应用的问题,最主要的区别在于:
- 当你的应用是一个多页应用的时候,每个页面的跳转需要访问后端的浏览器获取html相关资源,即你的轨迹记录是可以从类似apache,nginx等日志中提取的,但日志数据是否能够完整包含轨迹数据,存疑;而且,对应的,因为页面之间缺乏直接的联系,页面跳转后,记录需要在页面间传递的轨迹相关数据会比较麻烦。
- 当你的应用是一个单页应用时,页面的“跳转”实际上是浏览器重新渲染页面的一个过程,即用户的访问轨迹并不会出现在日志当中,也不会记录在浏览器的访问历史当中,但相对的,因为是一个整体内部的切换,通过页面上的数据治理,很容易在页面的“跳转”之间的数据传递。
结论,无论是多页应用还是单页应用,通过技术都是能够做到用户轨迹数据的提起的,只是在不同的场景下可能会有数据准确性的问题。在这里,我们以单页应用为例子,因为手机web大多是采用单页应用的方式提高用户体验,而用户体验和用户行为轨迹监控是更为相关的行为。
前端框架的组件生命周期
对于一个web应用,如果我们需要做用户行为轨迹监控,那么基本也意味着,我们会要求该web应用有一个好的设计。如果一个web应用连基本的前端web框架都没有使用,那么就不在我们的讨论范围之内。。。现在流行的web框架,比如,vue, angularJS, react等,都提供了组件生命周期管理的勾子函数,以Vue这个在中国最流行的框架来举例,每个页面都是一个vue的组件(当然,组件里面还有很多子组件),组件包含以下生命周期:
类似的,AngularJs和React里面也生命周期勾子函数。
解决方案
从上图可以看到,created
,activated
,deactivated
,destroyed
等函数所代表的事件,是和我们需要记录的用户轨迹的事件,如:进入,离开等事件是吻合的。并且,作为一个勾子函数,我们可以在函数中访问框架提供的组件间通信数据以及公共数据,如此,我们可以在函数中得到如下信息:
- 登录的用户名,以及其他用户信息
- 进入组件的时间,离开组件的时间,组件停留时间
- 发生组件切换的前后关系
下面,是关键的代码实现。
package.json
在dependencies中增加:
"elastic-apm-js-base": "^3.0.0"
main.js
在VUE的初始化文件中,增加以下功能:
- 启动APM
- 新增transaction函数,打开和关闭APM的transaction,并在transaction中记录:用户名,组件名,进入时间,离开时间,停留时间
- 使用
mixin
,将transaction函数挂载到勾子上
大家需要注意的是,这是一个POC的试验方案,真正的生产实现,需要大家继续优化
import {init as initApm} from 'elastic-apm-js-base'
const apm = initApm({
// Set required service name (allowed characters: a-z, A-Z, 0-9, -, _, and space)
serviceName: 'qsa_smart_advisor',
// Set custom APM Server URL (default: http://localhost:8200)
serverUrl: 'http://localhost:8200',
// Set service version (required for sourcemap feature)
serviceVersion: '',
pageLoadTransactionName: 'home'
})
// User Journey Monitoring
function startUJMTransaction(theVue) {
var enableAPM = theVue.$options.enableAPM;
if (enableAPM) {
store.dispatch('getUserInfo').then(user => {
apm.setUserContext({
id: user.id,
username: user.name
})
});
if (theVue.$options.transaction) {
endUJMTransaction(theVue);
}
theVue.$options.transaction = apm.startTransaction(theVue.$options.name, 'custom');
let startTime = new Date().getTime();
theVue.$options.transaction_start = startTime;
apm.addTags({int_start: new Date().getTime()});
console.log("create " + theVue.$options.transaction.name + " with tag: " + {start: startTime})
}
}
function endUJMTransaction(theVue) {
var enableAPM = theVue.$options.enableAPM;
if (enableAPM && theVue.$options.transaction) {
let endTime = new Date().getTime();
apm.addTags({int_end: endTime});
apm.addTags({int_duration: endTime - theVue.$options.transaction_start});
theVue.$options.transaction.end();
theVue.$options.transaction = null;
theVue.$options.transaction_start = null;
}
}
Vue.mixin({
created: function () {
startUJMTransaction(this)
},
activated: function () {
let currentTrans = apm.getCurrentTransaction();
if (currentTrans) {
endUJMTransaction(this)
}
startUJMTransaction(this)
},
deactivated: function () {
endUJMTransaction(this)
},
destroyed: function () {
endUJMTransaction(this)
}
});
展示
因为通过elastic我们可以做到地图热点,加载性能监控(RUM),最终是能够实现如下效果的dashboard的: