Skip to content

Commit 46e3d54

Browse files
feat: logsOrigin implementation (#5354)
* loggerOrigin implementation * JS tests * fix JS disabled also disabling native, lint fixes, test fixes * rename feat to logsOrigin * Update CHANGELOG.md Co-authored-by: Antonis Lilis <antonis.lilis@sentry.io> * restore replay-stubs from main, added comments to client.ts * changelog sync --------- Co-authored-by: Antonis Lilis <antonis.lilis@sentry.io>
1 parent 128ee72 commit 46e3d54

File tree

9 files changed

+207
-3
lines changed

9 files changed

+207
-3
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,17 @@
66
> make sure you follow our [migration guide](https://docs.sentry.io/platforms/react-native/migration/) first.
77
<!-- prettier-ignore-end -->
88
9+
## Unreleased
10+
11+
### Features
12+
13+
- Added `logsOrigin` to Sentry Options ([#5354](https://github.com/getsentry/sentry-react-native/pull/5354))
14+
- You can now choose which logs are captured: 'native' for logs from native code only, 'js' for logs from the JavaScript layer only, or 'all' for both layers.
15+
- Takes effect only if `enableLogs` is `true` and defaults to 'all', preserving previous behavior.
16+
917
## 7.6.0
1018

19+
1120
### Fixes
1221

1322
- Android SDK not being disabled when `options.enabled` is set to `false` ([#5334](https://github.com/getsentry/sentry-react-native/pull/5334))

packages/core/src/js/client.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,15 @@ export class ReactNativeClient extends Client<ReactNativeClientOptions> {
6464
// We default this to true, as it is the safer scenario
6565
options.parentSpanIsAlwaysRootSpan =
6666
options.parentSpanIsAlwaysRootSpan === undefined ? true : options.parentSpanIsAlwaysRootSpan;
67+
68+
// enableLogs must be disabled before calling super() to avoid logs being captured.
69+
// This makes a copy of the user defined value, so we can restore it later for the native usaege.
70+
const originalEnableLogs = options.enableLogs;
71+
if (options.enableLogs && options.logsOrigin === 'native') {
72+
debug.log('disabling Sentry logs on JavaScript due to rule set by logsOrigin');
73+
options.enableLogs = false;
74+
}
75+
6776
super(options);
6877

6978
this._outcomesBuffer = [];
@@ -87,6 +96,9 @@ export class ReactNativeClient extends Client<ReactNativeClientOptions> {
8796
}, DEFAULT_FLUSH_INTERVAL);
8897
});
8998
}
99+
100+
// Restore original settings for enabling Native options.
101+
options.enableLogs = originalEnableLogs;
90102
}
91103

92104
/**

packages/core/src/js/integrations/default.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export function getDefaultIntegrations(options: ReactNativeClientOptions): Integ
8585
if (options.enableNative) {
8686
integrations.push(deviceContextIntegration());
8787
integrations.push(modulesLoaderIntegration());
88-
if (options.enableLogs) {
88+
if (options.enableLogs && options.logsOrigin !== 'native') {
8989
integrations.push(logEnricherIntegration());
9090
integrations.push(consoleLoggingIntegration());
9191
}

packages/core/src/js/options.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,16 @@ export interface BaseReactNativeOptions {
316316
* @default false
317317
*/
318318
propagateTraceparent?: boolean;
319+
320+
/**
321+
* Controls which log origin is captured when `enableLogs` is set to true.
322+
* 'all' will log all origins.
323+
* 'js' will capture only JavaScript logs.
324+
* 'native' will capture only native logs.
325+
*
326+
* @default 'all'
327+
*/
328+
logsOrigin?: 'all' | 'js' | 'native';
319329
}
320330

321331
export type SentryReplayQuality = 'low' | 'medium' | 'high';

packages/core/src/js/wrapper.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,10 @@ export const NATIVE: SentryNativeWrapper = {
228228
enableNative: true,
229229
autoInitializeNativeSdk: true,
230230
...originalOptions,
231+
// Keeps original behavior of enableLogs by not setting it when not defined.
232+
...(originalOptions.enableLogs !== undefined
233+
? { enableLogs: originalOptions.enableLogs && originalOptions.logsOrigin !== 'js' }
234+
: {}),
231235
};
232236

233237
if (!options.enableNative) {
@@ -273,8 +277,15 @@ export const NATIVE: SentryNativeWrapper = {
273277

274278
// filter out all the options that would crash native.
275279
/* eslint-disable @typescript-eslint/unbound-method,@typescript-eslint/no-unused-vars */
276-
const { beforeSend, beforeBreadcrumb, beforeSendTransaction, integrations, ignoreErrors, ...filteredOptions } =
277-
options;
280+
const {
281+
beforeSend,
282+
beforeBreadcrumb,
283+
beforeSendTransaction,
284+
integrations,
285+
ignoreErrors,
286+
logsOrigin,
287+
...filteredOptions
288+
} = options;
278289
/* eslint-enable @typescript-eslint/unbound-method,@typescript-eslint/no-unused-vars */
279290
const nativeIsReady = await RNSentry.initNativeSdk(filteredOptions);
280291

packages/core/test/client.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {
77
Transport,
88
TransportMakeRequestResponse,
99
} from '@sentry/core';
10+
import * as SentryCore from '@sentry/core';
1011
import {
1112
addAutoIpAddressToSession,
1213
addAutoIpAddressToUser,
@@ -852,6 +853,50 @@ describe('Tests ReactNativeClient', () => {
852853
);
853854
});
854855
});
856+
857+
describe('logger initialization', () => {
858+
afterEach(() => {
859+
jest.useRealTimers();
860+
jest.restoreAllMocks();
861+
});
862+
863+
test('does not flush logs when enableLogs is false', () => {
864+
jest.useFakeTimers();
865+
const flushLogsSpy = jest.spyOn(SentryCore, '_INTERNAL_flushLogsBuffer').mockImplementation(jest.fn());
866+
867+
const { client } = createClientWithSpy({ enableLogs: false });
868+
869+
client.emit('afterCaptureLog', { message: 'test', attributes: {} } as unknown);
870+
jest.advanceTimersByTime(5000);
871+
872+
expect(flushLogsSpy).not.toHaveBeenCalled();
873+
});
874+
875+
test('does not flush logs when logsOrigin is native', () => {
876+
jest.useFakeTimers();
877+
const flushLogsSpy = jest.spyOn(SentryCore, '_INTERNAL_flushLogsBuffer').mockImplementation(jest.fn());
878+
879+
const { client } = createClientWithSpy({ enableLogs: true, logsOrigin: 'native' });
880+
881+
client.emit('afterCaptureLog', { message: 'test', attributes: {} } as unknown);
882+
jest.advanceTimersByTime(5000);
883+
884+
expect(flushLogsSpy).not.toHaveBeenCalled();
885+
});
886+
887+
it.each([['all' as const], ['js' as const]])('flushes logs when logsOrigin is %s', logOrlogsOriginigin => {
888+
jest.useFakeTimers();
889+
const flushLogsSpy = jest.spyOn(SentryCore, '_INTERNAL_flushLogsBuffer').mockImplementation(jest.fn());
890+
891+
const { client } = createClientWithSpy({ enableLogs: true, logsOrigin: logOrlogsOriginigin });
892+
893+
client.emit('afterCaptureLog', { message: 'test', attributes: {} } as unknown);
894+
jest.advanceTimersByTime(5000);
895+
896+
expect(flushLogsSpy).toHaveBeenCalledTimes(1);
897+
expect(flushLogsSpy).toHaveBeenLastCalledWith(client);
898+
});
899+
});
855900
});
856901

