Skip to content

feat(transport): Return result through Transport send #6626

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 6 commits into from
Jan 5, 2023
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
12 changes: 8 additions & 4 deletions packages/core/src/transports/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
EventItem,
InternalBaseTransportOptions,
Transport,
TransportMakeRequestResponse,
TransportRequestExecutor,
} from '@sentry/types';
import {
Expand Down Expand Up @@ -35,13 +36,15 @@ export const DEFAULT_TRANSPORT_BUFFER_SIZE = 30;
export function createTransport(
options: InternalBaseTransportOptions,
makeRequest: TransportRequestExecutor,
buffer: PromiseBuffer<void> = makePromiseBuffer(options.bufferSize || DEFAULT_TRANSPORT_BUFFER_SIZE),
buffer: PromiseBuffer<void | TransportMakeRequestResponse> = makePromiseBuffer(
options.bufferSize || DEFAULT_TRANSPORT_BUFFER_SIZE,
),
): Transport {
let rateLimits: RateLimits = {};

const flush = (timeout?: number): PromiseLike<boolean> => buffer.drain(timeout);

function send(envelope: Envelope): PromiseLike<void> {
function send(envelope: Envelope): PromiseLike<void | TransportMakeRequestResponse> {
const filteredEnvelopeItems: EnvelopeItem[] = [];

// Drop rate limited items from envelope
Expand Down Expand Up @@ -71,7 +74,7 @@ export function createTransport(
});
};

const requestTask = (): PromiseLike<void> =>
const requestTask = (): PromiseLike<void | TransportMakeRequestResponse> =>
makeRequest({ body: serializeEnvelope(filteredEnvelope, options.textEncoder) }).then(
response => {
// We don't want to throw on NOK responses, but we want to at least log them
Expand All @@ -80,10 +83,11 @@ export function createTransport(
}

rateLimits = updateRateLimits(rateLimits, response);
return response;
},
error => {
__DEBUG_BUILD__ && logger.error('Failed while sending event:', error);
recordEnvelopeLoss('network_error');
throw error;
},
);

Expand Down
40 changes: 17 additions & 23 deletions packages/node/test/transports/http.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { createGunzip } from 'zlib';

import { makeNodeTransport } from '../../src/transports';

const textEncoder = new TextEncoder();

jest.mock('@sentry/core', () => {
const actualCore = jest.requireActual('@sentry/core');
return {
Expand Down Expand Up @@ -70,22 +72,19 @@ const EVENT_ENVELOPE = createEnvelope<EventEnvelope>({ event_id: 'aa3ff046696b4b
[{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }] as EventItem,
]);

const SERIALIZED_EVENT_ENVELOPE = serializeEnvelope(EVENT_ENVELOPE, new TextEncoder());
const SERIALIZED_EVENT_ENVELOPE = serializeEnvelope(EVENT_ENVELOPE, textEncoder);

const ATTACHMENT_ITEM = createAttachmentEnvelopeItem(
{ filename: 'empty-file.bin', data: new Uint8Array(50_000) },
new TextEncoder(),
textEncoder,
);
const EVENT_ATTACHMENT_ENVELOPE = addItemToEnvelope(EVENT_ENVELOPE, ATTACHMENT_ITEM);
const SERIALIZED_EVENT_ATTACHMENT_ENVELOPE = serializeEnvelope(
EVENT_ATTACHMENT_ENVELOPE,
new TextEncoder(),
) as Uint8Array;
const SERIALIZED_EVENT_ATTACHMENT_ENVELOPE = serializeEnvelope(EVENT_ATTACHMENT_ENVELOPE, textEncoder) as Uint8Array;

const defaultOptions = {
url: TEST_SERVER_URL,
recordDroppedEvent: () => undefined,
textEncoder: new TextEncoder(),
textEncoder,
};

describe('makeNewHttpTransport()', () => {
Expand Down Expand Up @@ -151,7 +150,9 @@ describe('makeNewHttpTransport()', () => {

const transport = makeNodeTransport(defaultOptions);

await expect(transport.send(EVENT_ENVELOPE)).resolves.toBeUndefined();
await expect(transport.send(EVENT_ENVELOPE)).resolves.toEqual(
expect.objectContaining({ statusCode: serverStatusCode }),
);
},
);

Expand All @@ -165,20 +166,13 @@ describe('makeNewHttpTransport()', () => {
});

const transport = makeNodeTransport(defaultOptions);
await expect(transport.send(EVENT_ENVELOPE)).resolves.toBeUndefined();
});

it('should resolve when server responds with rate limit header and status code 200', async () => {
await setupTestServer({
await expect(transport.send(EVENT_ENVELOPE)).resolves.toEqual({
statusCode: SUCCESS,
responseHeaders: {
'Retry-After': '2700',
'X-Sentry-Rate-Limits': '60::organization, 2700::organization',
headers: {
'retry-after': '2700',
'x-sentry-rate-limits': '60::organization, 2700::organization',
},
});

const transport = makeNodeTransport(defaultOptions);
await transport.send(EVENT_ENVELOPE);
});
});

Expand Down Expand Up @@ -308,7 +302,7 @@ describe('makeNewHttpTransport()', () => {
const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1];

const executorResult = registeredRequestExecutor({
body: serializeEnvelope(EVENT_ENVELOPE, new TextEncoder()),
body: serializeEnvelope(EVENT_ENVELOPE, textEncoder),
category: 'error',
});

Expand All @@ -328,7 +322,7 @@ describe('makeNewHttpTransport()', () => {
const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1];

const executorResult = registeredRequestExecutor({
body: serializeEnvelope(EVENT_ENVELOPE, new TextEncoder()),
body: serializeEnvelope(EVENT_ENVELOPE, textEncoder),
category: 'error',
});

Expand Down Expand Up @@ -356,7 +350,7 @@ describe('makeNewHttpTransport()', () => {
const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1];

const executorResult = registeredRequestExecutor({
body: serializeEnvelope(EVENT_ENVELOPE, new TextEncoder()),
body: serializeEnvelope(EVENT_ENVELOPE, textEncoder),
category: 'error',
});

Expand Down Expand Up @@ -384,7 +378,7 @@ describe('makeNewHttpTransport()', () => {
const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1];

const executorResult = registeredRequestExecutor({
body: serializeEnvelope(EVENT_ENVELOPE, new TextEncoder()),
body: serializeEnvelope(EVENT_ENVELOPE, textEncoder),
category: 'error',
});

Expand Down
29 changes: 12 additions & 17 deletions packages/node/test/transports/https.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { makeNodeTransport } from '../../src/transports';
import { HTTPModule, HTTPModuleRequestIncomingMessage } from '../../src/transports/http-module';
import testServerCerts from './test-server-certs';

const textEncoder = new TextEncoder();

jest.mock('@sentry/core', () => {
const actualCore = jest.requireActual('@sentry/core');
return {
Expand Down Expand Up @@ -70,7 +72,7 @@ const EVENT_ENVELOPE = createEnvelope<EventEnvelope>({ event_id: 'aa3ff046696b4b
[{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }] as EventItem,
]);

const SERIALIZED_EVENT_ENVELOPE = serializeEnvelope(EVENT_ENVELOPE, new TextEncoder());
const SERIALIZED_EVENT_ENVELOPE = serializeEnvelope(EVENT_ENVELOPE, textEncoder);

const unsafeHttpsModule: HTTPModule = {
request: jest
Expand All @@ -84,7 +86,7 @@ const defaultOptions = {
httpModule: unsafeHttpsModule,
url: TEST_SERVER_URL,
recordDroppedEvent: () => undefined, // noop
textEncoder: new TextEncoder(),
textEncoder,
};

describe('makeNewHttpsTransport()', () => {
Expand Down Expand Up @@ -151,20 +153,13 @@ describe('makeNewHttpsTransport()', () => {
});

const transport = makeNodeTransport(defaultOptions);
await expect(transport.send(EVENT_ENVELOPE)).resolves.toBeUndefined();
});

it('should resolve when server responds with rate limit header and status code 200', async () => {
await setupTestServer({
await expect(transport.send(EVENT_ENVELOPE)).resolves.toEqual({
statusCode: SUCCESS,
responseHeaders: {
'Retry-After': '2700',
'X-Sentry-Rate-Limits': '60::organization, 2700::organization',
headers: {
'retry-after': '2700',
'x-sentry-rate-limits': '60::organization, 2700::organization',
},
});

const transport = makeNodeTransport(defaultOptions);
await expect(transport.send(EVENT_ENVELOPE)).resolves.toBeUndefined();
});

it('should use `caCerts` option', async () => {
Expand Down Expand Up @@ -299,7 +294,7 @@ describe('makeNewHttpsTransport()', () => {
const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1];

const executorResult = registeredRequestExecutor({
body: serializeEnvelope(EVENT_ENVELOPE, new TextEncoder()),
body: serializeEnvelope(EVENT_ENVELOPE, textEncoder),
category: 'error',
});

Expand All @@ -319,7 +314,7 @@ describe('makeNewHttpsTransport()', () => {
const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1];

const executorResult = registeredRequestExecutor({
body: serializeEnvelope(EVENT_ENVELOPE, new TextEncoder()),
body: serializeEnvelope(EVENT_ENVELOPE, textEncoder),
category: 'error',
});

Expand Down Expand Up @@ -347,7 +342,7 @@ describe('makeNewHttpsTransport()', () => {
const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1];

const executorResult = registeredRequestExecutor({
body: serializeEnvelope(EVENT_ENVELOPE, new TextEncoder()),
body: serializeEnvelope(EVENT_ENVELOPE, textEncoder),
category: 'error',
});

Expand Down Expand Up @@ -375,7 +370,7 @@ describe('makeNewHttpsTransport()', () => {
const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1];

const executorResult = registeredRequestExecutor({
body: serializeEnvelope(EVENT_ENVELOPE, new TextEncoder()),
body: serializeEnvelope(EVENT_ENVELOPE, textEncoder),
category: 'error',
});

Expand Down
4 changes: 2 additions & 2 deletions packages/replay/src/replay.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable max-lines */ // TODO: We might want to split this file up
import { addGlobalEventProcessor, captureException, getCurrentHub, setContext } from '@sentry/core';
import { Breadcrumb, ReplayEvent } from '@sentry/types';
import { Breadcrumb, ReplayEvent, TransportMakeRequestResponse } from '@sentry/types';
import { addInstrumentationHandler, logger } from '@sentry/utils';
import { EventType, record } from 'rrweb';

Expand Down Expand Up @@ -904,7 +904,7 @@ export class ReplayContainer implements ReplayContainerInterface {
segmentId: segment_id,
includeReplayStartTimestamp,
eventContext,
}: SendReplay): Promise<void | undefined> {
}: SendReplay): Promise<void | TransportMakeRequestResponse> {
const payloadWithSequence = createPayload({
events,
headers: {
Expand Down
3 changes: 2 additions & 1 deletion packages/types/src/transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ export interface BaseTransportOptions extends InternalBaseTransportOptions {
}

export interface Transport {
send(request: Envelope): PromiseLike<void>;
// TODO (v8) Remove void from return as it was only retained to avoid a breaking change
send(request: Envelope): PromiseLike<void | TransportMakeRequestResponse>;
flush(timeout?: number): PromiseLike<boolean>;
}

Expand Down