今日介绍前端异常监控利器 Sentry 平台中用户行为记录的源码实现过程,为什么使用 Sentry,可以看以前的文章https://xie.infoq.cn/article/40fd56e8c8fd296b9bd0d91aa。
前言
在日常排查问题过程中,用户的行为操作记录能给到我们很大的参考及排查方向。目前市面上有的平台还不能记录用户的行为记录,Sentry 提供了很好的行为记录查询,如下图所示。从用户跳转页面,到点击元素请求接口,发生异常,如图所示,极大可能是因为接口相应的数据没有达到预期导致页面发送错误,本文将探讨 Sentry JS SDK 中是如何记录用户的操作行为记录。
源码实现
SDK 初始化时会调用init
方法,最终init
方法的 initAndBind
指向 getCurrentHub().bindClient()
Hub.prototype.bindClient = function (client) {
// 获取最后一个
var top = this.getStackTop();
// 把 new BrowerClient() 实例 绑定到top上
top.client = client;
if (client && client.setupIntegrations) {
client.setupIntegrations();
}
};
复制代码
这里调用了默认的集成 client.setupIntegrations()
,最终指向 @sentry/core/integration.js
,这里的 options 即 sdk 初始化时传入并处理后的 options。
export function setupIntegrations(options) {
var integrations = {};
getIntegrationsToSetup(options).forEach(function (integration) {
integrations[integration.name] = integration;
setupIntegration(integration);
});
return integrations;
}
复制代码
setupIntegration
调用 setupOnce
初始化拦截,这里的 setupOnce
是每个集成功能都有的能力。
export function setupIntegration(integration) {
if (installedIntegrations.indexOf(integration.name) !== -1) {
return;
}
integration.setupOnce(addGlobalEventProcessor, getCurrentHub);
installedIntegrations.push(integration.name);
logger.log("Integration installed: " + integration.name);
}
复制代码
breadcrumbs 面包屑的 setupOnce 函数
@sentry/broswer/integrations/breadcrumbs.js
– setupOnce
函数,可以看到对 console,dom 事件,网络请求,页面变化都做了拦截处理。
Breadcrumbs.prototype.setupOnce = function () {
var _this = this;
if (this._options.console) {
addInstrumentationHandler({
callback: function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
_this._consoleBreadcrumb.apply(_this, __spread(args));
},
type: 'console',
});
}
if (this._options.dom) {
addInstrumentationHandler({
...,
type: 'dom',
});
}
if (this._options.xhr) {
addInstrumentationHandler({
..,
type: 'xhr',
});
}
if (this._options.fetch) {
addInstrumentationHandler({
...,
type: 'fetch',
});
}
if (this._options.history) {
addInstrumentationHandler({
...,
type: 'history',
});
}
};
复制代码
addInstrumentationHandler
出自 @sentry/utils/instrument.js
,最终调用了instrument
方法,此方法实现了每种拦截类型的具体捕获逻辑。
function instrument(type) {
if (instrumented[type]) {
return;
}
instrumented[type] = true;
switch (type) {
case 'console':
instrumentConsole();
break;
case 'dom':
instrumentDOM();
break;
case 'xhr':
instrumentXHR();
break;
case 'fetch':
instrumentFetch();
break;
case 'history':
instrumentHistory();
break;
case 'error':
instrumentError();
break;
case 'unhandledrejection':
instrumentUnhandledRejection();
break;
default:
logger.warn('unknown instrumentation type:', type);
}
}
复制代码
再看针对日志打印的具体拦截捕获回调函数,所有捕获的回调函数最终都会调用 getCurrentHub().addBreadcrumb
添加行为记录,只是不同类型的行为记录会传入不同的处理参数。
Breadcrumbs.prototype._consoleBreadcrumb = function (handlerData) {
var breadcrumb = {
category: 'console',
data: {
arguments: handlerData.args,
logger: 'console',
},
level: Severity.fromString(handlerData.level),
message: safeJoin(handlerData.args, ' '),
};
if (handlerData.level === 'assert') {
if (handlerData.args[0] === false) {
breadcrumb.message = "Assertion failed: " + (safeJoin(handlerData.args.slice(1), ' ') || 'console.assert');
breadcrumb.data.arguments = handlerData.args.slice(1);
}
else {
// Don't capture a breadcrumb for passed assertions
return;
}
}
getCurrentHub().addBreadcrumb(breadcrumb, {
input: handlerData.args,
level: handlerData.level,
});
}
复制代码
getCurrentHub().addBreadcrumb
最终调用于 @sentry/hub/scope.js
,在这里增加了时间的记录,以及最大记录条数的处理,最终将行为记录保存在 this._breadcrumbs(Scope)
数组中。
Scope.prototype.addBreadcrumb = function (breadcrumb, maxBreadcrumbs) {
var mergedBreadcrumb = __assign({ timestamp: dateTimestampInSeconds() }, breadcrumb);
this._breadcrumbs =
maxBreadcrumbs !== undefined && maxBreadcrumbs >= 0
? __spread(this._breadcrumbs, [mergedBreadcrumb]).slice(-maxBreadcrumbs)
: __spread(this._breadcrumbs, [mergedBreadcrumb]);
this._notifyScopeListeners();
return this;
};
复制代码
在控制台打印全局注入的__SENTRY__
对象可以在 hub
对象中的 _breadcrumbs
看到以下格式的数据。
最终在发起异常上报时,通过调用this.getStackTop()
获取到当前scope
中的数据一起上报,这里的 this.getStackTop().scope
即包括上述的 _breadcrumbs
行为数据,除此之前还有 _user
用户信息, _tags
标签数据, _extra
额外数据等。
Hub.prototype._invokeClient = function (method) {
var _a;
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
var top = this.getStackTop();
if (top && top.client && top.client[method]) {
(_a = top.client)[method].apply(_a, __spread(args, [top.scope]));
}
};
复制代码
最终异常上报的数据格式,如下图所示。
整体流程图
最后
整体源码分析就到这里了,Sentry 是一款功能强大的前端异常监控平台,涉及的功能及数据分析很多,有兴趣的同学可以尝试看看,看我觉得有用记得点个赞再走吧,收藏起来说不定哪天就需要了。
评论