今日介绍前端异常监控利器 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 是一款功能强大的前端异常监控平台,涉及的功能及数据分析很多,有兴趣的同学可以尝试看看,看我觉得有用记得点个赞再走吧,收藏起来说不定哪天就需要了。
评论