Skip to content

Commit 6b1bef1

Browse files
committed
ref: Make hook afterSendEvent
1 parent 017f1e2 commit 6b1bef1

File tree

10 files changed

+244
-57
lines changed

10 files changed

+244
-57
lines changed

packages/core/src/baseclient.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -322,8 +322,8 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
322322
}
323323

324324
const promise = this._sendEnvelope(env);
325-
if (promise && isErrorEvent(event)) {
326-
promise.then(sendResponse => this.emit('afterSendErrorEvent', event, sendResponse), null);
325+
if (promise) {
326+
promise.then(sendResponse => this.emit('afterSendEvent', event, sendResponse), null);
327327
}
328328
}
329329
}
@@ -369,8 +369,8 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
369369

370370
/** @inheritdoc */
371371
public on(
372-
hook: 'afterSendErrorEvent',
373-
callback: (event: ErrorEvent, sendResponse: TransportMakeRequestResponse | void) => void,
372+
hook: 'afterSendEvent',
373+
callback: (event: Event, sendResponse: TransportMakeRequestResponse | void) => void,
374374
): void;
375375

376376
/** @inheritdoc */
@@ -390,7 +390,7 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
390390
public emit(hook: 'beforeEnvelope', envelope: Envelope): void;
391391

392392
/** @inheritdoc */
393-
public emit(hook: 'afterSendErrorEvent', event: ErrorEvent, sendResponse: TransportMakeRequestResponse | void): void;
393+
public emit(hook: 'afterSendEvent', event: Event, sendResponse: TransportMakeRequestResponse | void): void;
394394

