-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(react): Add Sentry.captureReactException
- Loading branch information
1 parent
abe31ba
commit 7741886
Showing
6 changed files
with
148 additions
and
70 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { captureException } from '@sentry/browser'; | ||
import type { EventHint } from '@sentry/types'; | ||
import { isError } from '@sentry/utils'; | ||
import { version } from 'react'; | ||
import type { ErrorInfo } from 'react'; | ||
|
||
/** | ||
* See if React major version is 17+ by parsing version string. | ||
*/ | ||
export function isAtLeastReact17(reactVersion: string): boolean { | ||
const reactMajor = reactVersion.match(/^([^.]+)/); | ||
return reactMajor !== null && parseInt(reactMajor[0]) >= 17; | ||
} | ||
|
||
/** | ||
* Recurse through `error.cause` chain to set cause on an error. | ||
*/ | ||
export function setCause(error: Error & { cause?: Error }, cause: Error): void { | ||
const seenErrors = new WeakMap<Error, boolean>(); | ||
|
||
function recurse(error: Error & { cause?: Error }, cause: Error): void { | ||
// If we've already seen the error, there is a recursive loop somewhere in the error's | ||
// cause chain. Let's just bail out then to prevent a stack overflow. | ||
if (seenErrors.has(error)) { | ||
return; | ||
} | ||
if (error.cause) { | ||
seenErrors.set(error, true); | ||
return recurse(error.cause, cause); | ||
} | ||
error.cause = cause; | ||
} | ||
|
||
recurse(error, cause); | ||
} | ||
|
||
/** | ||
* Captures an error that was thrown by a React ErrorBoundary or React root. | ||
* | ||
* @param error The error to capture. | ||
* @param errorInfo The errorInfo provided by React. | ||
* @param hint Optional additional data to attach to the Sentry event. | ||
* @returns the id of the captured Sentry event. | ||
*/ | ||
export function captureReactException( | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
error: any, | ||
{ componentStack }: ErrorInfo, | ||
hint?: EventHint, | ||
): string { | ||
// If on React version >= 17, create stack trace from componentStack param and links | ||
// to to the original error using `error.cause` otherwise relies on error param for stacktrace. | ||
// Linking errors requires the `LinkedErrors` integration be enabled. | ||
// See: https://reactjs.org/blog/2020/08/10/react-v17-rc.html#native-component-stacks | ||
// | ||
// Although `componentDidCatch` is typed to accept an `Error` object, it can also be invoked | ||
// with non-error objects. This is why we need to check if the error is an error-like object. | ||
// See: https://github.com/getsentry/sentry-javascript/issues/6167 | ||
if (isAtLeastReact17(version) && isError(error)) { | ||
const errorBoundaryError = new Error(error.message); | ||
errorBoundaryError.name = `React ErrorBoundary ${error.name}`; | ||
errorBoundaryError.stack = componentStack; | ||
|
||
// Using the `LinkedErrors` integration to link the errors together. | ||
setCause(error, errorBoundaryError); | ||
} | ||
|
||
return captureException(error, { | ||
...hint, | ||
captureContext: { | ||
contexts: { react: { componentStack } }, | ||
}, | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { isAtLeastReact17 } from '../src/error'; | ||
|
||
describe('isAtLeastReact17', () => { | ||
test.each([ | ||
['React 16', '16.0.4', false], | ||
['React 17', '17.0.0', true], | ||
['React 17 with no patch', '17.4', true], | ||
['React 17 with no patch and no minor', '17', true], | ||
['React 18', '18.1.0', true], | ||
['React 19', '19.0.0', true], | ||
])('%s', (_: string, input: string, output: ReturnType<typeof isAtLeastReact17>) => { | ||
expect(isAtLeastReact17(input)).toBe(output); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters