Skip to content

Commit 97caefa

Browse files
fix: Symbolicate error.cause on debug builds (#3920)
1 parent 4cc5c27 commit 97caefa

File tree

4 files changed

+541
-10
lines changed

4 files changed

+541
-10
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
### Fixes
1111

12+
- errors with cause are now correctly serialized on debug build ([#3920](https://github.com/getsentry/sentry-react-native/pull/3920))
1213
- `sentry-expo-upload-sourcemaps` no longer requires Sentry url when uploading sourcemaps to `sentry.io` ([#3915](https://github.com/getsentry/sentry-react-native/pull/3915))
1314
- Flavor aware Android builds use `SENTRY_AUTH_TOKEN` env as fallback when token not found in `sentry-flavor-type.properties`. ([#3917](https://github.com/getsentry/sentry-react-native/pull/3917))
1415
- `mechanism.handled:false` should crash current session ([#3900](https://github.com/getsentry/sentry-react-native/pull/3900))

src/js/integrations/debugsymbolicator.ts

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ import { convertIntegrationFnToClass } from '@sentry/core';
22
import type {
33
Event,
44
EventHint,
5+
Exception,
56
Integration,
67
IntegrationClass,
78
IntegrationFnResult,
89
StackFrame as SentryStackFrame,
910
} from '@sentry/types';
1011
import { addContextToFrame, logger } from '@sentry/utils';
1112

13+
import type { ExtendedError } from '../utils/error';
1214
import { getFramesToPop, isErrorLike } from '../utils/error';
1315
import type * as ReactNative from '../vendor/react-native';
1416
import { fetchSourceContext, getDevServer, parseErrorStack, symbolicateStackTrace } from './debugsymbolicatorutils';
@@ -51,13 +53,14 @@ export const DebugSymbolicator = convertIntegrationFnToClass(
5153
) as IntegrationClass<Integration>;
5254

5355
async function processEvent(event: Event, hint: EventHint): Promise<Event> {
54-
if (event.exception && isErrorLike(hint.originalException)) {
56+
if (event.exception?.values && isErrorLike(hint.originalException)) {
5557
// originalException is ErrorLike object
56-
const symbolicatedFrames = await symbolicate(
57-
hint.originalException.stack,
58-
getFramesToPop(hint.originalException as Error),
59-
);
60-
symbolicatedFrames && replaceExceptionFramesInEvent(event, symbolicatedFrames);
58+
const errorGroup = getExceptionGroup(hint.originalException);
59+
for (const [index, error] of errorGroup.entries()) {
60+
const symbolicatedFrames = await symbolicate(error.stack, getFramesToPop(error));
61+
62+
symbolicatedFrames && replaceExceptionFramesInException(event.exception.values[index], symbolicatedFrames);
63+
}
6164
} else if (hint.syntheticException && isErrorLike(hint.syntheticException)) {
6265
// syntheticException is Error object
6366
const symbolicatedFrames = await symbolicate(
@@ -66,7 +69,9 @@ async function processEvent(event: Event, hint: EventHint): Promise<Event> {
6669
);
6770

6871
if (event.exception) {
69-
symbolicatedFrames && replaceExceptionFramesInEvent(event, symbolicatedFrames);
72+
symbolicatedFrames &&
73+
event.exception.values &&
74+
replaceExceptionFramesInException(event.exception.values[0], symbolicatedFrames);
7075
} else if (event.threads) {
7176
// RN JS doesn't have threads
7277
symbolicatedFrames && replaceThreadFramesInEvent(event, symbolicatedFrames);
@@ -149,9 +154,9 @@ async function convertReactNativeFramesToSentryFrames(frames: ReactNative.StackF
149154
* @param event Event
150155
* @param frames StackFrame[]
151156
*/
152-
function replaceExceptionFramesInEvent(event: Event, frames: SentryStackFrame[]): void {
153-
if (event.exception && event.exception.values && event.exception.values[0] && event.exception.values[0].stacktrace) {
154-
event.exception.values[0].stacktrace.frames = frames.reverse();
157+
function replaceExceptionFramesInException(exception: Exception, frames: SentryStackFrame[]): void {
158+
if (exception?.stacktrace) {
159+
exception.stacktrace.frames = frames.reverse();
155160
}
156161
}
157162

@@ -200,3 +205,17 @@ async function addSourceContext(frame: SentryStackFrame): Promise<void> {
200205
const lines = sourceContext.split('\n');
201206
addContextToFrame(lines, frame);
202207
}
208+
209+
/**
210+
* Return a list containing the original exception and also the cause if found.
211+
*
212+
* @param originalException The original exception.
213+
*/
214+
function getExceptionGroup(originalException: unknown): (Error & { stack: string })[] {
215+
const err = originalException as ExtendedError;
216+
const errorGroup: (Error & { stack: string })[] = [];
217+
for (let cause: ExtendedError | undefined = err; isErrorLike(cause); cause = cause.cause) {
218+
errorGroup.push(cause);
219+
}
220+
return errorGroup;
221+
}

src/js/utils/error.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export interface ExtendedError extends Error {
22
framesToPop?: number | undefined;
3+
cause?: Error | undefined;
34
}
45

56
// Sentry Stack Parser is skipping lines not frames

0 commit comments

Comments
 (0)