Skip to content

Commit ad17b3e

Browse files
fix(event): Message event includes current stack trace in threads (#2694)
1 parent f0ee0eb commit ad17b3e

File tree

4 files changed

+84
-33
lines changed

4 files changed

+84
-33
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
- Latest changes from 4.12.0
66

7+
### Breaking changes
8+
9+
- Message event current stack trace moved from exception to threads ([#2694](https://github.com/getsentry/sentry-react-native/pull/2694))
10+
711
## 4.12.0
812

913
### Features

src/js/client.ts

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { BrowserClient, defaultStackParser, makeFetchTransport } from '@sentry/browser';
1+
import { eventFromException, eventFromMessage,makeFetchTransport } from '@sentry/browser';
22
import { FetchImpl } from '@sentry/browser/types/transports/utils';
33
import { BaseClient } from '@sentry/core';
44
import {
@@ -7,8 +7,10 @@ import {
77
Envelope,
88
Event,
99
EventHint,
10+
Exception,
1011
Outcome,
1112
SeverityLevel,
13+
Thread,
1214
Transport,
1315
UserFeedback,
1416
} from '@sentry/types';
@@ -34,26 +36,24 @@ export class ReactNativeClient extends BaseClient<ReactNativeClientOptions> {
3436

3537
private _outcomesBuffer: Outcome[];
3638

37-
private readonly _browserClient: BrowserClient;
38-
3939
/**
4040
* Creates a new React Native SDK instance.
4141
* @param options Configuration options for this SDK.
4242
*/
43-
public constructor(options: ReactNativeClientOptions) {
44-
if (!options.transport) {
45-
options.transport = (options: ReactNativeTransportOptions, nativeFetch?: FetchImpl): Transport => {
46-
if (NATIVE.isNativeTransportAvailable()) {
47-
return makeReactNativeTransport(options);
48-
}
49-
return makeFetchTransport(options, nativeFetch);
50-
};
51-
}
52-
options._metadata = options._metadata || {};
53-
options._metadata.sdk = options._metadata.sdk || defaultSdkInfo;
54-
super(options);
55-
56-
this._outcomesBuffer = [];
43+
public constructor(options: ReactNativeClientOptions) {
44+
if (!options.transport) {
45+
options.transport = (options: ReactNativeTransportOptions, nativeFetch?: FetchImpl): Transport => {
46+
if (NATIVE.isNativeTransportAvailable()) {
47+
return makeReactNativeTransport(options);
48+
}
49+
return makeFetchTransport(options, nativeFetch);
50+
};
51+
}
52+
options._metadata = options._metadata || {};
53+
options._metadata.sdk = options._metadata.sdk || defaultSdkInfo;
54+
super(options);
55+
56+
this._outcomesBuffer = [];
5757

5858
// This is a workaround for now using fetch on RN, this is a known issue in react-native and only generates a warning
5959
// YellowBox deprecated and replaced with with LogBox in RN 0.63
@@ -65,33 +65,45 @@ export class ReactNativeClient extends BaseClient<ReactNativeClientOptions> {
6565
YellowBox.ignoreWarnings(['Require cycle:']);
6666
}
6767

68-
this._browserClient = new BrowserClient({
69-
dsn: options.dsn,
70-
transport: options.transport,
71-
transportOptions: options.transportOptions,
72-
stackParser: options.stackParser || defaultStackParser,
73-
integrations: [],
74-
_metadata: options._metadata,
75-
attachStacktrace: options.attachStacktrace,
76-
});
77-
78-
void this._initNativeSdk();
79-
}
68+
void this._initNativeSdk();
69+
}
8070

8171

8272
/**
8373
* @inheritDoc
8474
*/
8575
public eventFromException(exception: unknown, hint: EventHint = {}): PromiseLike<Event> {
8676
return Screenshot.attachScreenshotToEventHint(hint, this._options)
87-
.then(enrichedHint => this._browserClient.eventFromException(exception, enrichedHint));
77+
.then(hintWithScreenshot => eventFromException(
78+
this._options.stackParser,
79+
exception,
80+
hintWithScreenshot,
81+
this._options.attachStacktrace,
82+
));
8883
}
8984

9085
/**
9186
* @inheritDoc
9287
*/
93-
public eventFromMessage(_message: string, _level?: SeverityLevel, _hint?: EventHint): PromiseLike<Event> {
94-
return this._browserClient.eventFromMessage(_message, _level, _hint);
88+
public eventFromMessage(message: string, level?: SeverityLevel, hint?: EventHint): PromiseLike<Event> {
89+
return eventFromMessage(
90+
this._options.stackParser,
91+
message,
92+
level,
93+
hint,
94+
this._options.attachStacktrace,
95+
).then((event: Event) => {
96+
// TMP! Remove this function once JS SDK uses threads for messages
97+
if (!event.exception?.values || event.exception.values.length <= 0) {
98+
return event;
99+
}
100+
const values = event.exception.values.map((exception: Exception): Thread => ({
101+
stacktrace: exception.stacktrace,
102+
}));
103+
(event as { threads?: { values: Thread[] } }).threads = { values };
104+
delete event.exception;
105+
return event;
106+
});
95107
}
96108

97109
/**

src/js/sdk.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const DEFAULT_OPTIONS: ReactNativeOptions = {
4646
},
4747
sendClientReports: true,
4848
maxQueueSize: DEFAULT_BUFFER_SIZE,
49+
attachStacktrace: true,
4950
};
5051

5152
/**

test/client.test.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { Envelope, Event,Outcome, Transport } from '@sentry/types';
1+
import { defaultStackParser } from '@sentry/browser';
2+
import { Envelope, Event, Outcome, Transport } from '@sentry/types';
23
import { rejectedSyncPromise, SentryError } from '@sentry/utils';
34
import * as RN from 'react-native';
45

@@ -234,6 +235,39 @@ describe('Tests ReactNativeClient', () => {
234235
});
235236
});
236237

238+
describe('attachStacktrace', () => {
239+
let mockTransportSend: jest.Mock;
240+
let client: ReactNativeClient;
241+
242+
beforeEach(() => {
243+
mockTransportSend = jest.fn(() => Promise.resolve());
244+
client = new ReactNativeClient({
245+
...DEFAULT_OPTIONS,
246+
attachStacktrace: true,
247+
stackParser: defaultStackParser,
248+
dsn: EXAMPLE_DSN,
249+
transport: () => ({
250+
send: mockTransportSend,
251+
flush: jest.fn(),
252+
}),
253+
} as ReactNativeClientOptions);
254+
});
255+
256+
afterEach(() => {
257+
mockTransportSend.mockClear();
258+
});
259+
260+
const getMessageEventFrom = (func: jest.Mock) =>
261+
func.mock.calls[0][firstArg][envelopeItems][0][envelopeItemPayload];
262+
263+
test('captureMessage contains stack trace in threads', async () => {
264+
const mockSyntheticExceptionFromHub = new Error();
265+
client.captureMessage('test message', 'error', { syntheticException: mockSyntheticExceptionFromHub });
266+
expect(getMessageEventFrom(mockTransportSend).threads.values.length).toBeGreaterThan(0);
267+
expect(getMessageEventFrom(mockTransportSend).exception).toBeUndefined();
268+
});
269+
});
270+
237271
describe('envelopeHeader SdkInfo', () => {
238272
let mockTransportSend: jest.Mock;
239273
let client: ReactNativeClient;

0 commit comments

Comments
 (0)