Skip to content

Commit 7647e72

Browse files
authored
ref(react): Add mechanism to reactErrorHandler and adjust mechanism in ErrorBoundary (#17602)
Both mechanisms now follow the trace origin naming scheme. Decided to make the `handled` value of `reactErrorHandler` depend on the definedness of the passed callback.
1 parent 63a6797 commit 7647e72

File tree

5 files changed

+86
-10
lines changed

5 files changed

+86
-10
lines changed

dev-packages/e2e-tests/test-applications/react-19/tests/errors.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,20 @@ test('Catches errors caught by error boundary', async ({ page }) => {
1919

2020
expect(errorEvent.exception?.values).toHaveLength(2);
2121
expect(errorEvent.exception?.values?.[0]?.value).toBe('caught error');
22+
expect(errorEvent.exception?.values?.[0]?.mechanism).toEqual({
23+
type: 'auto.function.react.error_handler',
24+
handled: true, // true because a callback was provided
25+
exception_id: 1,
26+
parent_id: 0,
27+
source: 'cause',
28+
});
29+
30+
expect(errorEvent.exception?.values?.[1]?.value).toBe('caught error');
31+
expect(errorEvent.exception?.values?.[1]?.mechanism).toEqual({
32+
type: 'generic',
33+
handled: true, // true because a callback was provided
34+
exception_id: 0,
35+
});
2236
});
2337

2438
test('Catches errors uncaught by error boundary', async ({ page }) => {
@@ -39,4 +53,18 @@ test('Catches errors uncaught by error boundary', async ({ page }) => {
3953

4054
expect(errorEvent.exception?.values).toHaveLength(2);
4155
expect(errorEvent.exception?.values?.[0]?.value).toBe('uncaught error');
56+
expect(errorEvent.exception?.values?.[0]?.mechanism).toEqual({
57+
type: 'auto.function.react.error_handler',
58+
handled: true, // true because a callback was provided
59+
exception_id: 1,
60+
parent_id: 0,
61+
source: 'cause',
62+
});
63+
64+
expect(errorEvent.exception?.values?.[1]?.value).toBe('uncaught error');
65+
expect(errorEvent.exception?.values?.[1]?.mechanism).toEqual({
66+
type: 'generic',
67+
handled: true, // true because a callback was provided
68+
exception_id: 0,
69+
});
4270
});

packages/react/src/error.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,11 @@ export function reactErrorHandler(
9595
): (error: any, errorInfo: ErrorInfo) => void {
9696
// eslint-disable-next-line @typescript-eslint/no-explicit-any
9797
return (error: any, errorInfo: ErrorInfo) => {
98-
const eventId = captureReactException(error, errorInfo);
99-
if (callback) {
98+
const hasCallback = !!callback;
99+
const eventId = captureReactException(error, errorInfo, {
100+
mechanism: { handled: hasCallback, type: 'auto.function.react.error_handler' },
101+
});
102+
if (hasCallback) {
100103
callback(error, errorInfo, eventId);
101104
}
102105
};

packages/react/src/errorboundary.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,9 @@ class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundarySta
123123
}
124124

125125
const handled = this.props.handled != null ? this.props.handled : !!this.props.fallback;
126-
const eventId = captureReactException(error, errorInfo, { mechanism: { handled } });
126+
const eventId = captureReactException(error, errorInfo, {
127+
mechanism: { handled, type: 'auto.function.react.error_boundary' },
128+
});
127129

128130
if (onError) {
129131
onError(error, componentStack, eventId);

packages/react/test/error.test.ts

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { describe, expect, test } from 'vitest';
2-
import { isAtLeastReact17 } from '../src/error';
1+
import * as SentryBrowser from '@sentry/browser';
2+
import { beforeEach, describe, expect, it, test, vi } from 'vitest';
3+
import { isAtLeastReact17, reactErrorHandler } from '../src/error';
34

45
describe('isAtLeastReact17', () => {
56
test.each([
@@ -13,3 +14,45 @@ describe('isAtLeastReact17', () => {
1314
expect(isAtLeastReact17(input)).toBe(output);
1415
});
1516
});
17+
18+
describe('reactErrorHandler', () => {
19+
const captureException = vi.spyOn(SentryBrowser, 'captureException');
20+
21+
beforeEach(() => {
22+
captureException.mockClear();
23+
});
24+
25+
it('captures errors as unhandled when no callback is provided', () => {
26+
const error = new Error('test error');
27+
const errorInfo = { componentStack: 'component stack' };
28+
29+
const handler = reactErrorHandler();
30+
31+
handler(error, errorInfo);
32+
33+
expect(captureException).toHaveBeenCalledTimes(1);
34+
expect(captureException).toHaveBeenCalledWith(error, {
35+
mechanism: { handled: false, type: 'auto.function.react.error_handler' },
36+
});
37+
});
38+
39+
it('captures errors as handled when a callback is provided', () => {
40+
captureException.mockReturnValueOnce('custom-event-id');
41+
42+
const error = new Error('test error');
43+
const errorInfo = { componentStack: 'component stack' };
44+
45+
const callback = vi.fn();
46+
const handler = reactErrorHandler(callback);
47+
48+
handler(error, errorInfo);
49+
50+
expect(captureException).toHaveBeenCalledTimes(1);
51+
expect(captureException).toHaveBeenCalledWith(error, {
52+
mechanism: { handled: true, type: 'auto.function.react.error_handler' },
53+
});
54+
55+
expect(callback).toHaveBeenCalledTimes(1);
56+
expect(callback).toHaveBeenCalledWith(error, errorInfo, 'custom-event-id');
57+
});
58+
});

packages/react/test/errorboundary.test.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ describe('ErrorBoundary', () => {
385385

386386
expect(mockCaptureException).toHaveBeenCalledTimes(1);
387387
expect(mockCaptureException).toHaveBeenLastCalledWith(expect.any(Error), {
388-
mechanism: { handled: true },
388+
mechanism: { handled: true, type: 'auto.function.react.error_boundary' },
389389
});
390390

391391
expect(scopeSetContextSpy).toHaveBeenCalledTimes(1);
@@ -444,7 +444,7 @@ describe('ErrorBoundary', () => {
444444

445445
expect(mockCaptureException).toHaveBeenCalledTimes(1);
446446
expect(mockCaptureException).toHaveBeenLastCalledWith('bam', {
447-
mechanism: { handled: true },
447+
mechanism: { handled: true, type: 'auto.function.react.error_boundary' },
448448
});
449449

450450
expect(scopeSetContextSpy).toHaveBeenCalledTimes(1);
@@ -483,7 +483,7 @@ describe('ErrorBoundary', () => {
483483

484484
expect(mockCaptureException).toHaveBeenCalledTimes(1);
485485
expect(mockCaptureException).toHaveBeenLastCalledWith(expect.any(Error), {
486-
mechanism: { handled: true },
486+
mechanism: { handled: true, type: 'auto.function.react.error_boundary' },
487487
});
488488

489489
expect(scopeSetContextSpy).toHaveBeenCalledTimes(1);
@@ -527,7 +527,7 @@ describe('ErrorBoundary', () => {
527527

528528
expect(mockCaptureException).toHaveBeenCalledTimes(1);
529529
expect(mockCaptureException).toHaveBeenLastCalledWith(expect.any(Error), {
530-
mechanism: { handled: true },
530+
mechanism: { handled: true, type: 'auto.function.react.error_boundary' },
531531
});
532532

533533
expect(scopeSetContextSpy).toHaveBeenCalledTimes(1);
@@ -695,7 +695,7 @@ describe('ErrorBoundary', () => {
695695

696696
expect(mockCaptureException).toHaveBeenCalledTimes(1);
697697
expect(mockCaptureException).toHaveBeenLastCalledWith(expect.any(Object), {
698-
mechanism: { handled: expected },
698+
mechanism: { handled: expected, type: 'auto.function.react.error_boundary' },
699699
});
700700

701701
expect(scopeSetContextSpy).toHaveBeenCalledTimes(1);

0 commit comments

Comments
 (0)