diff --git a/CHANGELOG.md b/CHANGELOG.md index fc634f92b4..2bd5e5e5f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ > make sure you follow our [migration guide](https://docs.sentry.io/platforms/react-native/migration/) first. +## Unreleased + +### Breaking Changes + +- `enableLogs` defaults to `true` ([#6084](https://github.com/getsentry/sentry-react-native/pull/6084)) +- `consoleLoggingIntegration` is no longer added by default. To forward `console.*` calls to Sentry logs, add it explicitly: `integrations: [Sentry.consoleLoggingIntegration()]` ([#6084](https://github.com/getsentry/sentry-react-native/pull/6084)) + ## 8.10.0 ### Features diff --git a/packages/core/src/js/client.ts b/packages/core/src/js/client.ts index 894fab9bf3..54cbca24fc 100644 --- a/packages/core/src/js/client.ts +++ b/packages/core/src/js/client.ts @@ -70,6 +70,9 @@ export class ReactNativeClient extends Client { options.parentSpanIsAlwaysRootSpan = options.parentSpanIsAlwaysRootSpan === undefined ? true : options.parentSpanIsAlwaysRootSpan; + // Logs are opt-out now: calling `Sentry.logger.*` is the user's opt-in. + options.enableLogs = options.enableLogs ?? options._experiments?.enableLogs ?? true; + // enableLogs must be disabled before calling super() to avoid logs being captured. // This makes a copy of the user defined value, so we can restore it later for the native usaege. const originalEnableLogs = options.enableLogs; diff --git a/packages/core/src/js/integrations/default.ts b/packages/core/src/js/integrations/default.ts index 30dd3a156e..27156d46af 100644 --- a/packages/core/src/js/integrations/default.ts +++ b/packages/core/src/js/integrations/default.ts @@ -1,7 +1,7 @@ /* oxlint-disable eslint(complexity) */ import type { Integration } from '@sentry/core'; -import { browserSessionIntegration, consoleLoggingIntegration } from '@sentry/browser'; +import { browserSessionIntegration } from '@sentry/browser'; import type { ReactNativeClientOptions } from '../options'; @@ -92,7 +92,6 @@ export function getDefaultIntegrations(options: ReactNativeClientOptions): Integ integrations.push(modulesLoaderIntegration()); if (options.enableLogs && options.logsOrigin !== 'native') { integrations.push(logEnricherIntegration()); - integrations.push(consoleLoggingIntegration()); } if (options.attachScreenshot) { integrations.push(screenshotIntegration()); diff --git a/packages/core/src/js/options.ts b/packages/core/src/js/options.ts index 4dc92f7bdd..9c236d8103 100644 --- a/packages/core/src/js/options.ts +++ b/packages/core/src/js/options.ts @@ -406,6 +406,13 @@ export interface BaseReactNativeOptions { */ propagateTraceparent?: boolean; + /** + * Acts as the kill switch for Sentry logs. When set to `false`, no logs are sent to Sentry. + * + * @default true + */ + enableLogs?: boolean; + /** * Controls which log origin is captured when `enableLogs` is set to true. * 'all' will log all origins. diff --git a/packages/core/src/js/sdk.tsx b/packages/core/src/js/sdk.tsx index 6fffe6d66f..19154272ca 100644 --- a/packages/core/src/js/sdk.tsx +++ b/packages/core/src/js/sdk.tsx @@ -164,6 +164,8 @@ export function init(passedOptions: ReactNativeOptions): void { options.environment = getDefaultEnvironment(); } + options.enableLogs = options.enableLogs ?? options._experiments?.enableLogs ?? true; + const defaultIntegrations: false | Integration[] = userOptions.defaultIntegrations === undefined ? getDefaultIntegrations(options) : userOptions.defaultIntegrations; diff --git a/packages/core/test/client.test.ts b/packages/core/test/client.test.ts index c9b59c6d95..37ab57e0b4 100644 --- a/packages/core/test/client.test.ts +++ b/packages/core/test/client.test.ts @@ -900,6 +900,32 @@ describe('Tests ReactNativeClient', () => { expect(flushLogsSpy).toHaveBeenCalledTimes(1); expect(flushLogsSpy).toHaveBeenLastCalledWith(client); }); + + test('defaults enableLogs to true when not provided', () => { + jest.useFakeTimers(); + const flushLogsSpy = jest.spyOn(SentryCore, '_INTERNAL_flushLogsBuffer').mockImplementation(jest.fn()); + + const { client } = createClientWithSpy({}); + + expect(client.getOptions().enableLogs).toBe(true); + + client.emit('afterCaptureLog', { message: 'test', attributes: {} } as unknown); + jest.advanceTimersByTime(5000); + + expect(flushLogsSpy).toHaveBeenCalledTimes(1); + }); + + test('respects user-provided enableLogs: false over the default', () => { + const { client } = createClientWithSpy({ enableLogs: false }); + expect(client.getOptions().enableLogs).toBe(false); + }); + + test('backfills enableLogs from _experiments.enableLogs when top-level is undefined', () => { + const { client } = createClientWithSpy({ + _experiments: { enableLogs: false }, + } as Partial); + expect(client.getOptions().enableLogs).toBe(false); + }); }); }); diff --git a/packages/core/test/integrations/defaultLogs.test.ts b/packages/core/test/integrations/defaultLogs.test.ts index 23c2ad552b..0914ef9a4f 100644 --- a/packages/core/test/integrations/defaultLogs.test.ts +++ b/packages/core/test/integrations/defaultLogs.test.ts @@ -37,21 +37,26 @@ describe('getDefaultIntegrations - logging integrations', () => { return integrations.map((integration: Integration) => integration.name); }; - it('does not add logging integrations when enableLogs is falsy', () => { + it('does not add logEnricher when enableLogs is false', () => { 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', () => { + it('adds logEnricher 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', () => { + it('never adds consoleLoggingIntegration by default — it must be opt-in', () => { + const names = getIntegrationNames(createOptions({ enableLogs: true })); + + expect(names).not.toContain(consoleLoggingIntegrationName); + }); + + it('does not add logEnricher when logsOrigin is native', () => { const names = getIntegrationNames( createOptions({ enableLogs: true, @@ -76,6 +81,7 @@ describe('getDefaultIntegrations - logging integrations', () => { ); expect(names.includes(logEnricherIntegrationName)).toBe(shouldInclude); - expect(names.includes(consoleLoggingIntegrationName)).toBe(shouldInclude); + // consoleLoggingIntegration is always opt-in regardless of logsOrigin + expect(names).not.toContain(consoleLoggingIntegrationName); }); }); diff --git a/packages/core/test/sdk.test.ts b/packages/core/test/sdk.test.ts index eeb36a76c9..263ea81872 100644 --- a/packages/core/test/sdk.test.ts +++ b/packages/core/test/sdk.test.ts @@ -1296,6 +1296,35 @@ describe('Tests the SDK functionality', () => { }), ); }); + + describe('enableLogs default', () => { + it('defaults enableLogs to true when not provided', () => { + init({}); + expect(usedOptions()?.enableLogs).toBe(true); + }); + + it('registers logEnricherIntegration in default integrations when enableLogs defaults to true', () => { + init({}); + expectIntegration('LogEnricher'); + }); + + it('does not register consoleLoggingIntegration by default — it must be opt-in', () => { + init({}); + expectNotIntegration('ConsoleLogs'); + }); + + it('respects user-provided enableLogs: false', () => { + init({ enableLogs: false }); + expect(usedOptions()?.enableLogs).toBe(false); + expectNotIntegration('LogEnricher'); + }); + + it('backfills enableLogs from _experiments.enableLogs when top-level is undefined', () => { + init({ _experiments: { enableLogs: false } }); + expect(usedOptions()?.enableLogs).toBe(false); + expectNotIntegration('LogEnricher'); + }); + }); }); function expectIntegration(name: string): void {