Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
## Unreleased

### Features

- added `logsOrigin` to Sentry Options ([#5354](https://github.com/getsentry/sentry-react-native/pull/5354))
Now you can choose which logs will be captured: 'native' for only Logs that comes from the Native code, 'js' for logs that only comes from the JavaScript layer, or 'all' for both layers. This will only have any impact if `enableLogs` is set to `true`.
By default it is set to `all` so it behaves the same before this change.


### Fixes

- Android SDK not being disabled when `options.enabled` is set to `false` ([#5334](https://github.com/getsentry/sentry-react-native/pull/5334))
Expand Down
Binary file modified packages/core/android/libs/replay-stubs.jar
Binary file not shown.
10 changes: 10 additions & 0 deletions packages/core/src/js/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ export class ReactNativeClient extends Client<ReactNativeClientOptions> {
// We default this to true, as it is the safer scenario
options.parentSpanIsAlwaysRootSpan =
options.parentSpanIsAlwaysRootSpan === undefined ? true : options.parentSpanIsAlwaysRootSpan;

const originalEnableLogs = options.enableLogs;
if (options.enableLogs && options.logsOrigin === 'native') {
debug.log('disabling Sentry logs on JavaScript due to rule set by logsOrigin');
options.enableLogs = false;
}

super(options);

this._outcomesBuffer = [];
Expand All @@ -87,6 +94,9 @@ export class ReactNativeClient extends Client<ReactNativeClientOptions> {
}, DEFAULT_FLUSH_INTERVAL);
});
}

// Restore original settings for enabling Native options.
options.enableLogs = originalEnableLogs;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/js/integrations/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export function getDefaultIntegrations(options: ReactNativeClientOptions): Integ
if (options.enableNative) {
integrations.push(deviceContextIntegration());
integrations.push(modulesLoaderIntegration());
if (options.enableLogs) {
if (options.enableLogs && options.logsOrigin !== 'native') {
integrations.push(logEnricherIntegration());
integrations.push(consoleLoggingIntegration());
}
Expand Down
10 changes: 10 additions & 0 deletions packages/core/src/js/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,16 @@ export interface BaseReactNativeOptions {
* @default false
*/
propagateTraceparent?: boolean;

/**
* Controls which log origin is captured when `enableLogs` is set to true.
* 'all' will log all origins.
* 'js' will capture only JavaScript logs.
* 'native' will capture only native logs.
*
* @default 'all'
*/
logsOrigin?: 'all' | 'js' | 'native';
}

export type SentryReplayQuality = 'low' | 'medium' | 'high';
Expand Down
15 changes: 13 additions & 2 deletions packages/core/src/js/wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,10 @@ export const NATIVE: SentryNativeWrapper = {
enableNative: true,
autoInitializeNativeSdk: true,
...originalOptions,
// Keeps original behavior of enableLogs by not setting it when not defined.
...(originalOptions.enableLogs !== undefined
? { enableLogs: originalOptions.enableLogs && originalOptions.logsOrigin !== 'js' }
: {}),
};

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

// filter out all the options that would crash native.
/* eslint-disable @typescript-eslint/unbound-method,@typescript-eslint/no-unused-vars */
const { beforeSend, beforeBreadcrumb, beforeSendTransaction, integrations, ignoreErrors, ...filteredOptions } =
options;
const {
beforeSend,
beforeBreadcrumb,
beforeSendTransaction,
integrations,
ignoreErrors,
logsOrigin,
...filteredOptions
} = options;
/* eslint-enable @typescript-eslint/unbound-method,@typescript-eslint/no-unused-vars */
const nativeIsReady = await RNSentry.initNativeSdk(filteredOptions);

Expand Down
45 changes: 45 additions & 0 deletions packages/core/test/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
Transport,
TransportMakeRequestResponse,
} from '@sentry/core';
import * as SentryCore from '@sentry/core';
import {
addAutoIpAddressToSession,
addAutoIpAddressToUser,
Expand Down Expand Up @@ -852,6 +853,50 @@ describe('Tests ReactNativeClient', () => {
);
});
});

describe('logger initialization', () => {
afterEach(() => {
jest.useRealTimers();
jest.restoreAllMocks();
});

test('does not flush logs when enableLogs is false', () => {
jest.useFakeTimers();
const flushLogsSpy = jest.spyOn(SentryCore, '_INTERNAL_flushLogsBuffer').mockImplementation(jest.fn());

const { client } = createClientWithSpy({ enableLogs: false });

client.emit('afterCaptureLog', { message: 'test', attributes: {} } as unknown);
jest.advanceTimersByTime(5000);

expect(flushLogsSpy).not.toHaveBeenCalled();
});

test('does not flush logs when logsOrigin is native', () => {
jest.useFakeTimers();
const flushLogsSpy = jest.spyOn(SentryCore, '_INTERNAL_flushLogsBuffer').mockImplementation(jest.fn());

const { client } = createClientWithSpy({ enableLogs: true, logsOrigin: 'native' });

client.emit('afterCaptureLog', { message: 'test', attributes: {} } as unknown);
jest.advanceTimersByTime(5000);

expect(flushLogsSpy).not.toHaveBeenCalled();
});

it.each([['all' as const], ['js' as const]])('flushes logs when logsOrigin is %s', logOrlogsOriginigin => {
jest.useFakeTimers();
const flushLogsSpy = jest.spyOn(SentryCore, '_INTERNAL_flushLogsBuffer').mockImplementation(jest.fn());

const { client } = createClientWithSpy({ enableLogs: true, logsOrigin: logOrlogsOriginigin });

client.emit('afterCaptureLog', { message: 'test', attributes: {} } as unknown);
jest.advanceTimersByTime(5000);

expect(flushLogsSpy).toHaveBeenCalledTimes(1);
expect(flushLogsSpy).toHaveBeenLastCalledWith(client);
});
});
});

