Skip to content

Commit b2a3ef0

Browse files
lforstAbhiPrasad
authored andcommitted
feat(transport): Add client report hook to makeTransport (#5008)
1 parent d5ae88c commit b2a3ef0

32 files changed

+348
-272
lines changed

packages/browser/src/transports/fetch.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,5 @@ export function makeFetchTransport(
3030
}));
3131
}
3232

33-
return createTransport({ bufferSize: options.bufferSize }, makeRequest);
33+
return createTransport(options, makeRequest);
3434
}

packages/browser/src/transports/xhr.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,20 @@ export interface XHRTransportOptions extends BaseTransportOptions {
2121
*/
2222
export function makeXHRTransport(options: XHRTransportOptions): Transport {
2323
function makeRequest(request: TransportRequest): PromiseLike<TransportMakeRequestResponse> {
24-
return new SyncPromise<TransportMakeRequestResponse>((resolve, _reject) => {
24+
return new SyncPromise((resolve, reject) => {
2525
const xhr = new XMLHttpRequest();
2626

27+
xhr.onerror = reject;
28+
2729
xhr.onreadystatechange = (): void => {
2830
if (xhr.readyState === XHR_READYSTATE_DONE) {
29-
resolve({
31+
const response = {
3032
headers: {
3133
'x-sentry-rate-limits': xhr.getResponseHeader('X-Sentry-Rate-Limits'),
3234
'retry-after': xhr.getResponseHeader('Retry-After'),
3335
},
34-
});
36+
};
37+
resolve(response);
3538
}
3639
};
3740

@@ -47,5 +50,5 @@ export function makeXHRTransport(options: XHRTransportOptions): Transport {
4750
});
4851
}
4952

50-
return createTransport({ bufferSize: options.bufferSize }, makeRequest);
53+
return createTransport(options, makeRequest);
5154
}

packages/browser/test/unit/helper/browser-client-options.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { BrowserClientOptions } from '../../../src/client';
66
export function getDefaultBrowserClientOptions(options: Partial<BrowserClientOptions> = {}): BrowserClientOptions {
77
return {
88
integrations: [],
9-
transport: () => createTransport({}, _ => resolvedSyncPromise({})),
9+
transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => resolvedSyncPromise({})),
1010
stackParser: () => [],
1111
...options,
1212
};

packages/browser/test/unit/mocks/simpletransport.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ import { createTransport } from '@sentry/core';
22
import { resolvedSyncPromise } from '@sentry/utils';
33

44
export function makeSimpleTransport() {
5-
return createTransport({}, () => resolvedSyncPromise({}));
5+
return createTransport({ recordDroppedEvent: () => undefined }, () => resolvedSyncPromise({}));
66
}

packages/browser/test/unit/sdk.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const PUBLIC_DSN = 'https://username@domain/123';
1515
function getDefaultBrowserOptions(options: Partial<BrowserOptions> = {}): BrowserOptions {
1616
return {
1717
integrations: [],
18-
transport: () => createTransport({}, _ => resolvedSyncPromise({})),
18+
transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => resolvedSyncPromise({})),
1919
stackParser: () => [],
2020
...options,
2121
};

packages/browser/test/unit/transports/fetch.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { FetchImpl } from '../../../src/transports/utils';
66

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

1112
const ERROR_ENVELOPE = createEnvelope<EventEnvelope>({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [

packages/browser/test/unit/transports/xhr.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { makeXHRTransport, XHRTransportOptions } from '../../../src/transports/x
55

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

1011
const ERROR_ENVELOPE = createEnvelope<EventEnvelope>({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [

packages/core/src/baseclient.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,11 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
9797
if (options.dsn) {
9898
this._dsn = makeDsn(options.dsn);
9999
const url = getEnvelopeEndpointWithUrlEncodedAuth(this._dsn, options.tunnel);
100-
this._transport = options.transport({ ...options.transportOptions, url });
100+
this._transport = options.transport({
101+
recordDroppedEvent: () => undefined, // TODO(v7): Provide a proper function instead of noop
102+
...options.transportOptions,
103+
url,
104+
});
101105
} else {
102106
IS_DEBUG_BUILD && logger.warn('No DSN provided, client will not do anything.');
103107
}

packages/core/src/transports/base.ts

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
11
import {
2-
DataCategory,
32
Envelope,
3+
EnvelopeItem,
4+
EventDropReason,
45
InternalBaseTransportOptions,
56
Transport,
67
TransportRequestExecutor,
78
} from '@sentry/types';
89
import {
9-
getEnvelopeType,
10+
createEnvelope,
11+
envelopeItemTypeToDataCategory,
12+
forEachEnvelopeItem,
1013
isRateLimited,
14+
logger,
1115
makePromiseBuffer,
1216
PromiseBuffer,
1317
RateLimits,
1418
resolvedSyncPromise,
19+
SentryError,
1520
serializeEnvelope,
1621
updateRateLimits,
1722
} from '@sentry/utils';
1823

24+
import { IS_DEBUG_BUILD } from '../flags';
25+
1926
export const DEFAULT_TRANSPORT_BUFFER_SIZE = 30;
2027

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

3643
function send(envelope: Envelope): PromiseLike<void> {
37-
const envCategory = getEnvelopeType(envelope);
38-
const category = envCategory === 'event' ? 'error' : (envCategory as DataCategory);
44+
const filteredEnvelopeItems: EnvelopeItem[] = [];
45+
46+
// Drop rate limited items from envelope
47+
forEachEnvelopeItem(envelope, (item, type) => {
48+
const envelopeItemDataCategory = envelopeItemTypeToDataCategory(type);
49+
if (isRateLimited(rateLimits, envelopeItemDataCategory)) {
50+
options.recordDroppedEvent('ratelimit_backoff', envelopeItemDataCategory);
51+
} else {
52+
filteredEnvelopeItems.push(item);
53+
}
54+
});
3955

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

45-
const requestTask = (): PromiseLike<void> =>
46-
makeRequest({ body: serializeEnvelope(envelope) }).then(({ headers }): void => {
47-
if (headers) {
48-
rateLimits = updateRateLimits(rateLimits, headers);
49-
}
61+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
62+
const filteredEnvelope: Envelope = createEnvelope(envelope[0], filteredEnvelopeItems as any);
63+
64+
// Creates client report for each item in an envelope
65+
const recordEnvelopeLoss = (reason: EventDropReason): void => {
66+
forEachEnvelopeItem(filteredEnvelope, (_, type) => {
67+
options.recordDroppedEvent(reason, envelopeItemTypeToDataCategory(type));
5068
});
69+
};
70+
71+
const requestTask = (): PromiseLike<void> =>
72+
makeRequest({ body: serializeEnvelope(filteredEnvelope) }).then(
73+
({ headers }): void => {
74+
if (headers) {
75+
rateLimits = updateRateLimits(rateLimits, headers);
76+
}
77+
},
78+
error => {
79+
IS_DEBUG_BUILD && logger.error('Failed while recording event:', error);
80+
recordEnvelopeLoss('network_error');
81+
},
82+
);
5183

52-
return buffer.add(requestTask);
84+
return buffer.add(requestTask).then(
85+
result => result,
86+
error => {
87+
if (error instanceof SentryError) {
88+
IS_DEBUG_BUILD && logger.error('Skipped sending event due to full buffer');
89+
recordEnvelopeLoss('queue_overflow');
90+
return resolvedSyncPromise();
91+
} else {
92+
throw error;
93+
}
94+
},
95+
);
5396
}
5497

5598
return {

0 commit comments

Comments
 (0)