源码分析Sentry用户行为记录实现过程

Posted 南城FE

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了源码分析Sentry用户行为记录实现过程相关的知识,希望对你有一定的参考价值。

今日介绍前端异常监控利器Sentry平台中用户行为记录的源码实现过程,为什么使用Sentry,可以看以前的文章【前端异常监控平台对比】。

前言

在日常排查问题过程中,用户的行为操作记录能给到我们很大的参考及排查方向。目前市面上有的平台还不能记录用户的行为记录,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.jssetupOnce 函数,可以看到对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 
            // Dont 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是一款功能强大的前端异常监控平台,涉及的功能及数据分析很多,有兴趣的同学可以尝试看看,看我觉得有用记得点个赞再走吧,收藏起来说不定哪天就需要了。

以上是关于源码分析Sentry用户行为记录实现过程的主要内容,如果未能解决你的问题,请参考以下文章

常见用户行为分析模型:漏斗分析模型

常见用户行为分析模型:漏斗分析模型

kubernetes 源码分析之节点异常时 pod 驱逐过程

运维开发实践——基于Sentry搭建错误日志监控系统

thinkphp的记录用户行为的日志怎么实现?

Docker手动搭建sentry错误日志系统