最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Sentry的异常数据上报机制

    正文概述 掘金(The_fury)   2021-04-04   855

    Sentry的异常数据上报机制

    之前我们聊过了Sentry的异常监控方案中具体有那几种异常,以及大概的处理方式。这次我们来了解一下,这些异常数据的上报机制是怎么样的。

    上报方式

    就目前了解到的,主流的数据上报方式 而言,Sentry还是采用的ajax上报的方式。为了有更好的兼容性,在初始化的时候会去判断浏览器是否支持fetch,支持就使用fetch否则是xhr。同时也支持自定义的上报方式,且优先级会高于fetch和xhr

    class BaseBackend {
      if (this._options.transport) {
          return new this._options.transport(transportOptions);
      }
    	if (supportsFetch()) { return new FetchTransport(transportOptions); };
     	return new XHRTransport(transportOptions);
    }
    

    上报流程

    以unhandledrejection为例,首先是 全局监听 触发对应的triggerHandlers

    function instrumentUnhandledRejection(): void {
      _oldOnUnhandledRejectionHandler = global.onunhandledrejection;
    
      global.onunhandledrejection = function(e: any): boolean {
        triggerHandlers('unhandledrejection', e);
    
        if (_oldOnUnhandledRejectionHandler) {
          // eslint-disable-next-line prefer-rest-params
          return _oldOnUnhandledRejectionHandler.apply(this, arguments);
        }
        return true;
      };
    }
    

    对应的handler触发instrument.ts中的 captureEvent

     addInstrumentationHandler({
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          callback: (e: any) => {
            currentHub.captureEvent(event, {
              originalException: error,
            });
            return;
          },
          type: 'unhandledrejection',
        });
    

    触发baseclient.ts 中的_captureEvent

      protected _captureEvent(event: Event, hint?: EventHint, scope?: Scope): PromiseLike<string | undefined> {
        return this._processEvent(event, hint, scope).then(
          finalEvent => {
            return finalEvent.event_id;
          },
          reason => {
            logger.error(reason);
            return undefined;
          },
        );
      }
    

    最后走到核心主流程的函数方法上_processEvent

    核心方法_processEvent

    baseclient.ts _processEvent 参数event代表sentry要发送的事件本身的信息(event_id,timestamp,release 等等),hint代表其他的一些和原始异常相关的信息(captureContext,data,originalException等等),scope代表元数据的作用域

    	// 代码有部分删减
    protected _processEvent(event: Event, hint?: EventHint, scope?: Scope): PromiseLike<Event> {
        const { beforeSend, sampleRate } = this.getOptions();
        if (!this._isEnabled()) {
          return SyncPromise.reject(new SentryError('SDK not enabled, will not send event.'));
        }
        const isTransaction = event.type === 'transaction';
        if (!isTransaction && typeof sampleRate === 'number' && Math.random() > sampleRate) {
          return SyncPromise.reject(
            new SentryError(
              `Discarding event because it's not included in the random sample (sampling rate = ${sampleRate})`,
            ),
          );
        }
    
        return this._prepareEvent(event, scope, hint)
          .then(prepared => {
            const beforeSendResult = beforeSend(prepared, hint);
    		  if (isThenable(beforeSendResult)) {
              return (beforeSendResult as PromiseLike<Event | null>).then(
                event => event,
                e => {
                  throw new SentryError(`beforeSend rejected with ${e}`);
                },
              );
            }
            return beforeSendResult;
          })
          .then(processedEvent => {
            const session = scope && scope.getSession && scope.getSession();
            if (!isTransaction && session) {
              this._updateSessionFromEvent(session, processedEvent);
            }
    
            this._sendEvent(processedEvent);
            return processedEvent;
          })
          .then(null, reason => {
            if (reason instanceof SentryError) {
              throw reason;
            }
    
            this.captureException(reason, {
              data: {
                __sentry__: true,
              },
              originalException: reason as Error,
            });
            throw new SentryError(
              `Event processing pipeline threw an error, original event will not be sent. Details have been sent as a new event.\nReason: ${reason}`,
            );
          });
      }
    
    

    这一块的流程比较多,虽然已做删减,还是需要分成几个模块来讲解分析

    前置条件

        if (!this._isEnabled()) {
          return SyncPromise.reject(new SentryError('SDK not enabled, will not send event.'));
        }
        const isTransaction = event.type === 'transaction';
        if (!isTransaction && typeof sampleRate === 'number' && Math.random() > sampleRate) {
          return SyncPromise.reject(
            new SentryError(
              `Discarding event because it's not included in the random sample (sampling rate = ${sampleRate})`,
            ),
          );
        }
    

    前面基本是对是否满足上报的条件进行校验,初始化的时候是否设置了enabled = false(默认为true),为false即Sentry不可使用,不会上报数据。设置的sampleRate采样率。比如设置了sampleRate = 0.1即会有10%的数据会被发送,适用于日活非常大的情形。

    添加通用配置信息

    this._prepareEvent(event, scope, hint) 主要是添加每个事件都需要的通用信息 如environment,message,dist,release, breadcrumbs等等

    数据上报前的处理函数

    beforeSend其实就是Sentry.init传入的函数,入参即为event,hint,最后返回event。便于使用方对event数据做处理过滤,等等

    数据上报

            const session = scope && scope.getSession && scope.getSession();
            if (!isTransaction && session) {
              this._updateSessionFromEvent(session, processedEvent);
            }
    
            this._sendEvent(processedEvent);
            return processedEvent;
    
    
    

    判断是否有session,有则更新 _sendEvent则指向对应的transport(因为浏览器兼容fetch,则本次实际上报方式是使用fetch)

      public sendEvent(event: Event): PromiseLike<Response> {
        return this._sendRequest(eventToSentryRequest(event, this._api), event);
      }
    

    这里我们看到,在上报前还会执行eventToSentryRequest,这个方法主要是在序列化参数

    export function eventToSentryRequest(event: Event, api: API): SentryRequest {
    
      const req: SentryRequest = {
        body: JSON.stringify(sdkInfo ? enhanceEventWithSdkInfo(event, api.metadata.sdk) : event),
        type: eventType,
        url: useEnvelope ? api.getEnvelopeEndpointWithUrlEncodedAuth() : api.getStoreEndpointWithUrlEncodedAuth(),
      };
     
      return req;
    }
    
    

    Fetch中最后实现上报的地方为fetch.ts _sendRequest

      private _sendRequest(sentryRequest: SentryRequest, originalPayload: Event | Session): PromiseLike<Response> {
        if (this._isRateLimited(sentryRequest.type)) {
          return Promise.reject({
            event: originalPayload,
            type: sentryRequest.type,
            reason: `Transport locked till ${this._disabledUntil(sentryRequest.type)} due to too many requests.`,
            status: 429,
          });
        }
    
        const options: RequestInit = {
          body: sentryRequest.body,
          method: 'POST',
          referrerPolicy: (supportsReferrerPolicy() ? 'origin' : '') as ReferrerPolicy,
        };
    
        return this._buffer.add(
          new SyncPromise<Response>((resolve, reject) => {
            this._fetch(sentryRequest.url, options)
              .then(response => {
                const headers = {
                  'x-sentry-rate-limits': response.headers.get('X-Sentry-Rate-Limits'),
                  'retry-after': response.headers.get('Retry-After'),
                };
                this._handleResponse({
                  requestType: sentryRequest.type,
                  response,
                  headers,
                  resolve,
                  reject,
                });
              })
              .catch(reject);
          }),
        );
      }
    
    

    我们可以看到sentry中通过_isRateLimited方法来防止一瞬间太多相同的错误发生。 最终上报的数据格式为

    {
        "exception":{
            "values":[
                {
                    "type":"UnhandledRejection",
                    "value":"Non-Error promise rejection captured with value: 321",
                    "mechanism":{
                        "handled":false,
                        "type":"onunhandledrejection"
                    }
                }
            ]
        },
        "level":"error",
        "platform":"javascript",
        "event_id":"a94cd62ee6064321a340ce396da78de0",
        "timestamp":1617443534.168,
        "environment":"staging",
        "release":"1537345109360",
        "request":{
            "url":"http://127.0.0.1:5500/packages/browser/examples/index.html",
            "headers":{
                "Referer":"http://127.0.0.1:5500/packages/browser/examples/index.html",
                "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36"
            }
        },
        "sdk":{
            "name":"sentry.javascript.browser",
            "version":"6.2.5",
            "integrations":[
    
            ],
            "packages":[
                {
                    "name":"npm:@sentry/browser",
                    "version":"6.2.5"
                }
            ]
        }
    }
    

    总结

    其实这篇文档在写到一半的时候,我突然意识到一个略显尴尬的问题,我好像没有具体写错误数据是如何处理的,就直接写了上报的流程。但是毕竟写都写了,前期还是花了比较多的精力,重新开始就有点浪费时间了。于是我决定在后面的一篇中补充上,Sentry对于异常数据的处理。ps: 因为自己之前做过一次监控SDK,在对Sentry了解的越多后,感觉到了自己之前的很多不足,同时也印证了自己之前的一些想法,这个系列不出意外应该还会持续下去。

    参考资料

    GitHub - getsentry/sentry-javascript: Official Sentry SDKs for Javascript

    解析Sentry源码(三)| 数据上报


    起源地下载网 » Sentry的异常数据上报机制

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元