此页面需要javascript支持,请在浏览器中启用javascript

sentry前端监控for browser(1)

性能
Performance
js
browser
sentry
阅读 

基类包

@sentry/browser 是所有前端监控的基类了

假如所有的 init 是在 Gobal 层面初始化一个单例的,带有全局作用域的 hub 对象

What's a Scope, what's a Hub

那么从代码角度来讲,withScope 本身他就是一个马上会执行传入函数的函数,只不过带了点作用域罢了

看了一些官方文档之后,对源码的理解也更深了

init做了些啥

browsernode 在init时候,用的不同的实现,一个是 BrowserClient 一个是 NodeClient

core 里的 initAndBind 是一个完全抽象的func
接受一个 function / class 和 option

if (options.debug === true) {
    logger.enable();
  }
const hub = getCurrentHub();
const client = new clientClass(options);
hub.bindClient(client);

getCurrentHub:

export function getCurrentHub(): Hub {
  // Get main carrier (global for every environment)
  const registry = getMainCarrier();

  // If there's no hub, or its an old API, assign a new one
  if (!hasHubOnCarrier(registry) || getHubFromCarrier(registry).isOlderThan(API_VERSION)) {
    setHubOnCarrier(registry, new Hub());
  }

  // Prefer domains over global if they are there (applicable only to Node environment)
  if (isNodeEnv()) {
    return getHubFromActiveDomain(registry);
  }
  // Return hub that lives on a global object
  return getHubFromCarrier(registry);
}

getMainCarrier 实际上就是在全局挂载 __SENTRY__ 的方法

export function getMainCarrier(): Carrier {
  const carrier = getGlobalObject();
  carrier.__SENTRY__ = carrier.__SENTRY__ || { //好奇这种写法会触发装饰器的 set 吗?
    extensions: {},
    hub: undefined,
  };
  return carrier;
}

然后最后在给 hub 赋值为 new Hub()

export function getHubFromCarrier(carrier: Carrier): Hub {
  if (carrier && carrier.__SENTRY__ && carrier.__SENTRY__.hub) return carrier.__SENTRY__.hub;
  carrier.__SENTRY__ = carrier.__SENTRY__ || {};
  carrier.__SENTRY__.hub = new Hub();
  return carrier.__SENTRY__.hub;
}

其实这个逻辑很js,很多场景都遇到过

比如我们插 mongodb ,没有那个 collection,我们插会自己去 create 然后再插,本质上是一样的

Hub

Hun可以理解成一个 Stack,初始化带个空对象, [{}] 还有一个 _lastEventId

首先默认构建一个全局的作用域 new Scope() 赋给这个空对象
作用域里面是用于检测的一大坨玩意

hub.bindClient(client);就是给栈顶对象,绑定 client

这样空对象,又有 client 又有 scope 了

初始化完成

我们已经挂载了全局的对象,里面有 hub loggerglobalEventProcessors

我们手动记录错误的api做了啥呢?
看三个api

  • captureException
  • captureEvent
  • captureMessage

from @sentry/minimal 这个包可以理解成一个共享包, 其他包都用上了

从源码的角度看,里面就一个文件很少,而这里面几乎所有的方法都最终调用了 callOnHub

做了些啥? 针对 hub实例方法的一个封装罢了,实际上还是调用的hub上,对应key 的 function,让我们回到 hub

三者在做了一些参数转化之后。全部去 this._invokeClient ,这个方法就去取 栈顶对象的 scope, client 然后

  private _invokeClient<M extends keyof Client>(method: M, ...args: any[]): void {
    const { scope, client } = this.getStackTop();
    if (client && client[method]) {
      (client as any)[method](...args, scope);
    }
  }

所以,最终又回到了 Client 那里

BrowserClient

找不到这几个方法,向上找 BaseClientBrowserBackend

BaseClient

  public captureException(exception: any, hint?: EventHint, scope?: Scope): string | undefined {
    let eventId: string | undefined = hint && hint.event_id;

    this._process(
      this._getBackend()
        .eventFromException(exception, hint)
        .then(event => this._captureEvent(event, hint, scope))
        .then(result => {
          eventId = result;
        }),
    );

    return eventId;
  }

同理可得 _getBackend 拿到的是 BrowserBackend

this._process 是一个 微任务处理+计数,一看代码就知道

  protected _process<T>(promise: PromiseLike<T>): void {
    this._processing += 1;
    promise.then(
      value => {
        this._processing -= 1;
        return value;
      },
      reason => {
        this._processing -= 1;
        return reason;
      },
    );
  }

接下来就是 最重要的 _processEvent

就是它把事件和信息,送给了 Sentry

我们先只看 this._sendEvent
其他还有很多的概念,比如 session transaction 以后再讲

BaseBackend

  public sendEvent(event: Event): void {
    this._transport.sendEvent(event).then(null, reason => {
      logger.error(`Error while sending event: ${reason}`);
    });
  }

终于,快到那里了,先看看 _transport 构造器里初始化了一个 transports/noop

看来这个也是根据,不同平台实现的

transport

回到 @sentry/browser

  //backend.ts
  protected _setupTransport(): Transport {
    if (!this._options.dsn) {
      // We return the noop transport here in case there is no Dsn.
      return super._setupTransport();
    }

    const transportOptions = {
      ...this._options.transportOptions,
      dsn: this._options.dsn,
      _metadata: this._options._metadata,
    };

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

一下子就看懂了,又可以自定义,支持 fetch 就 fetch ,最差还有 xhr

XHRTransport

browser/transports

更加懂了

梳理一下很有收获,得画一个目录思维导图

© 2021 icebreaker 苏ICP备19002675号-2
version:1.2.2