Skip to content

Commit 6965615

Browse files
committed
feat: Add queue to backends and client + reason of skip
1 parent 024eb8f commit 6965615

File tree

9 files changed

+128
-22
lines changed

9 files changed

+128
-22
lines changed

packages/browser/src/backend.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Backend, logger, Options, SentryError } from '@sentry/core';
1+
import { Backend, logger, Options, Queue, SentryError } from '@sentry/core';
22
import { SentryEvent, SentryEventHint, SentryResponse, Severity, Status, Transport } from '@sentry/types';
33
import { isDOMError, isDOMException, isError, isErrorEvent, isPlainObject } from '@sentry/utils/is';
44
import { supportsBeacon, supportsFetch } from '@sentry/utils/supports';
@@ -40,6 +40,9 @@ export class BrowserBackend implements Backend {
4040
/** Cached transport used internally. */
4141
private transport?: Transport;
4242

43+
/** A simple queue holding all requests. */
44+
private queue: Queue<SentryResponse> = new Queue();
45+
4346
/**
4447
* @inheritDoc
4548
*/
@@ -141,8 +144,8 @@ export class BrowserBackend implements Backend {
141144
public async sendEvent(event: SentryEvent): Promise<SentryResponse> {
142145
if (!this.options.dsn) {
143146
logger.warn(`Event has been skipped because no Dsn is configured.`);
144-
// We do nothing in case there is no Dsn
145-
return { status: Status.Skipped };
147+
// We do nothing in case there is no DSN
148+
return { status: Status.Skipped, reason: `Event has been skipped because no Dsn is configured.` };
146149
}
147150

148151
if (!this.transport) {
@@ -161,7 +164,7 @@ export class BrowserBackend implements Backend {
161164
}
162165
}
163166

164-
return this.transport.captureEvent(event);
167+
return this.queue.add(this.transport.captureEvent(event));
165168
}
166169

167170
/**
@@ -177,4 +180,11 @@ export class BrowserBackend implements Backend {
177180
public storeScope(): void {
178181
// Noop
179182
}
183+
184+
/**
185+
* @inheritDoc
186+
*/
187+
public close(timeout?: number): Promise<boolean> {
188+
return this.queue.drain(timeout);
189+
}
180190
}

packages/core/src/base.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,4 +310,11 @@ export abstract class BaseClient<B extends Backend, O extends Options> implement
310310

311311
return response;
312312
}
313+
314+
/**
315+
* @inheritDoc
316+
*/
317+
public close(timeout?: number): Promise<boolean> {
318+
return this.backend.close(timeout);
319+
}
313320
}

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ export { API } from './api';
55
export { BackendClass, BaseClient } from './base';
66
export { Dsn } from './dsn';
77
export { SentryError } from './error';
8+
export { Queue } from './queue';
89
export { Backend, Client, LogLevel, Options } from './interfaces';
910
export { initAndBind, ClientClass } from './sdk';

packages/core/src/interfaces.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,15 @@ export interface Client<O extends Options = Options> {
194194

195195
/** Returns the current options. */
196196
getOptions(): O;
197+
198+
/**
199+
* A promise that resolves whenever the queue is empty.
200+
* If you provide a timeout and the queue takes longer to drain the promise returns false.
201+
* Calls {@link Backend.close}.
202+
*
203+
* @param timeout Maximum time in ms the client should wait.
204+
*/
205+
close(timeout?: number): Promise<boolean>;
197206
}
198207

199208
/**
@@ -254,4 +263,12 @@ export interface Backend {
254263
* @param scope The scope to store.
255264
*/
256265
storeScope(scope: Scope): void;
266+
267+
/**
268+
* A promise that resolves whenever the queue is empty.
269+
* If you provide a timeout and the queue takes longer to drain the promise returns false.
270+
*
271+
* @param timeout Maximum time in ms the client should wait.
272+
*/
273+
close(timeout?: number): Promise<boolean>;
257274
}