857902
function mockedOptions(options: Partial<ReactNativeClientOptions>): ReactNativeClientOptions {
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { consoleLoggingIntegration } from '@sentry/browser';
2+
import type { Integration } from '@sentry/core';
3+
import { getDefaultIntegrations } from '../../src/js/integrations/default';
4+
import { logEnricherIntegration } from '../../src/js/integrations/logEnricherIntegration';
5+
import type { ReactNativeClientOptions } from '../../src/js/options';
6+
import { notWeb } from '../../src/js/utils/environment';
7+
8+
jest.mock('../../src/js/utils/environment', () => {
9+
const actual = jest.requireActual('../../src/js/utils/environment');
10+
return {
11+
...actual,
12+
notWeb: jest.fn(() => true),
13+
};
14+
});
15+
16+
const logEnricherIntegrationName = logEnricherIntegration().name;
17+
const consoleLoggingIntegrationName = consoleLoggingIntegration().name;
18+
19+
describe('getDefaultIntegrations - logging integrations', () => {
20+
beforeEach(() => {
21+
(notWeb as jest.Mock).mockReturnValue(true);
22+
});
23+
24+
const createOptions = (overrides: Partial<ReactNativeClientOptions>): ReactNativeClientOptions => {
25+
return {
26+
dsn: 'https://example.com/1',
27+
enableNative: true,
28+
...overrides,
29+
} as ReactNativeClientOptions;
30+
};
31+
32+
const getIntegrationNames = (options: ReactNativeClientOptions): string[] => {
33+
const integrations = getDefaultIntegrations(options);
34+
return integrations.map((integration: Integration) => integration.name);
35+
};
36+
37+
it('does not add logging integrations when enableLogs is falsy', () => {
38+
const names = getIntegrationNames(createOptions({ enableLogs: false }));
39+
40+
expect(names).not.toContain(logEnricherIntegrationName);
41+
expect(names).not.toContain(consoleLoggingIntegrationName);
42+
});
43+
44+
it('adds logging integrations when enableLogs is true and logsOrigin is not native', () => {
45+
const names = getIntegrationNames(createOptions({ enableLogs: true }));
46+
47+
expect(names).toContain(logEnricherIntegrationName);
48+
expect(names).toContain(consoleLoggingIntegrationName);
49+
});
50+
51+
it('does not add logging integrations when logsOrigin is native', () => {
52+
const names = getIntegrationNames(
53+
createOptions({
54+
enableLogs: true,
55+
logsOrigin: 'native' as unknown as ReactNativeClientOptions['logsOrigin'],
56+
}),
57+
);
58+
59+
expect(names).not.toContain(logEnricherIntegrationName);
60+
expect(names).not.toContain(consoleLoggingIntegrationName);
61+
});
62+
63+
it.each([
64+
['all', true],
65+
['js', true],
66+
['native', false],
67+
])('handles logsOrigin %s correctly', (logsOrigin, shouldInclude) => {
68+
const names = getIntegrationNames(
69+
createOptions({
70+
enableLogs: true,
71+
logsOrigin: logsOrigin as unknown as ReactNativeClientOptions['logsOrigin'],
72+
}),
73+
);
74+
75+
expect(names.includes(logEnricherIntegrationName)).toBe(shouldInclude);
76+
expect(names.includes(consoleLoggingIntegrationName)).toBe(shouldInclude);
77+
});
78+
});

packages/core/test/wrapper.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,44 @@ describe('Tests Native Wrapper', () => {
312312
expect(initParameter.ignoreErrorsStr).toBeUndefined();
313313
expect(initParameter.ignoreErrorsRegex).toBeUndefined();
314314
});
315+
316+
test('does not set enableLogs when option is undefined', async () => {
317+
await NATIVE.initNativeSdk({
318+
dsn: 'test',
319+
enableNative: true,
320+
autoInitializeNativeSdk: true,
321+
devServerUrl: undefined,
322+
defaultSidecarUrl: undefined,
323+
mobileReplayOptions: undefined,
324+
});
325+
326+
expect(RNSentry.initNativeSdk).toHaveBeenCalled();
327+
const initParameter = (RNSentry.initNativeSdk as jest.MockedFunction<any>).mock.calls[0][0];
328+
expect(initParameter.enableLogs).toBeUndefined();
329+
});
330+
331+
it.each([
332+
['without logsOrigin', undefined, true],
333+
['with logsOrigin set to Native', 'native' as const, true],
334+
['with logsOrigin set to all', 'all' as const, true],
335+
['with logsOrigin set to JS', 'js' as const, false],
336+
])('handles enableLogs %s', async (_description, logsOrigin, expectedEnableLogs) => {
337+
await NATIVE.initNativeSdk({
338+
dsn: 'test',
339+
enableNative: true,
340+
autoInitializeNativeSdk: true,
341+
enableLogs: true,
342+
...(logsOrigin !== undefined ? { logsOrigin } : {}),
343+
devServerUrl: undefined,
344+
defaultSidecarUrl: undefined,
345+
mobileReplayOptions: undefined,
346+
});
347+
348+
expect(RNSentry.initNativeSdk).toHaveBeenCalled();
349+
const initParameter = (RNSentry.initNativeSdk as jest.MockedFunction<any>).mock.calls[0][0];
350+
expect(initParameter.enableLogs).toBe(expectedEnableLogs);
351+
expect(initParameter.logsOrigin).toBeUndefined();
352+
});
315353
});
316354

317355
describe('sendEnvelope', () => {

samples/react-native/src/App.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ Sentry.init({
6767
_experiments: {
6868
enableUnhandledCPPExceptionsV2: true,
6969
},
70+
logsOrigin: 'all',
7071
enableLogs: true,
7172
beforeSendLog: (log) => {
7273
return log;

0 commit comments

Comments
 (0)