function mockedOptions(options: Partial<ReactNativeClientOptions>): ReactNativeClientOptions {
Expand Down
78 changes: 78 additions & 0 deletions packages/core/test/integrations/defaultLogs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { consoleLoggingIntegration } from '@sentry/browser';
import type { Integration } from '@sentry/core';
import { getDefaultIntegrations } from '../../src/js/integrations/default';
import { logEnricherIntegration } from '../../src/js/integrations/logEnricherIntegration';
import type { ReactNativeClientOptions } from '../../src/js/options';
import { notWeb } from '../../src/js/utils/environment';

jest.mock('../../src/js/utils/environment', () => {
const actual = jest.requireActual('../../src/js/utils/environment');
return {
...actual,
notWeb: jest.fn(() => true),
};
});

const logEnricherIntegrationName = logEnricherIntegration().name;
const consoleLoggingIntegrationName = consoleLoggingIntegration().name;

describe('getDefaultIntegrations - logging integrations', () => {
beforeEach(() => {
(notWeb as jest.Mock).mockReturnValue(true);
});

const createOptions = (overrides: Partial<ReactNativeClientOptions>): ReactNativeClientOptions => {
return {
dsn: 'https://example.com/1',
enableNative: true,
...overrides,
} as ReactNativeClientOptions;
};

const getIntegrationNames = (options: ReactNativeClientOptions): string[] => {
const integrations = getDefaultIntegrations(options);
return integrations.map((integration: Integration) => integration.name);
};

it('does not add logging integrations when enableLogs is falsy', () => {
const names = getIntegrationNames(createOptions({ enableLogs: false }));

expect(names).not.toContain(logEnricherIntegrationName);
expect(names).not.toContain(consoleLoggingIntegrationName);
});

it('adds logging integrations when enableLogs is true and logsOrigin is not native', () => {
const names = getIntegrationNames(createOptions({ enableLogs: true }));

expect(names).toContain(logEnricherIntegrationName);
expect(names).toContain(consoleLoggingIntegrationName);
});

it('does not add logging integrations when logsOrigin is native', () => {
const names = getIntegrationNames(
createOptions({
enableLogs: true,
logsOrigin: 'native' as unknown as ReactNativeClientOptions['logsOrigin'],
}),
);

expect(names).not.toContain(logEnricherIntegrationName);
expect(names).not.toContain(consoleLoggingIntegrationName);
});

it.each([
['all', true],
['js', true],
['native', false],
])('handles logsOrigin %s correctly', (logsOrigin, shouldInclude) => {
const names = getIntegrationNames(
createOptions({
enableLogs: true,
logsOrigin: logsOrigin as unknown as ReactNativeClientOptions['logsOrigin'],
}),
);

expect(names.includes(logEnricherIntegrationName)).toBe(shouldInclude);
expect(names.includes(consoleLoggingIntegrationName)).toBe(shouldInclude);
});
});
38 changes: 38 additions & 0 deletions packages/core/test/wrapper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,44 @@ describe('Tests Native Wrapper', () => {
expect(initParameter.ignoreErrorsStr).toBeUndefined();
expect(initParameter.ignoreErrorsRegex).toBeUndefined();
});

test('does not set enableLogs when option is undefined', async () => {
await NATIVE.initNativeSdk({
dsn: 'test',
enableNative: true,
autoInitializeNativeSdk: true,
devServerUrl: undefined,
defaultSidecarUrl: undefined,
mobileReplayOptions: undefined,
});

expect(RNSentry.initNativeSdk).toHaveBeenCalled();
const initParameter = (RNSentry.initNativeSdk as jest.MockedFunction<any>).mock.calls[0][0];
expect(initParameter.enableLogs).toBeUndefined();
});

it.each([
['without logsOrigin', undefined, true],
['with logsOrigin set to Native', 'native' as const, true],
['with logsOrigin set to all', 'all' as const, true],
['with logsOrigin set to JS', 'js' as const, false],
])('handles enableLogs %s', async (_description, logsOrigin, expectedEnableLogs) => {
await NATIVE.initNativeSdk({
dsn: 'test',
enableNative: true,
autoInitializeNativeSdk: true,
enableLogs: true,
...(logsOrigin !== undefined ? { logsOrigin } : {}),
devServerUrl: undefined,
defaultSidecarUrl: undefined,
mobileReplayOptions: undefined,
});

expect(RNSentry.initNativeSdk).toHaveBeenCalled();
const initParameter = (RNSentry.initNativeSdk as jest.MockedFunction<any>).mock.calls[0][0];
expect(initParameter.enableLogs).toBe(expectedEnableLogs);
expect(initParameter.logsOrigin).toBeUndefined();
});
});

describe('sendEnvelope', () => {
Expand Down
1 change: 1 addition & 0 deletions samples/react-native/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ Sentry.init({
_experiments: {
enableUnhandledCPPExceptionsV2: true,
},
logsOrigin: 'all',
enableLogs: true,
beforeSendLog: (log) => {
return log;
Expand Down
Loading