From 1e36892b5bf5c9af7dd65f14dec4fcdd77129178 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Mon, 5 Aug 2024 09:52:29 +0200 Subject: [PATCH 1/3] fix(client): Keep `captureMessage` stack trace in `exception` --- CHANGELOG.md | 2 ++ src/js/client.ts | 19 +------------------ test/client.test.ts | 7 ++++--- 3 files changed, 7 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd1647752d..09a1a45577 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ - Pass `sampleRate` option to the Android SDK ([#3979](https://github.com/getsentry/sentry-react-native/pull/3979)) - Drop app start data older than one minute ([#3974](https://github.com/getsentry/sentry-react-native/pull/3974)) - Use `Platform.constants.reactNativeVersion` instead of `react-native` internal export ([#3949](https://github.com/getsentry/sentry-react-native/pull/3949)) +- `Sentry.captureMessage` stack trace is in `event.exception` (moved from `event.threads`) ([#3635](https://github.com/getsentry/sentry-react-native/pull/3635), [#3988](https://github.com/getsentry/sentry-react-native/pull/3988)) + - To revert to the old behavior (causing the stack to be unsymbolicated) use `useThreadsForMessageStack` option ### Dependencies diff --git a/src/js/client.ts b/src/js/client.ts index cc45b15e8f..84c887991e 100644 --- a/src/js/client.ts +++ b/src/js/client.ts @@ -6,10 +6,8 @@ import type { Envelope, Event, EventHint, - Exception, Outcome, SeverityLevel, - Thread, UserFeedback, } from '@sentry/types'; import { dateTimestampInSeconds, logger, SentryError } from '@sentry/utils'; @@ -59,22 +57,7 @@ export class ReactNativeClient extends BaseClient { * @inheritDoc */ public eventFromMessage(message: string, level?: SeverityLevel, hint?: EventHint): PromiseLike { - return eventFromMessage(this._options.stackParser, message, level, hint, this._options.attachStacktrace).then( - (event: Event) => { - // TMP! Remove this function once JS SDK uses threads for messages - if (!event.exception?.values || event.exception.values.length <= 0) { - return event; - } - const values = event.exception.values.map( - (exception: Exception): Thread => ({ - stacktrace: exception.stacktrace, - }), - ); - (event as { threads?: { values: Thread[] } }).threads = { values }; - delete event.exception; - return event; - }, - ); + return eventFromMessage(this._options.stackParser, message, level, hint, this._options.attachStacktrace); } /** diff --git a/test/client.test.ts b/test/client.test.ts index 2cd761f78d..7a593a30d2 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -368,11 +368,12 @@ describe('Tests ReactNativeClient', () => { const getMessageEventFrom = (func: jest.Mock) => func.mock.calls[0][firstArg][envelopeItems][0][envelopeItemPayload]; - test('captureMessage contains stack trace in threads', async () => { + test('captureMessage contains stack trace in exception', async () => { const mockSyntheticExceptionFromHub = new Error(); client.captureMessage('test message', 'error', { syntheticException: mockSyntheticExceptionFromHub }); - expect(getMessageEventFrom(mockTransportSend).threads.values.length).toBeGreaterThan(0); - expect(getMessageEventFrom(mockTransportSend).exception).toBeUndefined(); + expect(getMessageEventFrom(mockTransportSend).exception.values.length).toBeGreaterThan(0); + expect(getMessageEventFrom(mockTransportSend).exception).toBeDefined(); + expect(getMessageEventFrom(mockTransportSend).threads).toBeUndefined(); }); }); From 9afb6bc40a84e28746b90608892bc170140f2ae9 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Mon, 5 Aug 2024 09:59:57 +0200 Subject: [PATCH 2/3] Add tests --- src/js/client.ts | 21 +++++++++++++++++++++ src/js/options.ts | 8 ++++++++ test/client.test.ts | 38 +++++++++++++++++++++++++++++--------- 3 files changed, 58 insertions(+), 9 deletions(-) diff --git a/src/js/client.ts b/src/js/client.ts index 84c887991e..e22eceebd6 100644 --- a/src/js/client.ts +++ b/src/js/client.ts @@ -6,8 +6,10 @@ import type { Envelope, Event, EventHint, + Exception, Outcome, SeverityLevel, + Thread, UserFeedback, } from '@sentry/types'; import { dateTimestampInSeconds, logger, SentryError } from '@sentry/utils'; @@ -57,6 +59,25 @@ export class ReactNativeClient extends BaseClient { * @inheritDoc */ public eventFromMessage(message: string, level?: SeverityLevel, hint?: EventHint): PromiseLike { + if (this._options.useThreadsForMessageStack) { + return eventFromMessage(this._options.stackParser, message, level, hint, this._options.attachStacktrace).then( + (event: Event) => { + // TMP! Remove this function once JS SDK uses threads for messages + if (!event.exception?.values || event.exception.values.length <= 0) { + return event; + } + const values = event.exception.values.map( + (exception: Exception): Thread => ({ + stacktrace: exception.stacktrace, + }), + ); + (event as { threads?: { values: Thread[] } }).threads = { values }; + delete event.exception; + return event; + }, + ); + } + return eventFromMessage(this._options.stackParser, message, level, hint, this._options.attachStacktrace); } diff --git a/src/js/options.ts b/src/js/options.ts index 0c5a4baa43..d798b42204 100644 --- a/src/js/options.ts +++ b/src/js/options.ts @@ -213,6 +213,14 @@ export interface BaseReactNativeOptions { */ replaysOnErrorSampleRate?: number; }; + + /** + * This options changes the placement of the attached stacktrace of `captureMessage` in the event. + * + * @default false + * @deprecated This option will be removed in the next major version. Use `beforeSend` instead. + */ + useThreadsForMessageStack?: boolean; } export interface ReactNativeTransportOptions extends BrowserTransportOptions { diff --git a/test/client.test.ts b/test/client.test.ts index 7a593a30d2..d6eafc1567 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -349,6 +349,16 @@ describe('Tests ReactNativeClient', () => { beforeEach(() => { mockTransportSend = jest.fn(() => Promise.resolve()); + }); + + afterEach(() => { + mockTransportSend.mockClear(); + }); + + const getMessageEventFrom = (func: jest.Mock) => + func.mock.calls[0][firstArg][envelopeItems][0][envelopeItemPayload]; + + test('captureMessage contains stack trace in exception', async () => { client = new ReactNativeClient({ ...DEFAULT_OPTIONS, attachStacktrace: true, @@ -359,22 +369,32 @@ describe('Tests ReactNativeClient', () => { flush: jest.fn(), }), } as ReactNativeClientOptions); - }); - - afterEach(() => { - mockTransportSend.mockClear(); - }); - - const getMessageEventFrom = (func: jest.Mock) => - func.mock.calls[0][firstArg][envelopeItems][0][envelopeItemPayload]; - test('captureMessage contains stack trace in exception', async () => { const mockSyntheticExceptionFromHub = new Error(); client.captureMessage('test message', 'error', { syntheticException: mockSyntheticExceptionFromHub }); expect(getMessageEventFrom(mockTransportSend).exception.values.length).toBeGreaterThan(0); expect(getMessageEventFrom(mockTransportSend).exception).toBeDefined(); expect(getMessageEventFrom(mockTransportSend).threads).toBeUndefined(); }); + + test('captureMessage contains stack trace in exception', async () => { + client = new ReactNativeClient({ + ...DEFAULT_OPTIONS, + attachStacktrace: true, + stackParser: defaultStackParser, + dsn: EXAMPLE_DSN, + transport: () => ({ + send: mockTransportSend, + flush: jest.fn(), + }), + useThreadsForMessageStack: true, + } as ReactNativeClientOptions); + + const mockSyntheticExceptionFromHub = new Error(); + client.captureMessage('test message', 'error', { syntheticException: mockSyntheticExceptionFromHub }); + expect(getMessageEventFrom(mockTransportSend).threads.values.length).toBeGreaterThan(0); + expect(getMessageEventFrom(mockTransportSend).exception).toBeUndefined(); + }); }); describe('envelopeHeader SdkInfo', () => { From 8b65e2643b024e489157683998011326133c6be5 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich <31292499+krystofwoldrich@users.noreply.github.com> Date: Tue, 6 Aug 2024 11:49:27 +0200 Subject: [PATCH 3/3] Update CHANGELOG.md --- CHANGELOG.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebe0fd04e1..c0a5b995db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## Unreleased + +### Fixes + +- `Sentry.captureMessage` stack trace is in `event.exception` (moved from `event.threads`) ([#3635](https://github.com/getsentry/sentry-react-native/pull/3635), [#3988](https://github.com/getsentry/sentry-react-native/pull/3988)) + - To revert to the old behavior (causing the stack to be unsymbolicated) use `useThreadsForMessageStack` option + ## 5.27.0 ### Fixes @@ -7,8 +14,6 @@ - Pass `sampleRate` option to the Android SDK ([#3979](https://github.com/getsentry/sentry-react-native/pull/3979)) - Drop app start data older than one minute ([#3974](https://github.com/getsentry/sentry-react-native/pull/3974)) - Use `Platform.constants.reactNativeVersion` instead of `react-native` internal export ([#3949](https://github.com/getsentry/sentry-react-native/pull/3949)) -- `Sentry.captureMessage` stack trace is in `event.exception` (moved from `event.threads`) ([#3635](https://github.com/getsentry/sentry-react-native/pull/3635), [#3988](https://github.com/getsentry/sentry-react-native/pull/3988)) - - To revert to the old behavior (causing the stack to be unsymbolicated) use `useThreadsForMessageStack` option ### Dependencies