packages/core/src/queue.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,29 @@ export class Queue<T> {
33

44
public add(task: Promise<T>): Promise<T> {
55
this.queue.add(task);
6-
task.then(() => this.queue.delete(task));
6+
task.then(() => this.queue.delete(task)).catch(() => this.queue.delete(task));
77
return task;
88
}
99

1010
public length(): number {
1111
return this.queue.size;
1212
}
1313

14-
public drain(timeout?: number): Promise<void> {
15-
return new Promise((resolve, reject) => {
14+
public drain(timeout?: number): Promise<boolean> {
15+
return new Promise(resolve => {
1616
const capturedSetTimeout = setTimeout(() => {
1717
if (timeout && timeout > 0) {
18-
reject('Drain timeout reached');
18+
resolve(false);
1919
}
2020
}, timeout);
21-
Promise.all(this.queue.values()).then(() => {
22-
clearTimeout(capturedSetTimeout);
23-
resolve();
24-
});
21+
Promise.all(this.queue.values())
22+
.then(() => {
23+
clearTimeout(capturedSetTimeout);
24+
resolve(true);
25+
})
26+
.catch(() => {
27+
resolve(true);
28+
});
2529
});
2630
}
2731
}

packages/core/test/lib/queue.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,15 @@ describe('Queue', () => {
4343
});
4444

4545
test('drain()', async () => {
46-
expect.assertions(2);
46+
expect.assertions(3);
4747
const q = new Queue<void>();
4848
for (let i = 0; i < 5; i++) {
4949
const p = new Promise<void>(resolve => setTimeout(resolve, 1));
5050
q.add(p);
5151
}
5252
expect(q.length()).toBe(5);
53-
q.drain().then(() => {
53+
q.drain().then(result => {
54+
expect(result).toBeTruthy();
5455
expect(q.length()).toBe(0);
5556
});
5657
jest.runAllTimers();
@@ -64,8 +65,8 @@ describe('Queue', () => {
6465
q.add(p);
6566
}
6667
expect(q.length()).toBe(5);
67-
q.drain(50).catch(e => {
68-
expect(e).toBeTruthy();
68+
q.drain(50).then(result => {
69+
expect(result).toBeFalsy();
6970
});
7071
jest.runAllTimers();
7172
});

packages/node/src/backend.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Backend, Dsn, Options, SentryError } from '@sentry/core';
1+
import { Backend, Dsn, Options, Queue, SentryError } from '@sentry/core';
22
import { getCurrentHub } from '@sentry/hub';
33
import { SentryEvent, SentryEventHint, SentryResponse, Severity, Transport } from '@sentry/types';
44
import { isError, isPlainObject } from '@sentry/utils/is';
@@ -27,6 +27,9 @@ export class NodeBackend implements Backend {
2727
/** Cached transport used internally. */
2828
private transport?: Transport;
2929

30+
/** A simple queue holding all requests. */
31+
private queue: Queue<SentryResponse> = new Queue();
32+
3033
/**
3134
* @inheritDoc
3235
*/
@@ -105,7 +108,7 @@ export class NodeBackend implements Backend {
105108
: new HTTPSTransport(transportOptions);
106109
}
107110

108-
return this.transport.captureEvent(event);
111+
return this.queue.add(this.transport.captureEvent(event));
109112
}
110113

111114
/**
@@ -121,4 +124,11 @@ export class NodeBackend implements Backend {
121124
public storeScope(): void {
122125
// Noop
123126
}
127+
128+
/**
129+
* @inheritDoc
130+
*/
131+
public close(timeout?: number): Promise<boolean> {
132+
return this.queue.drain(timeout);
133+
}
124134
}

packages/node/test/index.test.ts

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,26 @@ import {
1313
Scope,
1414
SentryEvent,
1515
SentryResponse,
16+
Status,
1617
} from '../src';
18+
import { BaseTransport } from '../src/transports';
1719

1820
const dsn = 'https://[email protected]/4291';
1921

2022
declare var global: any;
2123

24+
class SetTimeoutTransport extends BaseTransport {
25+
public async captureEvent(_: SentryEvent): Promise<SentryResponse> {
26+
return new Promise<SentryResponse>(resolve => {
27+
setTimeout(() => {
28+
resolve({
29+
status: Status.fromHttpCode(200),
30+
});
31+
}, 1);
32+
});
33+
}
34+
}
35+
2236
describe('SentryNode', () => {
2337
beforeAll(() => {
2438
init({ dsn });
@@ -32,6 +46,47 @@ describe('SentryNode', () => {
3246
getCurrentHub().popScope();
3347
});
3448

49+
test('close() with to short timeout', done => {
50+
expect.assertions(1);
51+
jest.useFakeTimers();
52+
getCurrentHub().pushScope();
53+
const client = new NodeClient({
54+
dsn,
55+
transport: SetTimeoutTransport,
56+
});
57+
getCurrentHub().bindClient(client);
58+
captureMessage('test');
59+
captureMessage('test');
60+
captureMessage('test');
61+
client.close(50).then(result => {
62+
expect(result).toBeFalsy();
63+
done();
64+
});
65+
jest.runAllTimers();
66+
getCurrentHub().popScope();
67+
});
68+
69+
test('close() with timeout', done => {
70+
expect.assertions(1);
71+
jest.useFakeTimers();
72+
getCurrentHub().pushScope();
73+
const client = new NodeClient({
74+
dsn,
75+
transport: SetTimeoutTransport,
76+
});
77+
getCurrentHub().bindClient(client);
78+
captureMessage('test');
79+
captureMessage('test');
80+
captureMessage('test');
81+
jest.runAllTimers();
82+
client.close(50).then(result => {
83+
expect(result).toBeFalsy();
84+
done();
85+
});
86+
jest.runAllTimers();
87+
getCurrentHub().popScope();
88+
});
89+
3590
describe('getContext() / setContext()', () => {
3691
test('store/load extra', async () => {
3792
configureScope((scope: Scope) => {
@@ -111,10 +166,10 @@ describe('SentryNode', () => {
111166
beforeSend: (event: SentryEvent) => {
112167
expect(event.tags).toEqual({ test: '1' });
113168
expect(event.exception).not.toBeUndefined();
114-
expect(event.exception!.values[0]).not.toBeUndefined();
115-
expect(event.exception!.values[0].type).toBe('Error');
116-
expect(event.exception!.values[0].value).toBe('test');
117-
expect(event.exception!.values[0].stacktrace).toBeTruthy();
169+
expect(event.exception!.values![0]).not.toBeUndefined();
170+
expect(event.exception!.values![0].type).toBe('Error');
171+
expect(event.exception!.values![0].value).toBe('test');
172+
expect(event.exception!.values![0].stacktrace).toBeTruthy();
118173
done();
119174
return event;
120175
},

packages/types/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ export interface Integration {
211211
export interface SentryResponse {
212212
status: Status;
213213
event?: SentryEvent;
214+
reason?: string;
214215
}
215216

216217
/** JSDoc */

0 commit comments

Comments
 (0)