Skip to content

Commit a271c6d

Browse files
authored
ref(core): Streamline event processor handling (#17634)
Avoid re-creating many sync promises where not needed. We used to wrap every result of an event processor in a sync promise, no matter if needed or not. This is not really necessary, likely having a (very minor) perf impact but also making the stacktraces etc. more complicated then neccessary. With this PR, we only wrap the overall result in a sync promise once, if needed, and otherwise just run the event processors directly. Somewhat inspired by looking into this for #17592, but likely does not actually fix this.
1 parent ce46ffc commit a271c6d

File tree

2 files changed

+142
-24
lines changed

2 files changed

+142
-24
lines changed

packages/core/src/eventProcessors.ts

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Event, EventHint } from './types-hoist/event';
33
import type { EventProcessor } from './types-hoist/eventprocessor';
44
import { debug } from './utils/debug-logger';
55
import { isThenable } from './utils/is';
6-
import { SyncPromise } from './utils/syncpromise';
6+
import { rejectedSyncPromise, resolvedSyncPromise } from './utils/syncpromise';
77

88
/**
99
* Process an array of event processors, returning the processed event (or `null` if the event was dropped).
@@ -14,24 +14,33 @@ export function notifyEventProcessors(
1414
hint: EventHint,
1515
index: number = 0,
1616
): PromiseLike<Event | null> {
17-
return new SyncPromise<Event | null>((resolve, reject) => {
18-
const processor = processors[index];
19-
if (event === null || typeof processor !== 'function') {
20-
resolve(event);
21-
} else {
22-
const result = processor({ ...event }, hint) as Event | null;
17+
try {
18+
const result = _notifyEventProcessors(event, hint, processors, index);
19+
return isThenable(result) ? result : resolvedSyncPromise(result);
20+
} catch (error) {
21+
return rejectedSyncPromise(error);
22+
}
23+
}
24+
25+
function _notifyEventProcessors(
26+
event: Event | null,
27+
hint: EventHint,
28+
processors: EventProcessor[],
29+
index: number,
30+
): Event | null | PromiseLike<Event | null> {
31+
const processor = processors[index];
32+
33+
if (!event || !processor) {
34+
return event;
35+
}
36+
37+
const result = processor({ ...event }, hint);
38+
39+
DEBUG_BUILD && result === null && debug.log(`Event processor "${processor.id || '?'}" dropped event`);
2340

24-
DEBUG_BUILD && processor.id && result === null && debug.log(`Event processor "${processor.id}" dropped event`);
41+
if (isThenable(result)) {
42+
return result.then(final => _notifyEventProcessors(final, hint, processors, index + 1));
43+
}
2544

26-
if (isThenable(result)) {
27-
void result
28-
.then(final => notifyEventProcessors(processors, final, hint, index + 1).then(resolve))
29-
.then(null, reject);
30-
} else {
31-
void notifyEventProcessors(processors, result, hint, index + 1)
32-
.then(resolve)
33-
.then(null, reject);
34-
}
35-
}
36-
});
45+
return _notifyEventProcessors(result, hint, processors, index + 1);
3746
}

packages/core/test/lib/client.test.ts

Lines changed: 114 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1833,15 +1833,13 @@ describe('Client', () => {
18331833
});
18341834
});
18351835

1836-
test('event processor sends an event and logs when it crashes', () => {
1837-
expect.assertions(3);
1838-
1836+
test('event processor sends an event and logs when it crashes synchronously', () => {
18391837
const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN });
18401838
const client = new TestClient(options);
18411839
const captureExceptionSpy = vi.spyOn(client, 'captureException');
18421840
const loggerWarnSpy = vi.spyOn(debugLoggerModule.debug, 'warn');
18431841
const scope = new Scope();
1844-
const exception = new Error('sorry');
1842+
const exception = new Error('sorry 1');
18451843
scope.addEventProcessor(() => {
18461844
throw exception;
18471845
});
@@ -1850,7 +1848,43 @@ describe('Client', () => {
18501848

18511849
expect(TestClient.instance!.event!.exception!.values![0]).toStrictEqual({
18521850
type: 'Error',
1853-
value: 'sorry',
1851+
value: 'sorry 1',
1852+
mechanism: { type: 'internal', handled: false },
1853+
});
1854+
expect(captureExceptionSpy).toBeCalledWith(exception, {
1855+
data: {
1856+
__sentry__: true,
1857+
},
1858+
originalException: exception,
1859+
mechanism: { type: 'internal', handled: false },
1860+
});
1861+
expect(loggerWarnSpy).toBeCalledWith(
1862+
`Event processing pipeline threw an error, original event will not be sent. Details have been sent as a new event.\nReason: ${exception}`,
1863+
);
1864+
});
1865+
1866+
test('event processor sends an event and logs when it crashes asynchronously', async () => {
1867+
vi.useFakeTimers();
1868+
1869+
const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN });
1870+
const client = new TestClient(options);
1871+
const captureExceptionSpy = vi.spyOn(client, 'captureException');
1872+
const loggerWarnSpy = vi.spyOn(debugLoggerModule.debug, 'warn');
1873+
const scope = new Scope();
1874+
const exception = new Error('sorry 2');
1875+
scope.addEventProcessor(() => {
1876+
return new Promise((_resolve, reject) => {
1877+
reject(exception);
1878+
});
1879+
});
1880+
1881+
client.captureEvent({ message: 'hello' }, {}, scope);
1882+
1883+
await vi.runOnlyPendingTimersAsync();
1884+
1885+
expect(TestClient.instance!.event!.exception!.values![0]).toStrictEqual({
1886+
type: 'Error',
1887+
value: 'sorry 2',
18541888
mechanism: { type: 'internal', handled: false },
18551889
});
18561890
expect(captureExceptionSpy).toBeCalledWith(exception, {
@@ -1865,6 +1899,81 @@ describe('Client', () => {
18651899
);
18661900
});
18671901

1902+
test('event processor sends an event and logs when it crashes synchronously in processor chain', () => {
1903+
const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN });
1904+
const client = new TestClient(options);
1905+
const captureExceptionSpy = vi.spyOn(client, 'captureException');
1906+
const scope = new Scope();
1907+
const exception = new Error('sorry 3');
1908+
1909+
const processor1 = vi.fn(event => {
1910+
return event;
1911+
});
1912+
const processor2 = vi.fn(() => {
1913+
throw exception;
1914+
});
1915+
const processor3 = vi.fn(event => {
1916+
return event;
1917+
});
1918+
1919+
scope.addEventProcessor(processor1);
1920+
scope.addEventProcessor(processor2);
1921+
scope.addEventProcessor(processor3);
1922+
1923+
client.captureEvent({ message: 'hello' }, {}, scope);
1924+
1925+
expect(processor1).toHaveBeenCalledTimes(1);
1926+
expect(processor2).toHaveBeenCalledTimes(1);
1927+
expect(processor3).toHaveBeenCalledTimes(0);
1928+
1929+
expect(captureExceptionSpy).toBeCalledWith(exception, {
1930+
data: {
1931+
__sentry__: true,
1932+
},
1933+
originalException: exception,
1934+
mechanism: { type: 'internal', handled: false },
1935+
});
1936+
});
1937+
1938+
test('event processor sends an event and logs when it crashes asynchronously in processor chain', async () => {
1939+
vi.useFakeTimers();
1940+
1941+
const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN });
1942+
const client = new TestClient(options);
1943+
const captureExceptionSpy = vi.spyOn(client, 'captureException');
1944+
const scope = new Scope();
1945+
const exception = new Error('sorry 4');
1946+
1947+
const processor1 = vi.fn(async event => {
1948+
return event;
1949+
});
1950+
const processor2 = vi.fn(async () => {
1951+
throw exception;
1952+
});
1953+
const processor3 = vi.fn(event => {
1954+
return event;
1955+
});
1956+
1957+
scope.addEventProcessor(processor1);
1958+
scope.addEventProcessor(processor2);
1959+
scope.addEventProcessor(processor3);
1960+
1961+
client.captureEvent({ message: 'hello' }, {}, scope);
1962+
await vi.runOnlyPendingTimersAsync();
1963+
1964+
expect(processor1).toHaveBeenCalledTimes(1);
1965+
expect(processor2).toHaveBeenCalledTimes(1);
1966+
expect(processor3).toHaveBeenCalledTimes(0);
1967+
1968+
expect(captureExceptionSpy).toBeCalledWith(exception, {
1969+
data: {
1970+
__sentry__: true,
1971+
},
1972+
originalException: exception,
1973+
mechanism: { type: 'internal', handled: false },
1974+
});
1975+
});
1976+
18681977
test('records events dropped due to `sampleRate` option', () => {
18691978
expect.assertions(1);
18701979

0 commit comments

Comments
 (0)