From 69c6874d1aab9070b481e96ddd86f787e1335d1c Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 23 Mar 2022 09:59:53 -0400 Subject: [PATCH] feat(core): Add new transports to base backend (#4752) Adds new transports to base backend in core. For now, they are gated behind `options._experiments.newTransport = true`. The reason this is gated is because client reports does not work with the new transports. The next step is to add new transports for fetch, xhr (browser) as well as http, https (node). We can do this in any order! --- packages/core/src/basebackend.ts | 46 ++++++++++++++++--- packages/core/src/request.ts | 41 ++++++++++++++++- packages/core/src/transports/base.ts | 5 -- .../core/test/lib/transports/base.test.ts | 5 -- 4 files changed, 79 insertions(+), 18 deletions(-) diff --git a/packages/core/src/basebackend.ts b/packages/core/src/basebackend.ts index 4ba8cce63bd7..f770c5aa0d8c 100644 --- a/packages/core/src/basebackend.ts +++ b/packages/core/src/basebackend.ts @@ -1,6 +1,9 @@ import { Event, EventHint, Options, Session, Severity, Transport } from '@sentry/types'; import { isDebugBuild, logger, SentryError } from '@sentry/utils'; +import { initAPIDetails } from './api'; +import { createEventEnvelope, createSessionEnvelope } from './request'; +import { NewTransport } from './transports/base'; import { NoopTransport } from './transports/noop'; /** @@ -63,6 +66,9 @@ export abstract class BaseBackend implements Backend { /** Cached transport used internally. */ protected _transport: Transport; + /** New v7 Transport that is initialized alongside the old one */ + protected _newTransport?: NewTransport; + /** Creates a new backend instance. */ public constructor(options: O) { this._options = options; @@ -91,9 +97,23 @@ export abstract class BaseBackend implements Backend { * @inheritDoc */ public sendEvent(event: Event): void { - void this._transport.sendEvent(event).then(null, reason => { - isDebugBuild() && logger.error('Error while sending event:', reason); - }); + // TODO(v7): Remove the if-else + if ( + this._newTransport && + this._options.dsn && + this._options._experiments && + this._options._experiments.newTransport + ) { + const api = initAPIDetails(this._options.dsn, this._options._metadata, this._options.tunnel); + const env = createEventEnvelope(event, api); + void this._newTransport.send(env).then(null, reason => { + isDebugBuild() && logger.error('Error while sending event:', reason); + }); + } else { + void this._transport.sendEvent(event).then(null, reason => { + isDebugBuild() && logger.error('Error while sending event:', reason); + }); + } } /** @@ -105,9 +125,23 @@ export abstract class BaseBackend implements Backend { return; } - void this._transport.sendSession(session).then(null, reason => { - isDebugBuild() && logger.error('Error while sending session:', reason); - }); + // TODO(v7): Remove the if-else + if ( + this._newTransport && + this._options.dsn && + this._options._experiments && + this._options._experiments.newTransport + ) { + const api = initAPIDetails(this._options.dsn, this._options._metadata, this._options.tunnel); + const [env] = createSessionEnvelope(session, api); + void this._newTransport.send(env).then(null, reason => { + isDebugBuild() && logger.error('Error while sending session:', reason); + }); + } else { + void this._transport.sendSession(session).then(null, reason => { + isDebugBuild() && logger.error('Error while sending session:', reason); + }); + } } /** diff --git a/packages/core/src/request.ts b/packages/core/src/request.ts index 01cdd4a3035f..db53a0a367e6 100644 --- a/packages/core/src/request.ts +++ b/packages/core/src/request.ts @@ -39,8 +39,11 @@ function enhanceEventWithSdkInfo(event: Event, sdkInfo?: SdkInfo): Event { return event; } -/** Creates a SentryRequest from a Session. */ -export function sessionToSentryRequest(session: Session | SessionAggregates, api: APIDetails): SentryRequest { +/** Creates an envelope from a Session */ +export function createSessionEnvelope( + session: Session | SessionAggregates, + api: APIDetails, +): [SessionEnvelope, SentryRequestType] { const sdkInfo = getSdkMetadataForEnvelopeHeader(api); const envelopeHeaders = { sent_at: new Date().toISOString(), @@ -54,6 +57,13 @@ export function sessionToSentryRequest(session: Session | SessionAggregates, api // TODO (v7) Have to cast type because envelope items do not accept a `SentryRequestType` const envelopeItem = [{ type } as { type: 'session' | 'sessions' }, session] as SessionItem; const envelope = createEnvelope(envelopeHeaders, [envelopeItem]); + + return [envelope, type]; +} + +/** Creates a SentryRequest from a Session. */ +export function sessionToSentryRequest(session: Session | SessionAggregates, api: APIDetails): SentryRequest { + const [envelope, type] = createSessionEnvelope(session, api); return { body: serializeEnvelope(envelope), type, @@ -61,6 +71,33 @@ export function sessionToSentryRequest(session: Session | SessionAggregates, api }; } +/** + * Create an Envelope from an event. Note that this is duplicated from below, + * but on purpose as this will be refactored in v7. + */ +export function createEventEnvelope(event: Event, api: APIDetails): EventEnvelope { + const sdkInfo = getSdkMetadataForEnvelopeHeader(api); + const eventType = event.type || 'event'; + + const { transactionSampling } = event.sdkProcessingMetadata || {}; + const { method: samplingMethod, rate: sampleRate } = transactionSampling || {}; + + const envelopeHeaders = { + event_id: event.event_id as string, + sent_at: new Date().toISOString(), + ...(sdkInfo && { sdk: sdkInfo }), + ...(!!api.tunnel && { dsn: dsnToString(api.dsn) }), + }; + const eventItem: EventItem = [ + { + type: eventType, + sample_rates: [{ id: samplingMethod, rate: sampleRate }], + }, + event, + ]; + return createEnvelope(envelopeHeaders, [eventItem]); +} + /** Creates a SentryRequest from an event. */ export function eventToSentryRequest(event: Event, api: APIDetails): SentryRequest { const sdkInfo = getSdkMetadataForEnvelopeHeader(api); diff --git a/packages/core/src/transports/base.ts b/packages/core/src/transports/base.ts index d5e7218cd87d..6290fbfec36e 100644 --- a/packages/core/src/transports/base.ts +++ b/packages/core/src/transports/base.ts @@ -79,10 +79,6 @@ export interface NodeTransportOptions extends BaseTransportOptions { } export interface NewTransport { - // If `$` is set, we know that this is a new transport. - // TODO(v7): Remove this as we will no longer have split between - // old and new transports. - $: boolean; send(request: Envelope): PromiseLike; flush(timeout?: number): PromiseLike; } @@ -144,7 +140,6 @@ export function createTransport( } return { - $: true, send, flush, }; diff --git a/packages/core/test/lib/transports/base.test.ts b/packages/core/test/lib/transports/base.test.ts index cb32bb44bd73..2257a67165b1 100644 --- a/packages/core/test/lib/transports/base.test.ts +++ b/packages/core/test/lib/transports/base.test.ts @@ -20,11 +20,6 @@ const TRANSACTION_ENVELOPE = createEnvelope( ); describe('createTransport', () => { - it('has $ property', () => { - const transport = createTransport({}, _ => resolvedSyncPromise({ statusCode: 200 })); - expect(transport.$).toBeDefined(); - }); - it('flushes the buffer', async () => { const mockBuffer: PromiseBuffer = { $: [],