Skip to content

feat(transport): Add client report hook to makeTransport #5008

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/browser/src/transports/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@ export function makeFetchTransport(
}));
}

return createTransport({ bufferSize: options.bufferSize }, makeRequest);
return createTransport(options, makeRequest);
}
11 changes: 7 additions & 4 deletions packages/browser/src/transports/xhr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,20 @@ export interface XHRTransportOptions extends BaseTransportOptions {
*/
export function makeXHRTransport(options: XHRTransportOptions): Transport {
function makeRequest(request: TransportRequest): PromiseLike<TransportMakeRequestResponse> {
return new SyncPromise<TransportMakeRequestResponse>((resolve, _reject) => {
return new SyncPromise((resolve, reject) => {
const xhr = new XMLHttpRequest();

xhr.onerror = reject;

xhr.onreadystatechange = (): void => {
if (xhr.readyState === XHR_READYSTATE_DONE) {
resolve({
const response = {
headers: {
'x-sentry-rate-limits': xhr.getResponseHeader('X-Sentry-Rate-Limits'),
'retry-after': xhr.getResponseHeader('Retry-After'),
},
});
};
resolve(response);
}
};

Expand All @@ -47,5 +50,5 @@ export function makeXHRTransport(options: XHRTransportOptions): Transport {
});
}

return createTransport({ bufferSize: options.bufferSize }, makeRequest);
return createTransport(options, makeRequest);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { BrowserClientOptions } from '../../../src/client';
export function getDefaultBrowserClientOptions(options: Partial<BrowserClientOptions> = {}): BrowserClientOptions {
return {
integrations: [],
transport: () => createTransport({}, _ => resolvedSyncPromise({})),
transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => resolvedSyncPromise({})),
stackParser: () => [],
...options,
};
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/test/unit/mocks/simpletransport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import { createTransport } from '@sentry/core';
import { resolvedSyncPromise } from '@sentry/utils';

export function makeSimpleTransport() {
return createTransport({}, () => resolvedSyncPromise({}));
return createTransport({ recordDroppedEvent: () => undefined }, () => resolvedSyncPromise({}));
}
2 changes: 1 addition & 1 deletion packages/browser/test/unit/sdk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const PUBLIC_DSN = 'https://username@domain/123';
function getDefaultBrowserOptions(options: Partial<BrowserOptions> = {}): BrowserOptions {
return {
integrations: [],
transport: () => createTransport({}, _ => resolvedSyncPromise({})),
transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => resolvedSyncPromise({})),
stackParser: () => [],
...options,
};
Expand Down
1 change: 1 addition & 0 deletions packages/browser/test/unit/transports/fetch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { FetchImpl } from '../../../src/transports/utils';

const DEFAULT_FETCH_TRANSPORT_OPTIONS: FetchTransportOptions = {
url: 'https://sentry.io/api/42/store/?sentry_key=123&sentry_version=7',
recordDroppedEvent: () => undefined,
};

const ERROR_ENVELOPE = createEnvelope<EventEnvelope>({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [
Expand Down
1 change: 1 addition & 0 deletions packages/browser/test/unit/transports/xhr.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { makeXHRTransport, XHRTransportOptions } from '../../../src/transports/x

const DEFAULT_XHR_TRANSPORT_OPTIONS: XHRTransportOptions = {
url: 'https://sentry.io/api/42/store/?sentry_key=123&sentry_version=7',
recordDroppedEvent: () => undefined,
};

const ERROR_ENVELOPE = createEnvelope<EventEnvelope>({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [
Expand Down
6 changes: 5 additions & 1 deletion packages/core/src/baseclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,11 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
if (options.dsn) {
this._dsn = makeDsn(options.dsn);
const url = getEnvelopeEndpointWithUrlEncodedAuth(this._dsn, options.tunnel);
this._transport = options.transport({ ...options.transportOptions, url });
this._transport = options.transport({
recordDroppedEvent: () => undefined, // TODO(v7): Provide a proper function instead of noop
...options.transportOptions,
url,
});
} else {
IS_DEBUG_BUILD && logger.warn('No DSN provided, client will not do anything.');
}
Expand Down
67 changes: 55 additions & 12 deletions packages/core/src/transports/base.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import {
DataCategory,
Envelope,
EnvelopeItem,
EventDropReason,
InternalBaseTransportOptions,
Transport,
TransportRequestExecutor,
} from '@sentry/types';
import {
getEnvelopeType,
createEnvelope,
envelopeItemTypeToDataCategory,
forEachEnvelopeItem,
isRateLimited,
logger,
makePromiseBuffer,
PromiseBuffer,
RateLimits,
resolvedSyncPromise,
SentryError,
serializeEnvelope,
updateRateLimits,
} from '@sentry/utils';

import { IS_DEBUG_BUILD } from '../flags';

export const DEFAULT_TRANSPORT_BUFFER_SIZE = 30;

/**
Expand All @@ -34,22 +41,58 @@ export function createTransport(
const flush = (timeout?: number): PromiseLike<boolean> => buffer.drain(timeout);

function send(envelope: Envelope): PromiseLike<void> {
const envCategory = getEnvelopeType(envelope);
const category = envCategory === 'event' ? 'error' : (envCategory as DataCategory);
const filteredEnvelopeItems: EnvelopeItem[] = [];

// Drop rate limited items from envelope
forEachEnvelopeItem(envelope, (item, type) => {
const envelopeItemDataCategory = envelopeItemTypeToDataCategory(type);
if (isRateLimited(rateLimits, envelopeItemDataCategory)) {
options.recordDroppedEvent('ratelimit_backoff', envelopeItemDataCategory);
} else {
filteredEnvelopeItems.push(item);
}
});

// Don't add to buffer if transport is already rate-limited
if (isRateLimited(rateLimits, category)) {
// Skip sending if envelope is empty after filtering out rate limited events
if (filteredEnvelopeItems.length === 0) {
return resolvedSyncPromise();
}

const requestTask = (): PromiseLike<void> =>
makeRequest({ body: serializeEnvelope(envelope) }).then(({ headers }): void => {
if (headers) {
rateLimits = updateRateLimits(rateLimits, headers);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const filteredEnvelope: Envelope = createEnvelope(envelope[0], filteredEnvelopeItems as any);

// Creates client report for each item in an envelope
const recordEnvelopeLoss = (reason: EventDropReason): void => {
forEachEnvelopeItem(filteredEnvelope, (_, type) => {
options.recordDroppedEvent(reason, envelopeItemTypeToDataCategory(type));
});
};

const requestTask = (): PromiseLike<void> =>
makeRequest({ body: serializeEnvelope(filteredEnvelope) }).then(
({ headers }): void => {
if (headers) {
rateLimits = updateRateLimits(rateLimits, headers);
}
},
error => {
IS_DEBUG_BUILD && logger.error('Failed while recording event:', error);
recordEnvelopeLoss('network_error');
},
);

return buffer.add(requestTask);
return buffer.add(requestTask).then(
result => result,
error => {
if (error instanceof SentryError) {
IS_DEBUG_BUILD && logger.error('Skipped sending event due to full buffer');
recordEnvelopeLoss('queue_overflow');
return resolvedSyncPromise();
} else {
throw error;
}
},
);
}

return {
Expand Down
Loading