elastic APM 目前只提供了一个RUM功能(Real User Monitoring),并没有完整的推出User Journey Monitoring,那基于目前的工具,我们该如何实现User Journey Monitoring呢?
这篇文章,我们以Vue为例子,讲解一下该如何做到UJM功能。
要实现用户的轨迹追踪,必须实现几个基本的要素:
当然,还有其他要求,比如能够远程打开关闭监控,监控接口防火墙,不能影响用户真实体验等,我们在这里不做讨论。
因为我们讨论的是elastic APM,自然,它就承担了记录用户行为轨迹相关的数据的责任,通过javascript agent
,我们可以将用户的轨迹映射为APM中的transaction
,然后发送到APM server。
在实现剩余的两个要素之前,我们通常要先解决以下问题:
为什么我们要讨论单页应用和多页应用的问题,最主要的区别在于:
对于一个web应用,如果我们需要做用户行为轨迹监控,那么基本也意味着,我们会要求该web应用有一个好的设计。如果一个web应用连基本的前端web框架都没有使用,那么就不在我们的讨论范围之内。。。现在流行的web框架,比如,vue, angularJS, react等,都提供了组件生命周期管理的勾子函数,以Vue这个在中国最流行的框架来举例,每个页面都是一个vue的组件(当然,组件里面还有很多子组件),组件包含以下生命周期:
类似的,AngularJs和React里面也生命周期勾子函数。
从上图可以看到,created
,activated
,deactivated
,destroyed
等函数所代表的事件,是和我们需要记录的用户轨迹的事件,如:进入,离开等事件是吻合的。并且,作为一个勾子函数,我们可以在函数中访问框架提供的组件间通信数据以及公共数据,如此,我们可以在函数中得到如下信息:
下面,是关键的代码实现。
在dependencies中增加:
"elastic-apm-js-base": "^3.0.0"
在VUE的初始化文件中,增加以下功能:
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)
}
});