Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

### Fixes

- App slowdown when using Sentry console Integration and filtering by `assert` ([#4770](https://github.com/getsentry/sentry-react-native/pull/4770))
- Equalize TTID and TTFD duration when TTFD manual API is called and resolved before auto TTID ([#4680](https://github.com/getsentry/sentry-react-native/pull/4680))
- Avoid loading Sentry native components in Expo Go ([#4696](https://github.com/getsentry/sentry-react-native/pull/4696))
- Avoid silent failure when JS bundle was not created due to Sentry Xcode scripts failure ([#4690](https://github.com/getsentry/sentry-react-native/pull/4690))
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/js/integrations/debugsymbolicator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ export const debugSymbolicatorIntegration = (): Integration => {
};

async function processEvent(event: Event, hint: EventHint): Promise<Event> {
// event created by consoleIntegration, symbolicator can trigger those events.
// so we drop the event to avoid an infinite loop.
if (
event.extra?.['arguments'] &&
event.message?.startsWith("Assertion failed: 'this' is expected an Event object, but got")
) {
return event;
}
if (event.exception?.values && isErrorLike(hint.originalException)) {
// originalException is ErrorLike object
const errorGroup = getExceptionGroup(hint.originalException);
Expand Down
58 changes: 58 additions & 0 deletions packages/core/test/integrations/debugsymbolicator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
parseErrorStack,
symbolicateStackTrace,
} from '../../src/js/integrations/debugsymbolicatorutils';
import * as ErrorUtils from '../../src/js/utils/error';
import type * as ReactNative from '../../src/js/vendor/react-native';

async function processEvent(mockedEvent: Event, mockedHint: EventHint): Promise<Event | null> {
Expand Down Expand Up @@ -365,6 +366,63 @@ describe('Debug Symbolicator Integration', () => {
});
});

it('skips metro events created by Sentry console integration', async () => {
const spy = jest.spyOn(ErrorUtils, 'isErrorLike').mockImplementation(() => {
throw new Error('only first if condition should be called');
});

const symbolicatedEvent = await processEvent(
{
exception: {
values: [],
},
extra: {
arguments: [false, "'this' is expected an Event object, but got", '<object>'],
},
message: "Assertion failed: 'this' is expected an Event object, but got",
},
{},
);

expect(symbolicatedEvent).toStrictEqual(<Event>{
exception: {
values: [],
},
extra: {
arguments: [false, "'this' is expected an Event object, but got", '<object>'],
},
message: "Assertion failed: 'this' is expected an Event object, but got",
});

// This is the second if condition after the tested element, it is here to make sure the returned code
// came from the console filter.
expect(spy).not.toHaveBeenCalled();
spy.mockRestore();
});

it('do not skip events similar to metro events created by Sentry console integration', async () => {
const spy = jest.spyOn(ErrorUtils, 'isErrorLike').mockImplementation(() => {
throw new Error('second if condition called.');
});

try {
await processEvent(
{
exception: {
values: [],
},
message: "Assertion failed: 'this' is expected an Event object, but got",
},
{},
);
} catch (err: Error | unknown) {
expect((err as Error).message).toBe('second if condition called.');
} finally {
expect(spy).toHaveBeenCalled();
spy.mockRestore();
}
});

it('should symbolicate error with cause ', async () => {
(parseErrorStack as jest.Mock)
.mockReturnValueOnce(<Array<ReactNative.StackFrame>>[
Expand Down
2 changes: 2 additions & 0 deletions samples/react-native/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Animated, {

// Import the Sentry React Native SDK
import * as Sentry from '@sentry/react-native';
import * as SentryCore from '@sentry/core';
import { FeedbackWidget } from '@sentry/react-native';

import ErrorsScreen from './Screens/ErrorsScreen';
Expand Down Expand Up @@ -81,6 +82,7 @@ Sentry.init({
integrations(integrations) {
integrations.push(
reactNavigationIntegration,
SentryCore.captureConsoleIntegration(),
Sentry.reactNativeTracingIntegration({
// The time to wait in ms until the transaction will be finished, For testing, default is 1000 ms
idleTimeoutMs: 5_000,
Expand Down
20 changes: 20 additions & 0 deletions samples/react-native/src/Screens/ErrorsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,26 @@ const ErrorsScreen = (_props: Props) => {
/>
) : null}
<View style={styles.mainViewBottomWhiteSpace} />
<Button
title="Log console assert"
onPress={() => {
// Sample code from https://gitlab.cin.ufpe.br/vrs2/iot-trafficlight-final/-/blob/main/node_modules/event-target-shim/dist/event-target-shim.mjs?ref_type=heads#L40
// This code is used by Metro and the assert is triggered when interacting with Debug Symbolicator integration.
// When using consoleintegration and filtering assert, it could trigger an infinite loop where debug symbolicator
// triggers the console integration, then it creates an event, that again calls debug symbolicator and the loop restart.
function pd(event: string) {
// const retv = privateData.get(event);
const retv = null;
console.assert(
retv != null,
"'this' is expected an Event object, but got",
event
);
return retv;
}
pd('<object>');
}}
/>
</ScrollView>
</>
);
Expand Down
Loading