395395
/** @inheritdoc */
396396
public emit(hook: string, ...rest: unknown[]): void {

packages/core/test/lib/base.test.ts

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1643,7 +1643,7 @@ describe('BaseClient', () => {
16431643
jest.useRealTimers();
16441644
});
16451645

1646-
it('emits `afterSendErrorEvent` when sending an error', async () => {
1646+
it('emits `afterSendEvent` when sending an error', async () => {
16471647
const client = new TestClient(
16481648
getDefaultTestClientOptions({
16491649
dsn: PUBLIC_DSN,
@@ -1657,7 +1657,7 @@ describe('BaseClient', () => {
16571657
const errorEvent: Event = { message: 'error' };
16581658

16591659
const callback = jest.fn();
1660-
client.on('afterSendErrorEvent', callback);
1660+
client.on('afterSendEvent', callback);
16611661

16621662
client.sendEvent(errorEvent);
16631663
jest.runAllTimers();
@@ -1671,7 +1671,35 @@ describe('BaseClient', () => {
16711671
expect(callback).toBeCalledWith(errorEvent, {});
16721672
});
16731673

1674-
it('still triggers `afterSendErrorEvent` when transport.send rejects', async () => {
1674+
it('emits `afterSendEvent` when sending a transaction', async () => {
1675+
const client = new TestClient(
1676+
getDefaultTestClientOptions({
1677+
dsn: PUBLIC_DSN,
1678+
enableSend: true,
1679+
}),
1680+
);
1681+
1682+
// @ts-ignore
1683+
const mockSend = jest.spyOn(client._transport, 'send');
1684+
1685+
const transactionEvent: Event = { type: 'transaction', event_id: 'tr1' };
1686+
1687+
const callback = jest.fn();
1688+
client.on('afterSendEvent', callback);
1689+
1690+
client.sendEvent(transactionEvent);
1691+
jest.runAllTimers();
1692+
// Wait for two ticks
1693+
// note that for whatever reason, await new Promise(resolve => setTimeout(resolve, 0)) causes the test to hang
1694+
await undefined;
1695+
await undefined;
1696+
1697+
expect(mockSend).toBeCalledTimes(1);
1698+
expect(callback).toBeCalledTimes(1);
1699+
expect(callback).toBeCalledWith(transactionEvent, {});
1700+
});
1701+
1702+
it('still triggers `afterSendEvent` when transport.send rejects', async () => {
16751703
expect.assertions(3);
16761704

16771705
const client = new TestClient(
@@ -1689,7 +1717,7 @@ describe('BaseClient', () => {
16891717
const errorEvent: Event = { message: 'error' };
16901718

16911719
const callback = jest.fn();
1692-
client.on('afterSendErrorEvent', callback);
1720+
client.on('afterSendEvent', callback);
16931721

16941722
client.sendEvent(errorEvent);
16951723
jest.runAllTimers();
@@ -1721,7 +1749,7 @@ describe('BaseClient', () => {
17211749
const errorEvent: Event = { message: 'error' };
17221750

17231751
const callback = jest.fn();
1724-
client.on('afterSendErrorEvent', callback);
1752+
client.on('afterSendEvent', callback);
17251753

17261754
client.sendEvent(errorEvent);
17271755
jest.runAllTimers();

packages/replay/src/coreHandlers/handleAfterSendError.ts renamed to packages/replay/src/coreHandlers/handleAfterSendEvent.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,45 @@
11
import { getCurrentHub } from '@sentry/core';
2-
import type { ErrorEvent, Transport, TransportMakeRequestResponse } from '@sentry/types';
2+
import type { Event, Transport, TransportMakeRequestResponse } from '@sentry/types';
33

44
import { UNABLE_TO_SEND_REPLAY } from '../constants';
55
import type { ReplayContainer } from '../types';
6+
import { isErrorEvent, isTransactionEvent } from '../util/eventUtils';
67

78
/**
89
* Returns a listener to be added to `client.on('afterSendErrorEvent, listener)`.
910
*/
10-
export function handleAfterSendError(
11+
export function handleAfterSendEvent(
1112
replay: ReplayContainer,
12-
): (event: ErrorEvent, sendResponse: TransportMakeRequestResponse | void) => void {
13+
): (event: Event, sendResponse: TransportMakeRequestResponse | void) => void {
1314
// Custom transports may still be returning `Promise<void>`, which means we cannot expect the status code to be available there
1415
const enforceStatusCode = isBaseTransportSend();
1516

16-
return (event: ErrorEvent, sendResponse: TransportMakeRequestResponse | void) => {
17+
return (event: Event, sendResponse: TransportMakeRequestResponse | void) => {
18+
if (!isErrorEvent(event) && !isTransactionEvent(event)) {
19+
return;
20+
}
21+
1722
const statusCode = sendResponse && sendResponse.statusCode;
1823

19-
// We only want to do stuff on successfull error sending, otherwise you get error replays without errors attached
24+
// We only want to do stuff on successful error sending, otherwise you get error replays without errors attached
2025
// If not using the base transport, we allow `undefined` response (as a custom transport may not implement this correctly yet)
2126
// If we do use the base transport, we skip if we encountered an non-OK status code
2227
if (enforceStatusCode && (!statusCode || statusCode < 200 || statusCode >= 300)) {
2328
return;
2429
}
2530

31+
// Collect traceIds in _context regardless of `recordingMode`
32+
// In error mode, _context gets cleared on every checkout
33+
if (isTransactionEvent(event) && event.contexts && event.contexts.trace && event.contexts.trace.trace_id) {
34+
replay.getContext().traceIds.add(event.contexts.trace.trace_id as string);
35+
return;
36+
}
37+
38+
// Everything below is just for error events
39+
if (!isErrorEvent(event)) {
40+
return;
41+
}
42+
2643
// Add error to list of errorIds of replay
2744
if (event.event_id) {
2845
replay.getContext().errorIds.add(event.event_id);

packages/replay/src/coreHandlers/handleGlobalEvent.ts

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,34 @@
11
import { addBreadcrumb } from '@sentry/core';
2-
import type { ErrorEvent, Event, EventHint } from '@sentry/types';
2+
import type { Event, EventHint } from '@sentry/types';
33
import { logger } from '@sentry/utils';
44

5-
import { REPLAY_EVENT_NAME } from '../constants';
65
import type { ReplayContainer } from '../types';
6+
import { isErrorEvent, isReplayEvent, isTransactionEvent } from '../util/eventUtils';
77
import { isRrwebError } from '../util/isRrwebError';
8-
import { handleAfterSendError } from './handleAfterSendError';
8+
import { handleAfterSendEvent } from './handleAfterSendEvent';
99

1010
/**
1111
* Returns a listener to be added to `addGlobalEventProcessor(listener)`.
1212
*/
1313
export function handleGlobalEventListener(
1414
replay: ReplayContainer,
15-
includeErrorHandling = false,
15+
includeAfterSendEventHandling = false,
1616
): (event: Event, hint: EventHint) => Event | null {
17-
const errorHandler = includeErrorHandling ? handleAfterSendError(replay) : undefined;
17+
const afterSendHandler = includeAfterSendEventHandling ? handleAfterSendEvent(replay) : undefined;
1818

1919
return (event: Event, hint: EventHint) => {
20-
// Do not apply replayId to the root event
21-
if (event.type === REPLAY_EVENT_NAME) {
20+
if (isReplayEvent(event)) {
2221
// Replays have separate set of breadcrumbs, do not include breadcrumbs
2322
// from core SDK
2423
delete event.breadcrumbs;
2524
return event;
2625
}
2726

27+
// We only want to handle errors & transactions, nothing else
28+
if (!isErrorEvent(event) && !isTransactionEvent(event)) {
29+
return event;
30+
}
31+
2832
// Unless `captureExceptions` is enabled, we want to ignore errors coming from rrweb
2933
// As there can be a bunch of stuff going wrong in internals there, that we don't want to bubble up to users
3034
if (isRrwebError(event, hint) && !replay.getOptions()._experiments.captureExceptions) {
@@ -33,28 +37,20 @@ export function handleGlobalEventListener(
3337
}
3438

3539
// Only tag transactions with replayId if not waiting for an error
36-
// @ts-ignore private
37-
if (!event.type || replay.recordingMode === 'session') {
40+
if (isErrorEvent(event) || (isTransactionEvent(event) && replay.recordingMode === 'session')) {
3841
event.tags = { ...event.tags, replayId: replay.getSessionId() };
3942
}
4043

41-
// Collect traceIds in _context regardless of `recordingMode` - if it's true,
42-
// _context gets cleared on every checkout
43-
if (event.type === 'transaction' && event.contexts && event.contexts.trace && event.contexts.trace.trace_id) {
44-
replay.getContext().traceIds.add(event.contexts.trace.trace_id as string);
45-
return event;
46-
}
47-
48-
if (__DEBUG_BUILD__ && replay.getOptions()._experiments.traceInternals) {
44+
if (__DEBUG_BUILD__ && replay.getOptions()._experiments.traceInternals && isErrorEvent(event)) {
4945
const exc = getEventExceptionValues(event);
5046
addInternalBreadcrumb({
5147
message: `Tagging event (${event.event_id}) - ${event.message} - ${exc.type}: ${exc.value}`,
5248
});
5349
}
5450

55-
if (errorHandler && !event.type) {
51+
if (afterSendHandler) {
5652
// Pretend the error had a 200 response so we capture it
57-
errorHandler(event as ErrorEvent, { statusCode: 200 });
53+
afterSendHandler(event, { statusCode: 200 });
5854
}
5955

6056
return event;

packages/replay/src/util/addGlobalListeners.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { BaseClient } from '@sentry/core';
22
import { addGlobalEventProcessor, getCurrentHub } from '@sentry/core';
33
import { addInstrumentationHandler } from '@sentry/utils';
44

5-
import { handleAfterSendError } from '../coreHandlers/handleAfterSendError';
5+
import { handleAfterSendEvent } from '../coreHandlers/handleAfterSendEvent';
66
import { handleDomListener } from '../coreHandlers/handleDom';
77
import { handleFetchSpanListener } from '../coreHandlers/handleFetch';
88
import { handleGlobalEventListener } from '../coreHandlers/handleGlobalEvent';
@@ -36,6 +36,6 @@ export function addGlobalListeners(replay: ReplayContainer): void {
3636

3737
if (hasHooks) {
3838
// eslint-disable-next-line @typescript-eslint/no-explicit-any
39-
(client as BaseClient<any>).on('afterSendErrorEvent', handleAfterSendError(replay));
39+
(client as BaseClient<any>).on('afterSendEvent', handleAfterSendEvent(replay));
4040
}
4141
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { ErrorEvent, Event, ReplayEvent, TransactionEvent } from '@sentry/types';
2+
3+
/** If the event is an error event */
4+
export function isErrorEvent(event: Event): event is ErrorEvent {
5+
return !event.type;
6+
}
7+
8+
/** If the event is a transaction event */
9+
export function isTransactionEvent(event: Event): event is TransactionEvent {
10+
return event.type === 'transaction';
11+
}
12+
13+
/** If the event is an replay event */
14+
export function isReplayEvent(event: Event): event is ReplayEvent {
15+
return event.type === 'replay_event';
16+
}

packages/replay/test/fixtures/transaction.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Event, SeverityLevel } from '@sentry/types';
22

3-
export function Transaction(obj?: Partial<Event>): any {
3+
export function Transaction(traceId?: string, obj?: Partial<Event>): any {
44
const timestamp = new Date().getTime() / 1000;
55

66
return {
@@ -22,7 +22,7 @@ export function Transaction(obj?: Partial<Event>): any {
2222
hardwareConcurrency: '10',
2323
sentry_reportAllChanges: false,
2424
},
25-
trace_id: 'trace_id',
25+
trace_id: traceId || 'trace_id',
2626
},
2727
}, // }}}
2828
spans: [

0 commit comments

Comments
 (0)