question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Revamp the Transports API

See original GitHub issue

As part of https://github.com/getsentry/sentry-javascript/issues/4240#issuecomment-1035323682, we want to revamp the Transports API to make it easier to extend (adding support for attachments), consolidate the options, and reduce bundle size.

This issue forms a living document on how we are going to tackle this - first by setting up an API that can be (possibly) merged in before v7.

Much of this is inspired from the transport design of https://github.com/getsentry/sentry-javascript/blob/v7-dev/packages/transport-base/src/transport.ts

Options

To start, let’s look at the current state of the options:

https://github.com/getsentry/sentry-javascript/blob/b1009b5766b34b77781e28cebbf6cbde870e01a2/packages/types/src/transport.ts#L51-L74

Doing a quick runthrough, here’s what that looks like:

export interface BaseTransportOptions {
  /** Sentry DSN */
  dsn: DsnLike;
  /** Define custom headers */
  headers?: { [key: string]: string };
  /** Set a HTTP proxy that should be used for outbound requests. */
  httpProxy?: string; // ONLY USED BY NODE SDK
  /** Set a HTTPS proxy that should be used for outbound requests. */
  httpsProxy?: string; // ONLY USED BY NODE SDK
  /** HTTPS proxy certificates path */
  caCerts?: string; // ONLY USED BY NODE SDK
  /** Fetch API init parameters */
  fetchParameters?: { [key: string]: string }; // ONLY USED BY BROWSER SDK
  /** The envelope tunnel to use. */
  tunnel?: string;
  /** Send SDK Client Reports. Enabled by default. */
  sendClientReports?: boolean; // ONLY USED BY BROWSER SDK ATM
  /**
   * Set of metadata about the SDK that can be internally used to enhance envelopes and events,
   * and provide additional data about every request.
   * */
  _metadata?: SdkMetadata;
}

This means we can probably reduce it down to:

export interface BaseTransportOptions {
 // url to send the event
  // transport does not care about dsn specific - client should take care of
  // parsing and figuring that out
  url: string;
  headers?: Record<string, string>;
  bufferSize?: number; // make transport buffer size configurable
};

export interface BrowserTransportOptions extends BaseTransportOptions {
  // options to pass into fetch request
  fetchParams: Record<string, string>;
  sendClientReports?: boolean;
  // TODO: Add custom fetch implementation?
}

export interface NodeTransportOptions extends BaseTransportOptions {
  // Set a HTTP proxy that should be used for outbound requests.
  httpProxy?: string;
  // Set a HTTPS proxy that should be used for outbound requests.
  httpsProxy?: string;
  // HTTPS proxy certificates path
  caCerts?: string;
}

API

The transport does a couple of things in the SDK, but we can think about it mainly turning a SentryRequest into a Response of some kind. Due to rate-limiting, the transport is essentially a closed-loop control system, so the Response must be of some form to guide the transport (and the SDK in general) in making future decisions about how it should function.

SentryRequest<T> -> SentryResponse. SentryRequest becomes generic (defaulting to T = string), so transports can work with buffers/event-emitters/streams as the body if it so pleases. This also leaves us open to have non-http transports (although we’ll never probably have that as a default).

Assuming that in v7, the client is in charge of creating the envelope (because it has to be able to dynamically add items).

interface INewTransport {
  // TODO: How to best attach the type?
  send(request: Envelope, type: SentryRequestType): PromiseLike<TransportResponse>;
  flush(timeout: number): PromiseLike<boolean>;
}

We can actually make this entirely functional

function createTransport(options: TransportOptions): INewTransport {
  // some rate limiting logic
  // some transport buffer logic

  // this is a huge breaking change though
  // makes it way harder for users to implement their own transports
  // but is that a case we really care about?
  return { send, flush };
}

Sticking with classes though

/**
 * Heavily based on Kamil's work in
 * https://github.com/getsentry/sentry-javascript/blob/v7-dev/packages/transport-base/src/transport.ts
 */
export abstract class BaseTransport implements INewTransport {
  protected readonly _buffer: PromiseBuffer<TransportResponse>;
  private readonly _rateLimits: Record<string, number> = {};

  public constructor(protected readonly _options: BaseTransportOptions) {
    this._buffer = makePromiseBuffer(this._options.bufferSize || 30);
  }

  /**  */
  public send(envelope: Envelope, type: SentryRequestType): PromiseLike<TransportResponse> {
    const request: TransportRequest = {
      // I'm undecided if the type API should work like this
      // though we are a little stuck with this because of how
      // minimal the envelopes implementation is
      // perhaps there is a way we can expand it?
      type,
      body: serializeEnvelope(envelope),
    };

    if (isRateLimited(this._rateLimits, type)) {
      return rejectedSyncPromise(
        new SentryError(`oh no, disabled until: ${rateLimitDisableUntil(this._rateLimits, type)}`),
      );
    }

    const requestTask = (): PromiseLike<TransportResponse> =>
      this._makeRequest(request).then(({ body, headers, reason, statusCode }): PromiseLike<TransportResponse> => {
        if (headers) {
          updateRateLimits(this._rateLimits, headers);
        }

        // TODO: This is the happy path!
        const status = eventStatusFromHttpCode(statusCode);
        if (status === 'success') {
          return resolvedSyncPromise({ status });
        }

        return rejectedSyncPromise(new SentryError(body || reason || 'Unknown transport error'));
      });

    return this._buffer.add(requestTask);
  }

  /** */
  public flush(timeout?: number): PromiseLike<boolean> {
    return this._buffer.drain(timeout);
  }

  // Each is up to each transport implementation to determine how to make a request -> return an according response
  // `TransportMakeRequestResponse` is different than `TransportResponse` because the client doesn't care about
  // these extra details
  protected abstract _makeRequest(request: TransportRequest): PromiseLike<TransportMakeRequestResponse>;
}

Edit: Have to incorporate client outcomes here - will figure that out in a bit.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:14 (13 by maintainers)

github_iconTop GitHub Comments

2reactions
AbhiPrasadcommented, Mar 29, 2022

All new transports have been merged in - now just waiting on v7 for us to delete the old ones.

2reactions
yordiscommented, Mar 4, 2022

I’m not sure about that tbh. It’s quite hard to justify the existence of this functionality inside the transport. I never liked it

So happy to see my thoughts are aligned with yours @kamilogorek your response was actually the conclusion I was getting at the more I read code and understood things.

Read more comments on GitHub >

github_iconTop Results From Across the Web

BSD Socket API Revamp - GitHub
Compared to classic BSD socket API the new API is much more lightweight and ... 1] Internet-Draft BSD Socket API Revamp February 2017...
Read more >
Revamp of the API credentials screens - Adyen
The Developer Experience team has re-designed the API Credentials screens to improve user experiences and introducing new features.
Read more >
BSD socket API revamp | Hacker News
It seems to propose to ditch all non-blocking APIs under the assumption that "runtimes" will provide lightweight concurrency. But AFAIK most ( ...
Read more >
6.1. Transport API — Fast DDS 2.2.0 documentation
6.1. Transport API¶. The following diagram presents the classes defined on the transport API of eProsima Fast DDS. It shows the abstract API...
Read more >
Piramal Pharma Solutions Expands API Capabilities at Digwal
Piramal Pharma Solutions Expands API Capabilities at Digwal; ... a new canteen facility, Quality Control lab revamp, and more.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found