From ad22d3872975fd1da62d7b6318c0ecbbd0d217e7 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 24 Jul 2026 15:50:55 +0200 Subject: [PATCH 1/5] feat(core)!: Enable logs by default Flip the `enableLogs` client option to default to `true`, mirroring `enableMetrics`. No logs are emitted unless the logger API is called or a logging integration is explicitly enabled, since no default integration captures logs. Fixes #20639 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../deno-streamed/src/app.ts | 1 - .../test-applications/deno/src/app.ts | 1 - .../effect-3-browser/src/index.js | 1 - .../effect-3-node/src/app.ts | 1 - .../effect-4-browser/src/index.js | 1 - .../effect-4-node/src/app.ts | 1 - .../node-express-streaming/src/app.ts | 1 - .../node-express-v5/src/app.ts | 1 - .../test-applications/node-express/src/app.ts | 1 - .../src/instrument.ts | 1 - .../tsx-express/instrument.mjs | 1 - .../suites/consola/instrument.mjs | 1 - .../suites/pino/instrument-auto-off.mjs | 1 - .../suites/pino/instrument.mjs | 1 - .../suites/public-api/logger/scenario.ts | 1 - .../public-api/logs-disabled/subject.ts | 28 +++++++ .../suites/public-api/logs-disabled/test.ts | 30 ++++++++ .../suites/public-api/logs/subject.ts | 1 - .../suites/winston/instrument.mjs | 1 - packages/core/src/client.ts | 2 +- packages/core/src/logs/internal.ts | 9 ++- packages/core/src/types/options.ts | 2 +- packages/core/test/lib/client.test.ts | 36 ++++----- .../test/lib/integrations/consola.test.ts | 2 - .../test/lib/logs/console-integration.test.ts | 2 - packages/core/test/lib/logs/internal.test.ts | 76 ++++++++----------- packages/deno/test/mod.test.ts | 3 - packages/node/test/sdk/client.test.ts | 7 +- 28 files changed, 115 insertions(+), 99 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/public-api/logs-disabled/subject.ts create mode 100644 dev-packages/node-integration-tests/suites/public-api/logs-disabled/test.ts diff --git a/dev-packages/e2e-tests/test-applications/deno-streamed/src/app.ts b/dev-packages/e2e-tests/test-applications/deno-streamed/src/app.ts index fccf12f5790c..5e8b7d123809 100644 --- a/dev-packages/e2e-tests/test-applications/deno-streamed/src/app.ts +++ b/dev-packages/e2e-tests/test-applications/deno-streamed/src/app.ts @@ -22,7 +22,6 @@ Sentry.init({ traceLifecycle: 'stream', tracesSampleRate: 1, dataCollection: { userInfo: true }, - enableLogs: true, }); const port = 3030; diff --git a/dev-packages/e2e-tests/test-applications/deno/src/app.ts b/dev-packages/e2e-tests/test-applications/deno/src/app.ts index 8dbd8870a3b6..256835014fd1 100644 --- a/dev-packages/e2e-tests/test-applications/deno/src/app.ts +++ b/dev-packages/e2e-tests/test-applications/deno/src/app.ts @@ -24,7 +24,6 @@ Sentry.init({ tunnel: 'http://localhost:3031/', tracesSampleRate: 1, dataCollection: { userInfo: true }, - enableLogs: true, }); const port = 3030; diff --git a/dev-packages/e2e-tests/test-applications/effect-3-browser/src/index.js b/dev-packages/e2e-tests/test-applications/effect-3-browser/src/index.js index 4e9cb70d6e44..d5be4900e256 100644 --- a/dev-packages/e2e-tests/test-applications/effect-3-browser/src/index.js +++ b/dev-packages/e2e-tests/test-applications/effect-3-browser/src/index.js @@ -19,7 +19,6 @@ const AppLayer = Layer.mergeAll( release: 'e2e-test', environment: 'qa', tunnel: 'http://localhost:3031', - enableLogs: true, }), Layer.setTracer(Sentry.SentryEffectTracer), Logger.replace(Logger.defaultLogger, Sentry.SentryEffectLogger), diff --git a/dev-packages/e2e-tests/test-applications/effect-3-node/src/app.ts b/dev-packages/e2e-tests/test-applications/effect-3-node/src/app.ts index 899adfb4aa98..9de243f3cab3 100644 --- a/dev-packages/e2e-tests/test-applications/effect-3-node/src/app.ts +++ b/dev-packages/e2e-tests/test-applications/effect-3-node/src/app.ts @@ -15,7 +15,6 @@ const SentryLive = Layer.mergeAll( debug: !!process.env.DEBUG, tunnel: 'http://localhost:3031/', tracesSampleRate: 1, - enableLogs: true, }), Layer.setTracer(Sentry.SentryEffectTracer), Logger.replace(Logger.defaultLogger, Sentry.SentryEffectLogger), diff --git a/dev-packages/e2e-tests/test-applications/effect-4-browser/src/index.js b/dev-packages/e2e-tests/test-applications/effect-4-browser/src/index.js index 1748b4200ce1..05827a758475 100644 --- a/dev-packages/e2e-tests/test-applications/effect-4-browser/src/index.js +++ b/dev-packages/e2e-tests/test-applications/effect-4-browser/src/index.js @@ -19,7 +19,6 @@ const AppLayer = Layer.mergeAll( release: 'e2e-test', environment: 'qa', tunnel: 'http://localhost:3031', - enableLogs: true, }), Logger.layer([Sentry.SentryEffectLogger]), Layer.succeed(Tracer.Tracer, Sentry.SentryEffectTracer), diff --git a/dev-packages/e2e-tests/test-applications/effect-4-node/src/app.ts b/dev-packages/e2e-tests/test-applications/effect-4-node/src/app.ts index 5ebfef33be77..1109f1a412b9 100644 --- a/dev-packages/e2e-tests/test-applications/effect-4-node/src/app.ts +++ b/dev-packages/e2e-tests/test-applications/effect-4-node/src/app.ts @@ -16,7 +16,6 @@ const SentryLive = Layer.mergeAll( debug: !!process.env.DEBUG, tunnel: 'http://localhost:3031/', tracesSampleRate: 1, - enableLogs: true, }), Logger.layer([Sentry.SentryEffectLogger]), Layer.succeed(Tracer.Tracer, Sentry.SentryEffectTracer), diff --git a/dev-packages/e2e-tests/test-applications/node-express-streaming/src/app.ts b/dev-packages/e2e-tests/test-applications/node-express-streaming/src/app.ts index 5a0d1afa4141..f02a6afff084 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-streaming/src/app.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-streaming/src/app.ts @@ -7,7 +7,6 @@ Sentry.init({ debug: !!process.env.DEBUG, tunnel: `http://localhost:3031/`, // proxy server tracesSampleRate: 1, - enableLogs: true, traceLifecycle: 'stream', integrations: [ Sentry.spanStreamingIntegration(), diff --git a/dev-packages/e2e-tests/test-applications/node-express-v5/src/app.ts b/dev-packages/e2e-tests/test-applications/node-express-v5/src/app.ts index 9a7f6f07d8bc..032ec0415036 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-v5/src/app.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-v5/src/app.ts @@ -13,7 +13,6 @@ Sentry.init({ debug: !!process.env.DEBUG, tunnel: `http://localhost:3031/`, // proxy server tracesSampleRate: 1, - enableLogs: true, integrations: [Sentry.nodeRuntimeMetricsIntegration({ collectionIntervalMs: 1_000 })], }); diff --git a/dev-packages/e2e-tests/test-applications/node-express/src/app.ts b/dev-packages/e2e-tests/test-applications/node-express/src/app.ts index dc755f95d062..3d42d7a65383 100644 --- a/dev-packages/e2e-tests/test-applications/node-express/src/app.ts +++ b/dev-packages/e2e-tests/test-applications/node-express/src/app.ts @@ -13,7 +13,6 @@ Sentry.init({ debug: !!process.env.DEBUG, tunnel: `http://localhost:3031/`, // proxy server tracesSampleRate: 1, - enableLogs: true, integrations: [ Sentry.nativeNodeFetchIntegration({ headersToSpanAttributes: { diff --git a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/src/instrument.ts b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/src/instrument.ts index ea9b6ae57545..6d5d78ff521c 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/src/instrument.ts +++ b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/src/instrument.ts @@ -17,7 +17,6 @@ Sentry.init({ // Tracing is completely disabled // Custom OTEL setup skipOpenTelemetrySetup: true, - enableLogs: true, }); // Create and configure NodeTracerProvider diff --git a/dev-packages/e2e-tests/test-applications/tsx-express/instrument.mjs b/dev-packages/e2e-tests/test-applications/tsx-express/instrument.mjs index ddc96c7c17fc..f3dd95215d03 100644 --- a/dev-packages/e2e-tests/test-applications/tsx-express/instrument.mjs +++ b/dev-packages/e2e-tests/test-applications/tsx-express/instrument.mjs @@ -6,5 +6,4 @@ Sentry.init({ debug: !!process.env.DEBUG, tunnel: `http://localhost:3031/`, // proxy server tracesSampleRate: 1, - enableLogs: true, }); diff --git a/dev-packages/node-integration-tests/suites/consola/instrument.mjs b/dev-packages/node-integration-tests/suites/consola/instrument.mjs index 938f870a3b89..cededb10ce20 100644 --- a/dev-packages/node-integration-tests/suites/consola/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/consola/instrument.mjs @@ -5,6 +5,5 @@ Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0.0', environment: 'test', - enableLogs: true, transport: loggingTransport, }); diff --git a/dev-packages/node-integration-tests/suites/pino/instrument-auto-off.mjs b/dev-packages/node-integration-tests/suites/pino/instrument-auto-off.mjs index 1e72e58fb043..42c938885229 100644 --- a/dev-packages/node-integration-tests/suites/pino/instrument-auto-off.mjs +++ b/dev-packages/node-integration-tests/suites/pino/instrument-auto-off.mjs @@ -4,6 +4,5 @@ Sentry.init({ dsn: process.env.SENTRY_DSN, release: '1.0', tracesSampleRate: 1.0, - enableLogs: true, integrations: [Sentry.pinoIntegration({ autoInstrument: false })], }); diff --git a/dev-packages/node-integration-tests/suites/pino/instrument.mjs b/dev-packages/node-integration-tests/suites/pino/instrument.mjs index 74d9f0e95119..009af91971f3 100644 --- a/dev-packages/node-integration-tests/suites/pino/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/pino/instrument.mjs @@ -4,6 +4,5 @@ Sentry.init({ dsn: process.env.SENTRY_DSN, release: '1.0', tracesSampleRate: 1.0, - enableLogs: true, integrations: [Sentry.pinoIntegration({ error: { levels: ['error', 'fatal'] } })], }); diff --git a/dev-packages/node-integration-tests/suites/public-api/logger/scenario.ts b/dev-packages/node-integration-tests/suites/public-api/logger/scenario.ts index d2afb5948b2a..74a43eb5356a 100644 --- a/dev-packages/node-integration-tests/suites/public-api/logger/scenario.ts +++ b/dev-packages/node-integration-tests/suites/public-api/logger/scenario.ts @@ -5,7 +5,6 @@ Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0.0', environment: 'test', - enableLogs: true, transport: loggingTransport, }); diff --git a/dev-packages/node-integration-tests/suites/public-api/logs-disabled/subject.ts b/dev-packages/node-integration-tests/suites/public-api/logs-disabled/subject.ts new file mode 100644 index 000000000000..06eee985d7bf --- /dev/null +++ b/dev-packages/node-integration-tests/suites/public-api/logs-disabled/subject.ts @@ -0,0 +1,28 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +const client = new Sentry.NodeClient({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + enableLogs: false, + transport: loggingTransport, + stackParser: Sentry.defaultStackParser, + integrations: [], +}); + +const scope = new Sentry.Scope(); +scope.setClient(client); +client.init(); + +async function run(): Promise { + // Should be dropped because logs are disabled. + Sentry.logger.info('this log should not be captured', {}, { scope }); + + // Should be delivered — used as a sentinel so the test has a deterministic + // envelope to match instead of asserting on the absence of one. + scope.captureException(new Error('sentinel_error')); + + await client.flush(); +} + +// eslint-disable-next-line @typescript-eslint/no-floating-promises +void run(); diff --git a/dev-packages/node-integration-tests/suites/public-api/logs-disabled/test.ts b/dev-packages/node-integration-tests/suites/public-api/logs-disabled/test.ts new file mode 100644 index 000000000000..eab0632bb8e4 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/public-api/logs-disabled/test.ts @@ -0,0 +1,30 @@ +import { afterAll, describe, test } from 'vitest'; +import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; + +describe('logs disabled', () => { + afterAll(() => { + cleanupChildProcesses(); + }); + + // With `enableLogs: false` the log is dropped at capture time, so it never reaches + // the transport. The sentinel error is the only envelope we expect — if a log + // envelope were emitted, it would arrive before the error and fail the assertion. + test('does not capture logs when enableLogs is disabled', async () => { + const runner = createRunner(__dirname, 'subject.ts') + .expect({ + event: { + exception: { + values: [ + { + type: 'Error', + value: 'sentinel_error', + }, + ], + }, + }, + }) + .start(); + + await runner.completed(); + }); +}); diff --git a/dev-packages/node-integration-tests/suites/public-api/logs/subject.ts b/dev-packages/node-integration-tests/suites/public-api/logs/subject.ts index 650a5112cfe1..bb64c10b2bc9 100644 --- a/dev-packages/node-integration-tests/suites/public-api/logs/subject.ts +++ b/dev-packages/node-integration-tests/suites/public-api/logs/subject.ts @@ -6,7 +6,6 @@ const client = new Sentry.NodeClient({ transport: loggingTransport, stackParser: Sentry.defaultStackParser, integrations: [], - enableLogs: true, dataCollection: { userInfo: true }, }); diff --git a/dev-packages/node-integration-tests/suites/winston/instrument.mjs b/dev-packages/node-integration-tests/suites/winston/instrument.mjs index 4906aea85032..f21bdc650450 100644 --- a/dev-packages/node-integration-tests/suites/winston/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/winston/instrument.mjs @@ -5,7 +5,6 @@ Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0.0', environment: 'test', - enableLogs: true, transport: loggingTransport, debug: true, }); diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 8171a53c02c4..314fbb597cef 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -267,7 +267,7 @@ export abstract class Client { // Backfill enableLogs option from _experiments.enableLogs // TODO(v11): Remove or change default value // eslint-disable-next-line typescript/no-deprecated - this._options.enableLogs = this._options.enableLogs ?? this._options._experiments?.enableLogs; + this._options.enableLogs = this._options.enableLogs ?? this._options._experiments?.enableLogs ?? true; // Setup log flushing with weight and timeout tracking if (this._options.enableLogs) { diff --git a/packages/core/src/logs/internal.ts b/packages/core/src/logs/internal.ts index 5987df008547..9453cadcf417 100644 --- a/packages/core/src/logs/internal.ts +++ b/packages/core/src/logs/internal.ts @@ -84,8 +84,13 @@ export function _INTERNAL_captureLog( return; } - const { release, environment, enableLogs = false, beforeSendLog } = client.getOptions(); - if (!enableLogs) { + const { release, environment, _experiments, enableLogs, beforeSendLog } = client.getOptions(); + + // todo(v11): Remove the experimental flag + // eslint-disable-next-line typescript/no-deprecated + const logsEnabled = enableLogs ?? _experiments?.enableLogs ?? true; + + if (!logsEnabled) { DEBUG_BUILD && debug.warn('logging option not enabled, log will not be captured.'); return; } diff --git a/packages/core/src/types/options.ts b/packages/core/src/types/options.ts index a161d8b36e10..5b81fc5c1260 100644 --- a/packages/core/src/types/options.ts +++ b/packages/core/src/types/options.ts @@ -583,7 +583,7 @@ export interface ClientOptions { .spyOn(logsInternalModule, '_INTERNAL_flushLogsBuffer') .mockImplementation(() => undefined); - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); await client.close(); @@ -2924,32 +2924,32 @@ describe('Client', () => { }); describe('enableLogs', () => { - it('defaults to `undefined`', () => { + it('defaults to `true`', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); - expect(client.getOptions().enableLogs).toBeUndefined(); + expect(client.getOptions().enableLogs).toBe(true); }); - it('can be set as a top-level option', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + it('can be disabled via the top-level option', () => { + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: false }); const client = new TestClient(options); - expect(client.getOptions().enableLogs).toBe(true); + expect(client.getOptions().enableLogs).toBe(false); }); - it('can be set as an experimental option', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, _experiments: { enableLogs: true } }); + it('can be disabled via the experimental option', () => { + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, _experiments: { enableLogs: false } }); const client = new TestClient(options); - expect(client.getOptions().enableLogs).toBe(true); + expect(client.getOptions().enableLogs).toBe(false); }); test('top-level option takes precedence over experimental option', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, - enableLogs: true, - _experiments: { enableLogs: false }, + enableLogs: false, + _experiments: { enableLogs: true }, }); const client = new TestClient(options); - expect(client.getOptions().enableLogs).toBe(true); + expect(client.getOptions().enableLogs).toBe(false); }); }); @@ -2965,7 +2965,6 @@ describe('Client', () => { it('flushes logs when weight exceeds 800KB', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, - enableLogs: true, }); const client = new TestClient(options); const scope = new Scope(); @@ -2983,7 +2982,6 @@ describe('Client', () => { it('accumulates log weight without flushing when under threshold', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, - enableLogs: true, }); const client = new TestClient(options); const scope = new Scope(); @@ -3001,7 +2999,6 @@ describe('Client', () => { it('flushes logs after idle timeout', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, - enableLogs: true, }); const client = new TestClient(options); const scope = new Scope(); @@ -3023,7 +3020,6 @@ describe('Client', () => { it('does not reset idle timeout when new logs are captured', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, - enableLogs: true, }); const client = new TestClient(options); const scope = new Scope(); @@ -3050,7 +3046,6 @@ describe('Client', () => { it('starts new timer after timeout completes and flushes', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, - enableLogs: true, }); const client = new TestClient(options); const scope = new Scope(); @@ -3082,7 +3077,6 @@ describe('Client', () => { it('flushes logs on flush event', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, - enableLogs: true, }); const client = new TestClient(options); const scope = new Scope(); @@ -3103,6 +3097,7 @@ describe('Client', () => { it('does not flush logs when logs are disabled', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, + enableLogs: false, }); const client = new TestClient(options); const scope = new Scope(); @@ -3122,7 +3117,6 @@ describe('Client', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, - enableLogs: true, }); const client = new TestClient(options); const scope = new Scope(); @@ -3140,9 +3134,7 @@ describe('Client', () => { it('flush() drains the log buffer when client has no transport', async () => { // Client without DSN — _transport is undefined - const options = getDefaultTestClientOptions({ - enableLogs: true, - }); + const options = getDefaultTestClientOptions({}); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); diff --git a/packages/core/test/lib/integrations/consola.test.ts b/packages/core/test/lib/integrations/consola.test.ts index 0ab7a3cc1e98..6cdaedc64cba 100644 --- a/packages/core/test/lib/integrations/consola.test.ts +++ b/packages/core/test/lib/integrations/consola.test.ts @@ -32,10 +32,8 @@ describe('createConsolaReporter', () => { beforeEach(() => { vi.clearAllMocks(); - // Create a test client with enableLogs: true mockClient = new TestClient({ ...getDefaultTestClientOptions({ dsn: 'https://username@domain/123' }), - enableLogs: true, normalizeDepth: 3, normalizeMaxBreadth: 1000, }); diff --git a/packages/core/test/lib/logs/console-integration.test.ts b/packages/core/test/lib/logs/console-integration.test.ts index ef39b4ebdb15..a2a878365064 100644 --- a/packages/core/test/lib/logs/console-integration.test.ts +++ b/packages/core/test/lib/logs/console-integration.test.ts @@ -44,7 +44,6 @@ describe('consoleLoggingIntegration', () => { client = new TestClient({ ...getDefaultTestClientOptions({ dsn: 'https://username@domain/123' }), - enableLogs: true, normalizeDepth: 3, normalizeMaxBreadth: 1000, }); @@ -418,7 +417,6 @@ describe('consoleLoggingIntegration', () => { it('only captures configured levels', () => { const filteredClient = new TestClient({ ...getDefaultTestClientOptions({ dsn: 'https://username@domain/123' }), - enableLogs: true, }); vi.mocked(getClient).mockReturnValue(filteredClient); diff --git a/packages/core/test/lib/logs/internal.test.ts b/packages/core/test/lib/logs/internal.test.ts index 608193b4a838..c98cd8574692 100644 --- a/packages/core/test/lib/logs/internal.test.ts +++ b/packages/core/test/lib/logs/internal.test.ts @@ -22,7 +22,7 @@ describe('_INTERNAL_captureLog', () => { _INTERNAL_resetSequenceNumber(); }); it('captures and sends logs', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -43,9 +43,9 @@ describe('_INTERNAL_captureLog', () => { ); }); - it('does not capture logs when enableLogs is not enabled', () => { + it('does not capture logs when enableLogs is disabled', () => { const logWarnSpy = vi.spyOn(loggerModule.debug, 'warn').mockImplementation(() => undefined); - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: false }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -59,7 +59,7 @@ describe('_INTERNAL_captureLog', () => { }); it('includes trace context when available', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -81,7 +81,6 @@ describe('_INTERNAL_captureLog', () => { it('includes release and environment in log attributes when available', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, - enableLogs: true, release: '1.0.0', environment: 'test', }); @@ -108,7 +107,6 @@ describe('_INTERNAL_captureLog', () => { it('includes SDK metadata in log attributes when available', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, - enableLogs: true, }); const client = new TestClient(options); const scope = new Scope(); @@ -140,7 +138,6 @@ describe('_INTERNAL_captureLog', () => { it('does not include SDK metadata in log attributes when not available', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, - enableLogs: true, }); const client = new TestClient(options); const scope = new Scope(); @@ -161,7 +158,7 @@ describe('_INTERNAL_captureLog', () => { describe('attributes', () => { it('includes custom attributes in log', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -190,7 +187,7 @@ describe('_INTERNAL_captureLog', () => { }); it('applies scope attributes attributes to log', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -251,7 +248,7 @@ describe('_INTERNAL_captureLog', () => { }); it('flushes logs buffer when it reaches max size', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -273,7 +270,7 @@ describe('_INTERNAL_captureLog', () => { }); it('does not flush logs buffer when it is empty', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const mockSendEnvelope = vi.spyOn(client as any, 'sendEnvelope').mockImplementation(() => {}); @@ -282,7 +279,7 @@ describe('_INTERNAL_captureLog', () => { }); it('handles parameterized strings correctly', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -310,7 +307,7 @@ describe('_INTERNAL_captureLog', () => { }); it('does not set the template attribute if there are no parameters', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -332,7 +329,6 @@ describe('_INTERNAL_captureLog', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, - enableLogs: true, beforeSendLog, }); const client = new TestClient(options); @@ -396,7 +392,6 @@ describe('_INTERNAL_captureLog', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, - enableLogs: true, beforeSendLog, }); const client = new TestClient(options); @@ -422,7 +417,7 @@ describe('_INTERNAL_captureLog', () => { it('emits beforeCaptureLog and afterCaptureLog events', () => { const beforeCaptureLogSpy = vi.spyOn(TestClient.prototype, 'emit'); - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -442,7 +437,7 @@ describe('_INTERNAL_captureLog', () => { describe('replay integration with onlyIfSampled', () => { it('includes replay ID for sampled sessions', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -474,7 +469,7 @@ describe('_INTERNAL_captureLog', () => { }); it('excludes replay ID for unsampled sessions when onlyIfSampled=true', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -501,7 +496,7 @@ describe('_INTERNAL_captureLog', () => { }); it('includes replay ID for buffer mode sessions', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -536,7 +531,7 @@ describe('_INTERNAL_captureLog', () => { }); it('handles missing replay integration gracefully', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -555,7 +550,6 @@ describe('_INTERNAL_captureLog', () => { it('combines replay ID with other log attributes', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, - enableLogs: true, release: '1.0.0', environment: 'test', }); @@ -607,7 +601,7 @@ describe('_INTERNAL_captureLog', () => { }); it('does not set replay ID attribute when getReplayId returns null or undefined', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -635,7 +629,7 @@ describe('_INTERNAL_captureLog', () => { }); it('sets replay_is_buffering attribute when replay is in buffer mode', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -668,7 +662,7 @@ describe('_INTERNAL_captureLog', () => { }); it('does not set replay_is_buffering attribute when replay is in session mode', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -698,7 +692,7 @@ describe('_INTERNAL_captureLog', () => { }); it('does not set replay_is_buffering attribute when replay is undefined mode', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -728,7 +722,7 @@ describe('_INTERNAL_captureLog', () => { }); it('does not set replay_is_buffering attribute when no replay ID is available', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -756,7 +750,7 @@ describe('_INTERNAL_captureLog', () => { }); it('does not set replay_is_buffering attribute when replay integration is missing', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -777,7 +771,6 @@ describe('_INTERNAL_captureLog', () => { it('combines replay_is_buffering with other replay attributes', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, - enableLogs: true, release: '1.0.0', environment: 'test', }); @@ -837,7 +830,6 @@ describe('_INTERNAL_captureLog', () => { it('includes user data in log attributes', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, - enableLogs: true, }); const client = new TestClient(options); const scope = new Scope(); @@ -871,7 +863,6 @@ describe('_INTERNAL_captureLog', () => { it('includes partial user data when only some fields are available', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, - enableLogs: true, sendDefaultPii: true, }); const client = new TestClient(options); @@ -897,7 +888,6 @@ describe('_INTERNAL_captureLog', () => { it('includes user email and username without id', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, - enableLogs: true, sendDefaultPii: true, }); const client = new TestClient(options); @@ -928,7 +918,6 @@ describe('_INTERNAL_captureLog', () => { it('does not include user data when user object is empty', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, - enableLogs: true, sendDefaultPii: true, }); const client = new TestClient(options); @@ -947,7 +936,6 @@ describe('_INTERNAL_captureLog', () => { it('combines user data with other log attributes', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, - enableLogs: true, sendDefaultPii: true, release: '1.0.0', environment: 'test', @@ -1002,7 +990,6 @@ describe('_INTERNAL_captureLog', () => { it('handles user data with non-string values', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, - enableLogs: true, sendDefaultPii: true, }); const client = new TestClient(options); @@ -1033,7 +1020,6 @@ describe('_INTERNAL_captureLog', () => { it('preserves existing user attributes in log and does not override them', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, - enableLogs: true, sendDefaultPii: true, }); const client = new TestClient(options); @@ -1077,7 +1063,6 @@ describe('_INTERNAL_captureLog', () => { it('only adds scope user data for attributes that do not already exist', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, - enableLogs: true, sendDefaultPii: true, }); const client = new TestClient(options); @@ -1127,7 +1112,6 @@ describe('_INTERNAL_captureLog', () => { it('overrides user-provided system attributes with SDK values', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, - enableLogs: true, release: 'sdk-release-1.0.0', environment: 'sdk-environment', }); @@ -1188,7 +1172,7 @@ describe('_INTERNAL_captureLog', () => { it('increments the sequence number across consecutive logs', () => { vi.spyOn(timeModule, 'timestampInSeconds').mockReturnValue(1000.001); - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -1215,7 +1199,7 @@ describe('_INTERNAL_captureLog', () => { return log; }); - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true, beforeSendLog }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, beforeSendLog }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -1235,7 +1219,7 @@ describe('_INTERNAL_captureLog', () => { it('produces monotonically increasing sequence numbers within the same millisecond', () => { vi.spyOn(timeModule, 'timestampInSeconds').mockReturnValue(1000.001); - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -1258,7 +1242,7 @@ describe('_INTERNAL_captureLog', () => { }); it('resets the sequence number via _INTERNAL_resetSequenceNumber', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -1280,7 +1264,7 @@ describe('_INTERNAL_captureLog', () => { describe.runIf(hasToWellFormed)('lone surrogate sanitization', () => { it('sanitizes lone surrogates in log message body', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -1292,7 +1276,7 @@ describe('_INTERNAL_captureLog', () => { }); it('sanitizes lone surrogates in parameterized (fmt) log message body', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -1305,7 +1289,7 @@ describe('_INTERNAL_captureLog', () => { }); it('sanitizes lone surrogates in log attribute values', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -1327,7 +1311,7 @@ describe('_INTERNAL_captureLog', () => { }); it('sanitizes lone surrogates in log attribute keys', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); @@ -1349,7 +1333,7 @@ describe('_INTERNAL_captureLog', () => { }); it('preserves valid emoji in log messages and attributes', () => { - const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true }); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); const scope = new Scope(); scope.setClient(client); diff --git a/packages/deno/test/mod.test.ts b/packages/deno/test/mod.test.ts index 1a2e376dd433..55be7ace7174 100644 --- a/packages/deno/test/mod.test.ts +++ b/packages/deno/test/mod.test.ts @@ -116,7 +116,6 @@ Deno.test('logger.info captures a log envelope item', async () => { const envelopes: Array = []; const client = new DenoClient({ dsn: 'https://233a45e5efe34c47a3536797ce15dafa@nothing.here/5650507', - enableLogs: true, integrations: getDefaultIntegrations({}), stackParser: createStackParser(nodeStackLineParser()), transport: makeTestTransport(envelope => { @@ -152,7 +151,6 @@ Deno.test('logger.info captures a log envelope item', async () => { Deno.test('adds server.address to log attributes', () => { const client = new DenoClient({ dsn: 'https://233a45e5efe34c47a3536797ce15dafa@nothing.here/5650507', - enableLogs: true, serverName: 'test-server', integrations: getDefaultIntegrations({}), stackParser: createStackParser(nodeStackLineParser()), @@ -168,7 +166,6 @@ Deno.test('adds server.address to log attributes', () => { Deno.test('preserves existing log attributes when adding server.address', () => { const client = new DenoClient({ dsn: 'https://233a45e5efe34c47a3536797ce15dafa@nothing.here/5650507', - enableLogs: true, serverName: 'test-server', integrations: getDefaultIntegrations({}), stackParser: createStackParser(nodeStackLineParser()), diff --git a/packages/node/test/sdk/client.test.ts b/packages/node/test/sdk/client.test.ts index c581f07b5c6a..72775f37b295 100644 --- a/packages/node/test/sdk/client.test.ts +++ b/packages/node/test/sdk/client.test.ts @@ -55,6 +55,7 @@ describe('NodeClient', () => { runtime: { name: 'node', version: expect.any(String) }, serverName: expect.any(String), tracesSampleRate: 1, + enableLogs: true, }); }); @@ -316,7 +317,7 @@ describe('NodeClient', () => { describe('log capture', () => { it('adds server name to log attributes', () => { - const options = getDefaultNodeClientOptions({ enableLogs: true }); + const options = getDefaultNodeClientOptions(); const client = new NodeClient(options); const log: Log = { level: 'info', message: 'test message', attributes: {} }; @@ -329,7 +330,7 @@ describe('NodeClient', () => { it('preserves existing log attributes', () => { const serverName = 'test-server'; - const options = getDefaultNodeClientOptions({ serverName, enableLogs: true }); + const options = getDefaultNodeClientOptions({ serverName }); const client = new NodeClient(options); const log: Log = { level: 'info', message: 'test message', attributes: { 'existing.attr': 'value' } }; @@ -390,7 +391,7 @@ describe('NodeClient', () => { it('stops log capture if it was started', async () => { const processOffSpy = vi.spyOn(process, 'off'); - const client = new NodeClient(getDefaultNodeClientOptions({ enableLogs: true })); + const client = new NodeClient(getDefaultNodeClientOptions()); const result = await client.close(); From 02c3315919294af5415ef29be7de90f44162cf68 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 24 Jul 2026 16:01:35 +0200 Subject: [PATCH 2/5] test(core): Account for backfilled enableLogs in getOptions assertion The client constructor backfills `enableLogs` onto its options, so the strict `getOptions()` equality check now needs the resolved default. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/core/test/lib/client.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/test/lib/client.test.ts b/packages/core/test/lib/client.test.ts index a539c06e1cfd..325d4a90f181 100644 --- a/packages/core/test/lib/client.test.ts +++ b/packages/core/test/lib/client.test.ts @@ -103,7 +103,7 @@ describe('Client', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, test: true }); const client = new TestClient(options); - expect(client.getOptions()).toEqual({ attachStacktrace: true, ...options }); + expect(client.getOptions()).toEqual({ attachStacktrace: true, ...options, enableLogs: true }); }); }); From 721edd97123eb6093f5e7f7f9ba3e279bbb436d2 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 24 Jul 2026 16:04:01 +0200 Subject: [PATCH 3/5] test(node): Make logs-disabled test deterministically catch leaks Flush the log buffer before capturing the sentinel error so a leaked log envelope arrives first and fails the ordered `event` expectation. Previously the error was sent first and the runner could complete before a stray log arrived, letting a broken disable path pass. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../suites/public-api/logs-disabled/subject.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/public-api/logs-disabled/subject.ts b/dev-packages/node-integration-tests/suites/public-api/logs-disabled/subject.ts index 06eee985d7bf..6f6112423614 100644 --- a/dev-packages/node-integration-tests/suites/public-api/logs-disabled/subject.ts +++ b/dev-packages/node-integration-tests/suites/public-api/logs-disabled/subject.ts @@ -14,11 +14,14 @@ scope.setClient(client); client.init(); async function run(): Promise { - // Should be dropped because logs are disabled. Sentry.logger.info('this log should not be captured', {}, { scope }); - // Should be delivered — used as a sentinel so the test has a deterministic - // envelope to match instead of asserting on the absence of one. + // Flush the log buffer before the sentinel is captured. If the disable path is + // broken, the leaked log envelope is sent here and arrives before the error, + // failing the ordered `event` expectation. If logs are correctly disabled, + // the buffer is empty and only the sentinel error is delivered. + await client.flush(); + scope.captureException(new Error('sentinel_error')); await client.flush(); From 59620ad420c67736041dad09eea78bec53955b84 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 24 Jul 2026 16:11:47 +0200 Subject: [PATCH 4/5] refactor(core): Simplify enableLogs guard to a plain default The constructor already backfills `enableLogs` from `_experiments.enableLogs`, so re-reading `_experiments` in the capture guard was dead code. Reduce it to a plain destructure default, matching the original shape. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/core/src/logs/internal.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/core/src/logs/internal.ts b/packages/core/src/logs/internal.ts index 9453cadcf417..efbbed137cc2 100644 --- a/packages/core/src/logs/internal.ts +++ b/packages/core/src/logs/internal.ts @@ -84,13 +84,8 @@ export function _INTERNAL_captureLog( return; } - const { release, environment, _experiments, enableLogs, beforeSendLog } = client.getOptions(); - - // todo(v11): Remove the experimental flag - // eslint-disable-next-line typescript/no-deprecated - const logsEnabled = enableLogs ?? _experiments?.enableLogs ?? true; - - if (!logsEnabled) { + const { release, environment, enableLogs = true, beforeSendLog } = client.getOptions(); + if (!enableLogs) { DEBUG_BUILD && debug.warn('logging option not enabled, log will not be captured.'); return; } From 10618d351ca0d3120422c64ade413c37705ea25d Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 24 Jul 2026 16:13:24 +0200 Subject: [PATCH 5/5] docs(core): Update stale enableLogs backfill TODO The default has now been flipped, so the "or change default value" branch of the TODO is done. Narrow it to removing the deprecated experimental flag, matching the adjacent enableMetrics TODO. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/core/src/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 314fbb597cef..98e7851097ce 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -265,7 +265,7 @@ export abstract class Client { } // Backfill enableLogs option from _experiments.enableLogs - // TODO(v11): Remove or change default value + // todo(v11): Remove the experimental flag // eslint-disable-next-line typescript/no-deprecated this._options.enableLogs = this._options.enableLogs ?? this._options._experiments?.enableLogs ?? true;