From fcb80405fbf0cb3dd748af1a50eae9693eed8e52 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 12 Aug 2026 16:55:10 +0200 Subject: [PATCH 1/9] feat(android): Warn for legacy Logs metadata Detect explicit io.sentry.logs.enabled Android manifest metadata and emit tailored migration warnings without changing the integration-local Logs options. Co-Authored-By: Claude --- .../android/core/ManifestMetadataReader.java | 17 ++++++ .../core/ManifestMetadataReaderTest.kt | 60 ++++++++++++++++++- 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java index 3161db3720e..2aa10e98abc 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java @@ -706,6 +706,23 @@ static void applyMetadata( } } + if (metadata.containsKey(ENABLE_LOGS)) { + final boolean enableLogs = readBool(metadata, logger, ENABLE_LOGS, false); + if (enableLogs) { + logger.log( + SentryLevel.WARNING, + "The Android manifest option 'io.sentry.logs.enabled' is no longer supported. " + + "Manual Sentry.logger() calls no longer require it, and automatic logging " + + "integrations now require their own opt-ins."); + } else { + logger.log( + SentryLevel.WARNING, + "The Android manifest option 'io.sentry.logs.enabled' no longer disables manual " + + "Sentry.logger() calls. Automatic logging integrations remain disabled " + + "unless enabled through their own opt-ins."); + } + } + options.setEnableTimberLogs( readBool(metadata, logger, ENABLE_TIMBER_LOGS, options.isEnableTimberLogs())); diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt index b7ea7b962af..6a0b17cff70 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt @@ -29,7 +29,7 @@ import org.mockito.kotlin.verify class ManifestMetadataReaderTest { private class Fixture { val logger = mock() - val options = SentryAndroidOptions().apply { setLogger(logger) } + val options = SentryAndroidOptions().apply { setLogger(this@Fixture.logger) } val buildInfoProvider = mock() fun getContext(metaData: Bundle = Bundle()): Context = @@ -1943,6 +1943,64 @@ class ManifestMetadataReaderTest { assertTrue(fixture.options.inAppExcludes.isEmpty()) } + @Test + fun `applyMetadata does not warn when legacy logs enabled metadata is absent`() { + fixture.options.isDebug = true + val context = fixture.getContext() + + ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) + + verify(fixture.logger, never()).log(eq(SentryLevel.WARNING), any()) + } + + @Test + fun `applyMetadata warns when legacy logs enabled metadata is true`() { + val bundle = + bundleOf( + ManifestMetadataReader.DEBUG to true, + ManifestMetadataReader.ENABLE_LOGS to true, + ) + val context = fixture.getContext(metaData = bundle) + + ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) + + verify(fixture.logger) + .log( + SentryLevel.WARNING, + "The Android manifest option 'io.sentry.logs.enabled' is no longer supported. " + + "Manual Sentry.logger() calls no longer require it, and automatic logging " + + "integrations now require their own opt-ins.", + *emptyArray(), + ) + assertThat(fixture.options.isEnableTimberLogs).isFalse() + assertThat(fixture.options.isEnableLogcatLogs).isFalse() + } + + @Test + fun `applyMetadata warns when legacy logs enabled metadata is false`() { + fixture.options.isEnableTimberLogs = true + fixture.options.isEnableLogcatLogs = true + val bundle = + bundleOf( + ManifestMetadataReader.DEBUG to true, + ManifestMetadataReader.ENABLE_LOGS to false, + ) + val context = fixture.getContext(metaData = bundle) + + ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) + + verify(fixture.logger) + .log( + SentryLevel.WARNING, + "The Android manifest option 'io.sentry.logs.enabled' no longer disables manual " + + "Sentry.logger() calls. Automatic logging integrations remain disabled unless " + + "enabled through their own opt-ins.", + *emptyArray(), + ) + assertThat(fixture.options.isEnableTimberLogs).isTrue() + assertThat(fixture.options.isEnableLogcatLogs).isTrue() + } + @Test fun `applyMetadata keeps Timber logs disabled if not found`() { val context = fixture.getContext() From 50dc84e682ae662511a09bee427e46a66a245f40 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 13 Aug 2026 05:16:13 +0200 Subject: [PATCH 2/9] feat(core): Warn for legacy Logs configuration Keep parsing the nullable logs.enabled external option and emit tailored migration warnings for explicit true and false values without changing manual Logs capture. Co-Authored-By: Claude --- .../main/java/io/sentry/SentryOptions.java | 15 ++++ .../java/io/sentry/ExternalOptionsTest.kt | 12 ++++ .../test/java/io/sentry/SentryOptionsTest.kt | 72 +++++++++++++++++++ 3 files changed, 99 insertions(+) diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 16748005af2..aa136d176aa 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -3733,6 +3733,21 @@ public void merge(final @NotNull ExternalOptions options) { } } + if (options.isEnableLogs() != null) { + if (options.isEnableLogs()) { + logger.log( + SentryLevel.WARNING, + "The 'logs.enabled' option is no longer supported. Manual Sentry.logger() calls no " + + "longer require it, and automatic logging integrations now require their own " + + "opt-ins."); + } else { + logger.log( + SentryLevel.WARNING, + "The 'logs.enabled' option no longer disables manual Sentry.logger() calls. Automatic " + + "logging integrations remain disabled unless enabled through their own opt-ins."); + } + } + if (options.isEnableMetrics() != null) { getMetrics().setEnabled(options.isEnableMetrics()); } diff --git a/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt b/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt index fee707d31f3..5288264c068 100644 --- a/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt @@ -437,6 +437,18 @@ class ExternalOptionsTest { withPropertiesFile("logs.enabled=true") { options -> assertTrue(options.isEnableLogs == true) } } + @Test + fun `creates options with enableLogs set to false`() { + withPropertiesFile("logs.enabled=false") { options -> + assertTrue(options.isEnableLogs == false) + } + } + + @Test + fun `creates options with enableLogs set to null when not set`() { + withPropertiesFile { assertNull(it.isEnableLogs) } + } + @Test fun `creates options with enableMetrics set to true`() { withPropertiesFile("metrics.enabled=true") { options -> diff --git a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt index 29df56a1669..17546c62136 100644 --- a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt @@ -2,6 +2,9 @@ package io.sentry import io.sentry.SentryOptions.RequestSize import io.sentry.logger.ILoggerBatchProcessorFactory +import io.sentry.logger.LoggerApi +import io.sentry.test.createSentryClientMock +import io.sentry.test.createTestScopes import io.sentry.util.StringUtils import java.io.File import java.net.Proxy @@ -15,8 +18,11 @@ import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertSame import kotlin.test.assertTrue +import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.eq import org.mockito.kotlin.mock +import org.mockito.kotlin.never import org.mockito.kotlin.verify class SentryOptionsTest { @@ -501,6 +507,72 @@ class SentryOptionsTest { assertTrue(options.metrics.isEnabled) } + @Test + fun `merging options does not warn when legacy logs configuration is absent`() { + val logger = mock() + val options = + SentryOptions().also { + it.isDebug = true + it.setLogger(logger) + } + + options.merge(ExternalOptions()) + + verify(logger, never()).log(eq(SentryLevel.WARNING), any()) + } + + @Test + fun `merging options warns when legacy logs configuration is true`() { + val logger = mock() + val options = + SentryOptions().also { + it.isDebug = true + it.setLogger(logger) + } + + options.merge(ExternalOptions().apply { isEnableLogs = true }) + + verify(logger) + .log( + SentryLevel.WARNING, + "The 'logs.enabled' option is no longer supported. Manual Sentry.logger() calls no " + + "longer require it, and automatic logging integrations now require their own opt-ins.", + *emptyArray(), + ) + assertLegacyLogsConfigurationDoesNotDisableCapture(options) + } + + @Test + fun `merging options warns when legacy logs configuration is false`() { + val logger = mock() + val options = + SentryOptions().also { + it.isDebug = true + it.setLogger(logger) + } + + options.merge(ExternalOptions().apply { isEnableLogs = false }) + + verify(logger) + .log( + SentryLevel.WARNING, + "The 'logs.enabled' option no longer disables manual Sentry.logger() calls. Automatic " + + "logging integrations remain disabled unless enabled through their own opt-ins.", + *emptyArray(), + ) + assertLegacyLogsConfigurationDoesNotDisableCapture(options) + } + + private fun assertLegacyLogsConfigurationDoesNotDisableCapture(options: SentryOptions) { + options.dsn = "https://key@sentry.io/proj" + val client = createSentryClientMock() + val scopes = createTestScopes(options).apply { bindClient(client) } + + LoggerApi(scopes).info("test log") + + verify(client).captureLog(any(), anyOrNull()) + } + @Test fun `merging options merges and overwrites existing tag values`() { val externalOptions = ExternalOptions() From 419986b50bd89516e61d6e30c341d1b3efbd3bd1 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 13 Aug 2026 05:35:55 +0200 Subject: [PATCH 3/9] feat(spring): Warn for legacy Logs property Inspect the Spring Environment before SDK initialization and emit tailored migration warnings for explicit sentry.logs.enabled values without binding or applying the obsolete property. Co-Authored-By: Claude --- .../spring/boot4/SentryAutoConfiguration.java | 30 +++++++++- .../boot4/SentryAutoConfigurationTest.kt | 60 +++++++++++++++++++ .../boot/jakarta/SentryAutoConfiguration.java | 30 +++++++++- .../jakarta/SentryAutoConfigurationTest.kt | 60 +++++++++++++++++++ .../spring/boot/SentryAutoConfiguration.java | 30 +++++++++- .../boot/SentryAutoConfigurationTest.kt | 60 +++++++++++++++++++ 6 files changed, 267 insertions(+), 3 deletions(-) diff --git a/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryAutoConfiguration.java b/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryAutoConfiguration.java index 2429c1e7446..17be2777505 100644 --- a/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryAutoConfiguration.java +++ b/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryAutoConfiguration.java @@ -11,6 +11,7 @@ import io.sentry.ScopesAdapter; import io.sentry.Sentry; import io.sentry.SentryIntegrationPackageStorage; +import io.sentry.SentryLevel; import io.sentry.SentryOptions; import io.sentry.protocol.SdkVersion; import io.sentry.quartz.SentryJobListener; @@ -163,7 +164,8 @@ static class OpenTelemetryNoAgentConfiguration {} final @NotNull List> optionsConfigurations, final @NotNull SentryProperties options, final @NotNull ObjectProvider spanFactory, - final @NotNull ObjectProvider gitProperties) { + final @NotNull ObjectProvider gitProperties, + final @NotNull Environment environment) { optionsConfigurations.forEach( optionsConfiguration -> optionsConfiguration.configure(options)); gitProperties.ifAvailable( @@ -184,10 +186,36 @@ static class OpenTelemetryNoAgentConfiguration {} // its technically possible to set non-throwable class to `ignoredExceptionsForType` set // here we make sure that only classes that extend throwable are set on this field options.getIgnoredExceptionsForType().removeIf(it -> !Throwable.class.isAssignableFrom(it)); + warnForLegacyLogsConfiguration(environment, options); Sentry.init(options); return ScopesAdapter.getInstance(); } + private void warnForLegacyLogsConfiguration( + final @NotNull Environment environment, final @NotNull SentryOptions options) { + if (environment.containsProperty("sentry.logs.enabled")) { + final boolean enableLogs = + Boolean.TRUE.equals(environment.getProperty("sentry.logs.enabled", Boolean.class)); + if (enableLogs) { + options + .getLogger() + .log( + SentryLevel.WARNING, + "The 'sentry.logs.enabled' property is no longer supported. Manual " + + "Sentry.logger() calls no longer require it, and automatic logging " + + "integrations now require their own opt-ins."); + } else { + options + .getLogger() + .log( + SentryLevel.WARNING, + "The 'sentry.logs.enabled' property no longer disables manual Sentry.logger() " + + "calls. Automatic logging integrations remain disabled unless enabled " + + "through their own opt-ins."); + } + } + } + @Configuration(proxyBeanMethods = false) @ConditionalOnClass(MDC.class) @Open diff --git a/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryAutoConfigurationTest.kt b/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryAutoConfigurationTest.kt index da87abd5ee9..c3a7556e3ec 100644 --- a/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryAutoConfigurationTest.kt +++ b/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryAutoConfigurationTest.kt @@ -8,6 +8,7 @@ import io.sentry.EventProcessor import io.sentry.FilterString import io.sentry.Hint import io.sentry.IContinuousProfiler +import io.sentry.ILogger import io.sentry.IProfileConverter import io.sentry.IScopes import io.sentry.ITransportFactory @@ -56,7 +57,9 @@ import org.assertj.core.api.Assertions.assertThat import org.mockito.internal.util.MockUtil.isMock import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.eq import org.mockito.kotlin.mock +import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.quartz.JobExecutionContext @@ -194,6 +197,56 @@ class SentryAutoConfigurationTest { } } + @Test + fun `legacy logs property emits no warning when absent`() { + val logger = mock() + dsnEnabledRunner + .withPropertyValues("sentry.debug=true") + .withBean(ILogger::class.java, { logger }) + .withUserConfiguration(LoggerConfiguration::class.java) + .run { verify(logger, never()).log(eq(SentryLevel.WARNING), any()) } + } + + @Test + fun `legacy logs property true emits migration warning`() { + val logger = mock() + dsnEnabledRunner + .withPropertyValues("sentry.debug=true", "sentry.logs.enabled=true") + .withBean(ILogger::class.java, { logger }) + .withUserConfiguration(LoggerConfiguration::class.java) + .run { + verify(logger) + .log( + SentryLevel.WARNING, + "The 'sentry.logs.enabled' property is no longer supported. Manual " + + "Sentry.logger() calls no longer require it, and automatic logging " + + "integrations now require their own opt-ins.", + *emptyArray(), + ) + assertThat(it.getBean(SentryProperties::class.java).logging.isEnableLogs).isFalse() + } + } + + @Test + fun `legacy logs property false emits migration warning`() { + val logger = mock() + dsnEnabledRunner + .withPropertyValues("sentry.debug=true", "sentry.logs.enabled=false") + .withBean(ILogger::class.java, { logger }) + .withUserConfiguration(LoggerConfiguration::class.java) + .run { + verify(logger) + .log( + SentryLevel.WARNING, + "The 'sentry.logs.enabled' property no longer disables manual Sentry.logger() " + + "calls. Automatic logging integrations remain disabled unless enabled through " + + "their own opt-ins.", + *emptyArray(), + ) + assertThat(it.getBean(SentryProperties::class.java).logging.isEnableLogs).isFalse() + } + } + @Test fun `properties are applied to SentryOptions`() { contextRunner @@ -1284,6 +1337,13 @@ class SentryAutoConfigurationTest { @Bean open fun sentryTransport() = transport } + @Configuration(proxyBeanMethods = false) + open class LoggerConfiguration { + @Bean + open fun loggerConfiguration(logger: ILogger) = + Sentry.OptionsConfiguration { it.setLogger(logger) } + } + @Configuration(proxyBeanMethods = false) open class NoOpTransportConfiguration { diff --git a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java index e1f8b026274..ec79b61e121 100644 --- a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java +++ b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java @@ -11,6 +11,7 @@ import io.sentry.ScopesAdapter; import io.sentry.Sentry; import io.sentry.SentryIntegrationPackageStorage; +import io.sentry.SentryLevel; import io.sentry.SentryOptions; import io.sentry.protocol.SdkVersion; import io.sentry.quartz.SentryJobListener; @@ -165,7 +166,8 @@ static class OpenTelemetryNoAgentConfiguration {} final @NotNull List> optionsConfigurations, final @NotNull SentryProperties options, final @NotNull ObjectProvider spanFactory, - final @NotNull ObjectProvider gitProperties) { + final @NotNull ObjectProvider gitProperties, + final @NotNull Environment environment) { optionsConfigurations.forEach( optionsConfiguration -> optionsConfiguration.configure(options)); gitProperties.ifAvailable( @@ -186,10 +188,36 @@ static class OpenTelemetryNoAgentConfiguration {} // its technically possible to set non-throwable class to `ignoredExceptionsForType` set // here we make sure that only classes that extend throwable are set on this field options.getIgnoredExceptionsForType().removeIf(it -> !Throwable.class.isAssignableFrom(it)); + warnForLegacyLogsConfiguration(environment, options); Sentry.init(options); return ScopesAdapter.getInstance(); } + private void warnForLegacyLogsConfiguration( + final @NotNull Environment environment, final @NotNull SentryOptions options) { + if (environment.containsProperty("sentry.logs.enabled")) { + final boolean enableLogs = + Boolean.TRUE.equals(environment.getProperty("sentry.logs.enabled", Boolean.class)); + if (enableLogs) { + options + .getLogger() + .log( + SentryLevel.WARNING, + "The 'sentry.logs.enabled' property is no longer supported. Manual " + + "Sentry.logger() calls no longer require it, and automatic logging " + + "integrations now require their own opt-ins."); + } else { + options + .getLogger() + .log( + SentryLevel.WARNING, + "The 'sentry.logs.enabled' property no longer disables manual Sentry.logger() " + + "calls. Automatic logging integrations remain disabled unless enabled " + + "through their own opt-ins."); + } + } + } + @Configuration(proxyBeanMethods = false) @ConditionalOnClass(MDC.class) @Open diff --git a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryAutoConfigurationTest.kt b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryAutoConfigurationTest.kt index 4887ef837ff..1ce6c8071ea 100644 --- a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryAutoConfigurationTest.kt +++ b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryAutoConfigurationTest.kt @@ -9,6 +9,7 @@ import io.sentry.EventProcessor import io.sentry.FilterString import io.sentry.Hint import io.sentry.IContinuousProfiler +import io.sentry.ILogger import io.sentry.IProfileConverter import io.sentry.IScopes import io.sentry.ITransportFactory @@ -59,7 +60,9 @@ import org.assertj.core.api.Assertions.assertThat import org.mockito.internal.util.MockUtil.isMock import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.eq import org.mockito.kotlin.mock +import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.quartz.JobExecutionContext @@ -197,6 +200,56 @@ class SentryAutoConfigurationTest { } } + @Test + fun `legacy logs property emits no warning when absent`() { + val logger = mock() + dsnEnabledRunner + .withPropertyValues("sentry.debug=true") + .withBean(ILogger::class.java, { logger }) + .withUserConfiguration(LoggerConfiguration::class.java) + .run { verify(logger, never()).log(eq(SentryLevel.WARNING), any()) } + } + + @Test + fun `legacy logs property true emits migration warning`() { + val logger = mock() + dsnEnabledRunner + .withPropertyValues("sentry.debug=true", "sentry.logs.enabled=true") + .withBean(ILogger::class.java, { logger }) + .withUserConfiguration(LoggerConfiguration::class.java) + .run { + verify(logger) + .log( + SentryLevel.WARNING, + "The 'sentry.logs.enabled' property is no longer supported. Manual " + + "Sentry.logger() calls no longer require it, and automatic logging " + + "integrations now require their own opt-ins.", + *emptyArray(), + ) + assertThat(it.getBean(SentryProperties::class.java).logging.isEnableLogs).isFalse() + } + } + + @Test + fun `legacy logs property false emits migration warning`() { + val logger = mock() + dsnEnabledRunner + .withPropertyValues("sentry.debug=true", "sentry.logs.enabled=false") + .withBean(ILogger::class.java, { logger }) + .withUserConfiguration(LoggerConfiguration::class.java) + .run { + verify(logger) + .log( + SentryLevel.WARNING, + "The 'sentry.logs.enabled' property no longer disables manual Sentry.logger() " + + "calls. Automatic logging integrations remain disabled unless enabled through " + + "their own opt-ins.", + *emptyArray(), + ) + assertThat(it.getBean(SentryProperties::class.java).logging.isEnableLogs).isFalse() + } + } + @Test fun `properties are applied to SentryOptions`() { contextRunner @@ -1276,6 +1329,13 @@ class SentryAutoConfigurationTest { @Bean open fun sentryTransport() = transport } + @Configuration(proxyBeanMethods = false) + open class LoggerConfiguration { + @Bean + open fun loggerConfiguration(logger: ILogger) = + Sentry.OptionsConfiguration { it.setLogger(logger) } + } + @Configuration(proxyBeanMethods = false) open class NoOpTransportConfiguration { diff --git a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java index f89f5c5bb31..bedb72a19e2 100644 --- a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java +++ b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java @@ -10,6 +10,7 @@ import io.sentry.ScopesAdapter; import io.sentry.Sentry; import io.sentry.SentryIntegrationPackageStorage; +import io.sentry.SentryLevel; import io.sentry.SentryOptions; import io.sentry.protocol.SdkVersion; import io.sentry.quartz.SentryJobListener; @@ -160,7 +161,8 @@ static class OpenTelemetryNoAgentConfiguration {} final @NotNull List> optionsConfigurations, final @NotNull SentryProperties options, final @NotNull ObjectProvider spanFactory, - final @NotNull ObjectProvider gitProperties) { + final @NotNull ObjectProvider gitProperties, + final @NotNull Environment environment) { optionsConfigurations.forEach( optionsConfiguration -> optionsConfiguration.configure(options)); gitProperties.ifAvailable( @@ -181,10 +183,36 @@ static class OpenTelemetryNoAgentConfiguration {} // its technically possible to set non-throwable class to `ignoredExceptionsForType` set // here we make sure that only classes that extend throwable are set on this field options.getIgnoredExceptionsForType().removeIf(it -> !Throwable.class.isAssignableFrom(it)); + warnForLegacyLogsConfiguration(environment, options); Sentry.init(options); return ScopesAdapter.getInstance(); } + private void warnForLegacyLogsConfiguration( + final @NotNull Environment environment, final @NotNull SentryOptions options) { + if (environment.containsProperty("sentry.logs.enabled")) { + final boolean enableLogs = + Boolean.TRUE.equals(environment.getProperty("sentry.logs.enabled", Boolean.class)); + if (enableLogs) { + options + .getLogger() + .log( + SentryLevel.WARNING, + "The 'sentry.logs.enabled' property is no longer supported. Manual " + + "Sentry.logger() calls no longer require it, and automatic logging " + + "integrations now require their own opt-ins."); + } else { + options + .getLogger() + .log( + SentryLevel.WARNING, + "The 'sentry.logs.enabled' property no longer disables manual Sentry.logger() " + + "calls. Automatic logging integrations remain disabled unless enabled " + + "through their own opt-ins."); + } + } + } + @Configuration(proxyBeanMethods = false) @ConditionalOnClass(MDC.class) @Open diff --git a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt index 6a4a251474a..b0ee03d6577 100644 --- a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt +++ b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt @@ -9,6 +9,7 @@ import io.sentry.EventProcessor import io.sentry.FilterString import io.sentry.Hint import io.sentry.IContinuousProfiler +import io.sentry.ILogger import io.sentry.IProfileConverter import io.sentry.IScopes import io.sentry.ITransportFactory @@ -59,7 +60,9 @@ import org.assertj.core.api.Assertions.assertThat import org.mockito.internal.util.MockUtil.isMock import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.eq import org.mockito.kotlin.mock +import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.quartz.JobExecutionContext @@ -195,6 +198,56 @@ class SentryAutoConfigurationTest { } } + @Test + fun `legacy logs property emits no warning when absent`() { + val logger = mock() + dsnEnabledRunner + .withPropertyValues("sentry.debug=true") + .withBean(ILogger::class.java, { logger }) + .withUserConfiguration(LoggerConfiguration::class.java) + .run { verify(logger, never()).log(eq(SentryLevel.WARNING), any()) } + } + + @Test + fun `legacy logs property true emits migration warning`() { + val logger = mock() + dsnEnabledRunner + .withPropertyValues("sentry.debug=true", "sentry.logs.enabled=true") + .withBean(ILogger::class.java, { logger }) + .withUserConfiguration(LoggerConfiguration::class.java) + .run { + verify(logger) + .log( + SentryLevel.WARNING, + "The 'sentry.logs.enabled' property is no longer supported. Manual " + + "Sentry.logger() calls no longer require it, and automatic logging " + + "integrations now require their own opt-ins.", + *emptyArray(), + ) + assertThat(it.getBean(SentryProperties::class.java).logging.isEnableLogs).isFalse() + } + } + + @Test + fun `legacy logs property false emits migration warning`() { + val logger = mock() + dsnEnabledRunner + .withPropertyValues("sentry.debug=true", "sentry.logs.enabled=false") + .withBean(ILogger::class.java, { logger }) + .withUserConfiguration(LoggerConfiguration::class.java) + .run { + verify(logger) + .log( + SentryLevel.WARNING, + "The 'sentry.logs.enabled' property no longer disables manual Sentry.logger() " + + "calls. Automatic logging integrations remain disabled unless enabled through " + + "their own opt-ins.", + *emptyArray(), + ) + assertThat(it.getBean(SentryProperties::class.java).logging.isEnableLogs).isFalse() + } + } + @Test fun `properties are applied to SentryOptions`() { contextRunner @@ -1209,6 +1262,13 @@ class SentryAutoConfigurationTest { @Bean open fun sentryTransport() = transport } + @Configuration(proxyBeanMethods = false) + open class LoggerConfiguration { + @Bean + open fun loggerConfiguration(logger: ILogger) = + Sentry.OptionsConfiguration { it.setLogger(logger) } + } + @Configuration(proxyBeanMethods = false) open class NoOpTransportConfiguration { From fb917337e689a475be4c148b26de1f88cb8c186b Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 13 Aug 2026 05:45:21 +0200 Subject: [PATCH 4/9] perf(core): Avoid unused Logs worker thread Track whether the logger batch processor has accepted an item and skip empty flush and restart-close scheduling until then. This prevents SDK initialization and Android background callbacks from starting a worker thread when Logs are unused. Co-Authored-By: Claude --- .../core/AndroidLoggerBatchProcessorTest.kt | 14 +++ .../sentry/logger/LoggerBatchProcessor.java | 17 ++-- .../sentry/logger/LoggerBatchProcessorTest.kt | 91 +++++++++++++++++++ 3 files changed, 116 insertions(+), 6 deletions(-) diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidLoggerBatchProcessorTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidLoggerBatchProcessorTest.kt index 369f7f6a148..66ae9f4d664 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidLoggerBatchProcessorTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidLoggerBatchProcessorTest.kt @@ -1,12 +1,15 @@ package io.sentry.android.core import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat import io.sentry.ISentryClient import io.sentry.SentryLogEvent import io.sentry.SentryLogLevel import io.sentry.SentryOptions import io.sentry.protocol.SentryId import io.sentry.test.ImmediateExecutorService +import io.sentry.test.getProperty +import java.util.concurrent.atomic.AtomicBoolean import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test @@ -15,6 +18,7 @@ import kotlin.test.assertTrue import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.mock +import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -55,6 +59,16 @@ class AndroidLoggerBatchProcessorTest { assertNotNull(AppState.getInstance().lifecycleObserver) } + @Test + fun `onBackground does not flush before first accepted item`() { + val sut = fixture.getSut(useImmediateExecutor = true) + + sut.onBackground() + + assertThat(sut.getProperty("hasScheduled").get()).isFalse() + verify(fixture.client, never()).captureBatchedLogEvents(any()) + } + @Test fun `onBackground schedules flush`() { val sut = fixture.getSut(useImmediateExecutor = true) diff --git a/sentry/src/main/java/io/sentry/logger/LoggerBatchProcessor.java b/sentry/src/main/java/io/sentry/logger/LoggerBatchProcessor.java index 71877c21dae..bdfe7281268 100644 --- a/sentry/src/main/java/io/sentry/logger/LoggerBatchProcessor.java +++ b/sentry/src/main/java/io/sentry/logger/LoggerBatchProcessor.java @@ -36,6 +36,7 @@ public class LoggerBatchProcessor implements ILoggerBatchProcessor { private final @NotNull Queue queue; private final @NotNull ISentryExecutorService executorService; private final @NotNull AtomicBoolean hasScheduled = new AtomicBoolean(false); + private volatile boolean hasAcceptedItem = false; private volatile boolean isShuttingDown = false; private final @NotNull ReusableCountLatch pendingCount = new ReusableCountLatch(); @@ -75,6 +76,7 @@ public void add(final @NotNull SentryLogEvent logEvent) { } pendingCount.increment(); queue.offer(logEvent); + hasAcceptedItem = true; maybeSchedule(false); } @@ -82,14 +84,14 @@ public void add(final @NotNull SentryLogEvent logEvent) { @Override public void close(final boolean isRestarting) { isShuttingDown = true; - if (isRestarting) { + if (isRestarting && hasAcceptedItem) { maybeSchedule(true); executorService.submit(() -> executorService.close(options.getShutdownTimeoutMillis())); - } else { - executorService.close(options.getShutdownTimeoutMillis()); - while (!queue.isEmpty()) { - flushBatch(); - } + return; + } + executorService.close(options.getShutdownTimeoutMillis()); + while (!queue.isEmpty()) { + flushBatch(); } } @@ -114,6 +116,9 @@ private void maybeSchedule(boolean immediately) { @Override public void flush(long timeoutMillis) { + if (!hasAcceptedItem) { + return; + } maybeSchedule(true); try { pendingCount.waitTillZero(timeoutMillis, TimeUnit.MILLISECONDS); diff --git a/sentry/src/test/java/io/sentry/logger/LoggerBatchProcessorTest.kt b/sentry/src/test/java/io/sentry/logger/LoggerBatchProcessorTest.kt index 01b8afdb4a3..21506c2443c 100644 --- a/sentry/src/test/java/io/sentry/logger/LoggerBatchProcessorTest.kt +++ b/sentry/src/test/java/io/sentry/logger/LoggerBatchProcessorTest.kt @@ -3,6 +3,7 @@ package io.sentry.logger import com.google.common.truth.Truth.assertThat import io.sentry.DataCategory import io.sentry.ISentryClient +import io.sentry.ISentryExecutorService import io.sentry.SentryLogEvent import io.sentry.SentryLogEvents import io.sentry.SentryLogLevel @@ -13,19 +14,106 @@ import io.sentry.clientreport.DiscardReason import io.sentry.clientreport.DiscardedEvent import io.sentry.protocol.SentryId import io.sentry.test.DeferredExecutorService +import io.sentry.test.getProperty import io.sentry.test.injectForField +import io.sentry.transport.ReusableCountLatch import io.sentry.util.JsonSerializationUtils import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue +import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.atLeast import org.mockito.kotlin.mock +import org.mockito.kotlin.never import org.mockito.kotlin.times import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoInteractions class LoggerBatchProcessorTest { + @Test + fun `constructor does not submit processor work`() { + val mockExecutor = mock() + + LoggerBatchProcessor(SentryOptions(), mock(), mockExecutor) + + verifyNoInteractions(mockExecutor) + } + + @Test + fun `empty flush does not submit processor work`() { + val mockExecutor = mock() + val processor = LoggerBatchProcessor(SentryOptions(), mock(), mockExecutor) + + processor.flush(0) + + verifyNoInteractions(mockExecutor) + } + + @Test + fun `close before first accepted item does not submit processor work`() { + val mockExecutor = mock() + val processor = LoggerBatchProcessor(SentryOptions(), mock(), mockExecutor) + + processor.close(false) + + verify(mockExecutor).close(any()) + verify(mockExecutor, never()).schedule(any(), any()) + verify(mockExecutor, never()).submit(any()) + } + + @Test + fun `restart close before first accepted item does not submit processor work`() { + val mockExecutor = mock() + val processor = LoggerBatchProcessor(SentryOptions(), mock(), mockExecutor) + + processor.close(true) + + verify(mockExecutor).close(any()) + verify(mockExecutor, never()).schedule(any(), any()) + verify(mockExecutor, never()).submit(any()) + } + + @Test + fun `item rejected during shutdown does not mark processor as used`() { + val mockExecutor = mock() + val processor = LoggerBatchProcessor(SentryOptions(), mock(), mockExecutor) + processor.close(false) + + processor.add(logEvent("rejected")) + processor.flush(0) + + verify(mockExecutor, never()).schedule(any(), any()) + verify(mockExecutor, never()).submit(any()) + } + + @Test + fun `item rejected due to queue capacity does not mark processor as used`() { + val mockExecutor = mock() + val processor = LoggerBatchProcessor(SentryOptions(), mock(), mockExecutor) + val pendingCount = processor.getProperty("pendingCount") + repeat(LoggerBatchProcessor.MAX_QUEUE_SIZE) { pendingCount.increment() } + + processor.add(logEvent("rejected")) + processor.flush(0) + + verifyNoInteractions(mockExecutor) + } + + @Test + fun `flush and restart close submit processor work after first accepted item`() { + val mockExecutor = mock() + val processor = LoggerBatchProcessor(SentryOptions(), mock(), mockExecutor) + processor.add(logEvent("accepted")) + + processor.flush(0) + processor.close(true) + + verify(mockExecutor, times(3)).schedule(any(), any()) + verify(mockExecutor).submit(any()) + } + @Test fun `schedules another flush after previous flush has run`() { val mockClient = mock() @@ -46,6 +134,9 @@ class LoggerBatchProcessorTest { .inOrder() } + private fun logEvent(body: String) = + SentryLogEvent(SentryId(), SentryNanotimeDate(), body, SentryLogLevel.INFO) + @Test fun `drops log events after reaching MAX_QUEUE_SIZE limit`() { // given From 8c2f35fda514494682780363b13c287930b49852 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 13 Aug 2026 14:15:49 +0200 Subject: [PATCH 5/9] fix(spring): Warn after SDK initialization Emit legacy Logs property diagnostics after Sentry.init so the configured diagnostic logger is available. Co-Authored-By: Claude --- .../java/io/sentry/spring/boot4/SentryAutoConfiguration.java | 2 +- .../io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java | 2 +- .../java/io/sentry/spring/boot/SentryAutoConfiguration.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryAutoConfiguration.java b/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryAutoConfiguration.java index 17be2777505..62052497f1a 100644 --- a/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryAutoConfiguration.java +++ b/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryAutoConfiguration.java @@ -186,8 +186,8 @@ static class OpenTelemetryNoAgentConfiguration {} // its technically possible to set non-throwable class to `ignoredExceptionsForType` set // here we make sure that only classes that extend throwable are set on this field options.getIgnoredExceptionsForType().removeIf(it -> !Throwable.class.isAssignableFrom(it)); - warnForLegacyLogsConfiguration(environment, options); Sentry.init(options); + warnForLegacyLogsConfiguration(environment, options); return ScopesAdapter.getInstance(); } diff --git a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java index ec79b61e121..555f2dcbe31 100644 --- a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java +++ b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java @@ -188,8 +188,8 @@ static class OpenTelemetryNoAgentConfiguration {} // its technically possible to set non-throwable class to `ignoredExceptionsForType` set // here we make sure that only classes that extend throwable are set on this field options.getIgnoredExceptionsForType().removeIf(it -> !Throwable.class.isAssignableFrom(it)); - warnForLegacyLogsConfiguration(environment, options); Sentry.init(options); + warnForLegacyLogsConfiguration(environment, options); return ScopesAdapter.getInstance(); } diff --git a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java index bedb72a19e2..0d63b686291 100644 --- a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java +++ b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java @@ -183,8 +183,8 @@ static class OpenTelemetryNoAgentConfiguration {} // its technically possible to set non-throwable class to `ignoredExceptionsForType` set // here we make sure that only classes that extend throwable are set on this field options.getIgnoredExceptionsForType().removeIf(it -> !Throwable.class.isAssignableFrom(it)); - warnForLegacyLogsConfiguration(environment, options); Sentry.init(options); + warnForLegacyLogsConfiguration(environment, options); return ScopesAdapter.getInstance(); } From 56278c84cea565046bc33b50d633c101bd499524 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Tue, 29 Sep 2026 16:09:07 +0200 Subject: [PATCH 6/9] fix(android): Use manifest metadata lookup helper Keep legacy Logs metadata detection compatible with both Android bundles and injected metadata maps after merging the v9 branch. --- .../java/io/sentry/android/core/ManifestMetadataReader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java index 1b7acc5f8e1..654a40c5c92 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java @@ -754,7 +754,7 @@ static void applyMetadata( } } - if (metadata.containsKey(ENABLE_LOGS)) { + if (containsKey(metadata, ENABLE_LOGS)) { final boolean enableLogs = readBool(metadata, logger, ENABLE_LOGS, false); if (enableLogs) { logger.log( From 3f987ba59bf08d7470c4ecc49765626c57e377a5 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 30 Sep 2026 10:39:02 +0200 Subject: [PATCH 7/9] ref(android): Remove legacy Logs metadata warnings Drop the warning-only behavior so the PR is neutral before removal from the v9 stack. Co-Authored-By: Claude --- .../android/core/ManifestMetadataReader.java | 17 ------ .../core/ManifestMetadataReaderTest.kt | 60 +------------------ 2 files changed, 1 insertion(+), 76 deletions(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java index 36b00f91e6a..2bf1f1827a8 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java @@ -752,23 +752,6 @@ static void applyMetadata( } } - if (containsKey(metadata, ENABLE_LOGS)) { - final boolean enableLogs = readBool(metadata, logger, ENABLE_LOGS, false); - if (enableLogs) { - logger.log( - SentryLevel.WARNING, - "The Android manifest option 'io.sentry.logs.enabled' is no longer supported. " - + "Manual Sentry.logger() calls no longer require it, and automatic logging " - + "integrations now require their own opt-ins."); - } else { - logger.log( - SentryLevel.WARNING, - "The Android manifest option 'io.sentry.logs.enabled' no longer disables manual " - + "Sentry.logger() calls. Automatic logging integrations remain disabled " - + "unless enabled through their own opt-ins."); - } - } - options.setEnableTimberLogs( readBool(metadata, logger, ENABLE_TIMBER_LOGS, options.isEnableTimberLogs())); diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt index 0d5ad2b2c62..91a1469702f 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt @@ -33,7 +33,7 @@ import org.mockito.kotlin.verifyNoInteractions class ManifestMetadataReaderTest { private class Fixture { val logger = mock() - val options = SentryAndroidOptions().apply { setLogger(this@Fixture.logger) } + val options = SentryAndroidOptions().apply { setLogger(logger) } val buildInfoProvider = mock() fun getContext(metaData: Bundle = Bundle()): Context = @@ -2175,64 +2175,6 @@ class ManifestMetadataReaderTest { assertTrue(fixture.options.inAppExcludes.isEmpty()) } - @Test - fun `applyMetadata does not warn when legacy logs enabled metadata is absent`() { - fixture.options.isDebug = true - val context = fixture.getContext() - - ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) - - verify(fixture.logger, never()).log(eq(SentryLevel.WARNING), any()) - } - - @Test - fun `applyMetadata warns when legacy logs enabled metadata is true`() { - val bundle = - bundleOf( - ManifestMetadataReader.DEBUG to true, - ManifestMetadataReader.ENABLE_LOGS to true, - ) - val context = fixture.getContext(metaData = bundle) - - ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) - - verify(fixture.logger) - .log( - SentryLevel.WARNING, - "The Android manifest option 'io.sentry.logs.enabled' is no longer supported. " + - "Manual Sentry.logger() calls no longer require it, and automatic logging " + - "integrations now require their own opt-ins.", - *emptyArray(), - ) - assertThat(fixture.options.isEnableTimberLogs).isFalse() - assertThat(fixture.options.isEnableLogcatLogs).isFalse() - } - - @Test - fun `applyMetadata warns when legacy logs enabled metadata is false`() { - fixture.options.isEnableTimberLogs = true - fixture.options.isEnableLogcatLogs = true - val bundle = - bundleOf( - ManifestMetadataReader.DEBUG to true, - ManifestMetadataReader.ENABLE_LOGS to false, - ) - val context = fixture.getContext(metaData = bundle) - - ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) - - verify(fixture.logger) - .log( - SentryLevel.WARNING, - "The Android manifest option 'io.sentry.logs.enabled' no longer disables manual " + - "Sentry.logger() calls. Automatic logging integrations remain disabled unless " + - "enabled through their own opt-ins.", - *emptyArray(), - ) - assertThat(fixture.options.isEnableTimberLogs).isTrue() - assertThat(fixture.options.isEnableLogcatLogs).isTrue() - } - @Test fun `applyMetadata keeps Timber logs disabled if not found`() { val context = fixture.getContext() From 95e50e260a9ea4041143f5b7370a71bddf4cdc5c Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 30 Sep 2026 10:40:13 +0200 Subject: [PATCH 8/9] ref(core): Remove legacy Logs configuration warnings Drop the warning-only behavior so the PR is neutral before removal from the v9 stack. Co-Authored-By: Claude --- .../main/java/io/sentry/SentryOptions.java | 15 ---- .../test/java/io/sentry/SentryOptionsTest.kt | 72 ------------------- 2 files changed, 87 deletions(-) diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 13edbd390e4..adb35d35ba8 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -3834,21 +3834,6 @@ public void merge(final @NotNull ExternalOptions options) { } } - if (options.isEnableLogs() != null) { - if (options.isEnableLogs()) { - logger.log( - SentryLevel.WARNING, - "The 'logs.enabled' option is no longer supported. Manual Sentry.logger() calls no " - + "longer require it, and automatic logging integrations now require their own " - + "opt-ins."); - } else { - logger.log( - SentryLevel.WARNING, - "The 'logs.enabled' option no longer disables manual Sentry.logger() calls. Automatic " - + "logging integrations remain disabled unless enabled through their own opt-ins."); - } - } - if (options.isEnableMetrics() != null) { getMetrics().setEnabled(options.isEnableMetrics()); } diff --git a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt index 58b9fbada23..ac16dc7e941 100644 --- a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt @@ -3,9 +3,6 @@ package io.sentry import com.google.common.truth.Truth.assertThat import io.sentry.SentryOptions.RequestSize import io.sentry.logger.ILoggerBatchProcessorFactory -import io.sentry.logger.LoggerApi -import io.sentry.test.createSentryClientMock -import io.sentry.test.createTestScopes import io.sentry.util.StringUtils import java.io.File import java.net.Proxy @@ -19,11 +16,8 @@ import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertSame import kotlin.test.assertTrue -import org.mockito.kotlin.any -import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.eq import org.mockito.kotlin.mock -import org.mockito.kotlin.never import org.mockito.kotlin.verify class SentryOptionsTest { @@ -670,72 +664,6 @@ class SentryOptionsTest { assertTrue(options.metrics.isEnabled) } - @Test - fun `merging options does not warn when legacy logs configuration is absent`() { - val logger = mock() - val options = - SentryOptions().also { - it.isDebug = true - it.setLogger(logger) - } - - options.merge(ExternalOptions()) - - verify(logger, never()).log(eq(SentryLevel.WARNING), any()) - } - - @Test - fun `merging options warns when legacy logs configuration is true`() { - val logger = mock() - val options = - SentryOptions().also { - it.isDebug = true - it.setLogger(logger) - } - - options.merge(ExternalOptions().apply { isEnableLogs = true }) - - verify(logger) - .log( - SentryLevel.WARNING, - "The 'logs.enabled' option is no longer supported. Manual Sentry.logger() calls no " + - "longer require it, and automatic logging integrations now require their own opt-ins.", - *emptyArray(), - ) - assertLegacyLogsConfigurationDoesNotDisableCapture(options) - } - - @Test - fun `merging options warns when legacy logs configuration is false`() { - val logger = mock() - val options = - SentryOptions().also { - it.isDebug = true - it.setLogger(logger) - } - - options.merge(ExternalOptions().apply { isEnableLogs = false }) - - verify(logger) - .log( - SentryLevel.WARNING, - "The 'logs.enabled' option no longer disables manual Sentry.logger() calls. Automatic " + - "logging integrations remain disabled unless enabled through their own opt-ins.", - *emptyArray(), - ) - assertLegacyLogsConfigurationDoesNotDisableCapture(options) - } - - private fun assertLegacyLogsConfigurationDoesNotDisableCapture(options: SentryOptions) { - options.dsn = "https://key@sentry.io/proj" - val client = createSentryClientMock() - val scopes = createTestScopes(options).apply { bindClient(client) } - - LoggerApi(scopes).info("test log") - - verify(client).captureLog(any(), anyOrNull()) - } - @Test fun `merging options merges and overwrites existing tag values`() { val externalOptions = ExternalOptions() From 4f318e5b12939e5c3067e0c83efdc5946efc2050 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 30 Sep 2026 10:40:49 +0200 Subject: [PATCH 9/9] ref(spring): Remove legacy Logs property warnings Drop the warning-only behavior so the PR is neutral before removal from the v9 stack. Co-Authored-By: Claude --- .../spring/boot4/SentryAutoConfiguration.java | 30 +--------- .../boot4/SentryAutoConfigurationTest.kt | 60 ------------------- .../boot/jakarta/SentryAutoConfiguration.java | 30 +--------- .../jakarta/SentryAutoConfigurationTest.kt | 60 ------------------- .../spring/boot/SentryAutoConfiguration.java | 30 +--------- .../boot/SentryAutoConfigurationTest.kt | 60 ------------------- 6 files changed, 3 insertions(+), 267 deletions(-) diff --git a/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryAutoConfiguration.java b/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryAutoConfiguration.java index 62052497f1a..2429c1e7446 100644 --- a/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryAutoConfiguration.java +++ b/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryAutoConfiguration.java @@ -11,7 +11,6 @@ import io.sentry.ScopesAdapter; import io.sentry.Sentry; import io.sentry.SentryIntegrationPackageStorage; -import io.sentry.SentryLevel; import io.sentry.SentryOptions; import io.sentry.protocol.SdkVersion; import io.sentry.quartz.SentryJobListener; @@ -164,8 +163,7 @@ static class OpenTelemetryNoAgentConfiguration {} final @NotNull List> optionsConfigurations, final @NotNull SentryProperties options, final @NotNull ObjectProvider spanFactory, - final @NotNull ObjectProvider gitProperties, - final @NotNull Environment environment) { + final @NotNull ObjectProvider gitProperties) { optionsConfigurations.forEach( optionsConfiguration -> optionsConfiguration.configure(options)); gitProperties.ifAvailable( @@ -187,35 +185,9 @@ static class OpenTelemetryNoAgentConfiguration {} // here we make sure that only classes that extend throwable are set on this field options.getIgnoredExceptionsForType().removeIf(it -> !Throwable.class.isAssignableFrom(it)); Sentry.init(options); - warnForLegacyLogsConfiguration(environment, options); return ScopesAdapter.getInstance(); } - private void warnForLegacyLogsConfiguration( - final @NotNull Environment environment, final @NotNull SentryOptions options) { - if (environment.containsProperty("sentry.logs.enabled")) { - final boolean enableLogs = - Boolean.TRUE.equals(environment.getProperty("sentry.logs.enabled", Boolean.class)); - if (enableLogs) { - options - .getLogger() - .log( - SentryLevel.WARNING, - "The 'sentry.logs.enabled' property is no longer supported. Manual " - + "Sentry.logger() calls no longer require it, and automatic logging " - + "integrations now require their own opt-ins."); - } else { - options - .getLogger() - .log( - SentryLevel.WARNING, - "The 'sentry.logs.enabled' property no longer disables manual Sentry.logger() " - + "calls. Automatic logging integrations remain disabled unless enabled " - + "through their own opt-ins."); - } - } - } - @Configuration(proxyBeanMethods = false) @ConditionalOnClass(MDC.class) @Open diff --git a/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryAutoConfigurationTest.kt b/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryAutoConfigurationTest.kt index df511711e68..e8ec76cdbbb 100644 --- a/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryAutoConfigurationTest.kt +++ b/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryAutoConfigurationTest.kt @@ -8,7 +8,6 @@ import io.sentry.EventProcessor import io.sentry.FilterString import io.sentry.Hint import io.sentry.IContinuousProfiler -import io.sentry.ILogger import io.sentry.IProfileConverter import io.sentry.IScopes import io.sentry.ITransportFactory @@ -58,9 +57,7 @@ import org.assertj.core.api.Assertions.assertThat import org.mockito.internal.util.MockUtil.isMock import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull -import org.mockito.kotlin.eq import org.mockito.kotlin.mock -import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.quartz.JobExecutionContext @@ -198,56 +195,6 @@ class SentryAutoConfigurationTest { } } - @Test - fun `legacy logs property emits no warning when absent`() { - val logger = mock() - dsnEnabledRunner - .withPropertyValues("sentry.debug=true") - .withBean(ILogger::class.java, { logger }) - .withUserConfiguration(LoggerConfiguration::class.java) - .run { verify(logger, never()).log(eq(SentryLevel.WARNING), any()) } - } - - @Test - fun `legacy logs property true emits migration warning`() { - val logger = mock() - dsnEnabledRunner - .withPropertyValues("sentry.debug=true", "sentry.logs.enabled=true") - .withBean(ILogger::class.java, { logger }) - .withUserConfiguration(LoggerConfiguration::class.java) - .run { - verify(logger) - .log( - SentryLevel.WARNING, - "The 'sentry.logs.enabled' property is no longer supported. Manual " + - "Sentry.logger() calls no longer require it, and automatic logging " + - "integrations now require their own opt-ins.", - *emptyArray(), - ) - assertThat(it.getBean(SentryProperties::class.java).logging.isEnableLogs).isFalse() - } - } - - @Test - fun `legacy logs property false emits migration warning`() { - val logger = mock() - dsnEnabledRunner - .withPropertyValues("sentry.debug=true", "sentry.logs.enabled=false") - .withBean(ILogger::class.java, { logger }) - .withUserConfiguration(LoggerConfiguration::class.java) - .run { - verify(logger) - .log( - SentryLevel.WARNING, - "The 'sentry.logs.enabled' property no longer disables manual Sentry.logger() " + - "calls. Automatic logging integrations remain disabled unless enabled through " + - "their own opt-ins.", - *emptyArray(), - ) - assertThat(it.getBean(SentryProperties::class.java).logging.isEnableLogs).isFalse() - } - } - @Test fun `properties are applied to SentryOptions`() { contextRunner @@ -1369,13 +1316,6 @@ class SentryAutoConfigurationTest { @Bean open fun sentryTransport() = transport } - @Configuration(proxyBeanMethods = false) - open class LoggerConfiguration { - @Bean - open fun loggerConfiguration(logger: ILogger) = - Sentry.OptionsConfiguration { it.setLogger(logger) } - } - @Configuration(proxyBeanMethods = false) open class NoOpTransportConfiguration { diff --git a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java index 555f2dcbe31..e1f8b026274 100644 --- a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java +++ b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java @@ -11,7 +11,6 @@ import io.sentry.ScopesAdapter; import io.sentry.Sentry; import io.sentry.SentryIntegrationPackageStorage; -import io.sentry.SentryLevel; import io.sentry.SentryOptions; import io.sentry.protocol.SdkVersion; import io.sentry.quartz.SentryJobListener; @@ -166,8 +165,7 @@ static class OpenTelemetryNoAgentConfiguration {} final @NotNull List> optionsConfigurations, final @NotNull SentryProperties options, final @NotNull ObjectProvider spanFactory, - final @NotNull ObjectProvider gitProperties, - final @NotNull Environment environment) { + final @NotNull ObjectProvider gitProperties) { optionsConfigurations.forEach( optionsConfiguration -> optionsConfiguration.configure(options)); gitProperties.ifAvailable( @@ -189,35 +187,9 @@ static class OpenTelemetryNoAgentConfiguration {} // here we make sure that only classes that extend throwable are set on this field options.getIgnoredExceptionsForType().removeIf(it -> !Throwable.class.isAssignableFrom(it)); Sentry.init(options); - warnForLegacyLogsConfiguration(environment, options); return ScopesAdapter.getInstance(); } - private void warnForLegacyLogsConfiguration( - final @NotNull Environment environment, final @NotNull SentryOptions options) { - if (environment.containsProperty("sentry.logs.enabled")) { - final boolean enableLogs = - Boolean.TRUE.equals(environment.getProperty("sentry.logs.enabled", Boolean.class)); - if (enableLogs) { - options - .getLogger() - .log( - SentryLevel.WARNING, - "The 'sentry.logs.enabled' property is no longer supported. Manual " - + "Sentry.logger() calls no longer require it, and automatic logging " - + "integrations now require their own opt-ins."); - } else { - options - .getLogger() - .log( - SentryLevel.WARNING, - "The 'sentry.logs.enabled' property no longer disables manual Sentry.logger() " - + "calls. Automatic logging integrations remain disabled unless enabled " - + "through their own opt-ins."); - } - } - } - @Configuration(proxyBeanMethods = false) @ConditionalOnClass(MDC.class) @Open diff --git a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryAutoConfigurationTest.kt b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryAutoConfigurationTest.kt index c396c61f1d2..6d584c9d609 100644 --- a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryAutoConfigurationTest.kt +++ b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryAutoConfigurationTest.kt @@ -9,7 +9,6 @@ import io.sentry.EventProcessor import io.sentry.FilterString import io.sentry.Hint import io.sentry.IContinuousProfiler -import io.sentry.ILogger import io.sentry.IProfileConverter import io.sentry.IScopes import io.sentry.ITransportFactory @@ -61,9 +60,7 @@ import org.assertj.core.api.Assertions.assertThat import org.mockito.internal.util.MockUtil.isMock import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull -import org.mockito.kotlin.eq import org.mockito.kotlin.mock -import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.quartz.JobExecutionContext @@ -201,56 +198,6 @@ class SentryAutoConfigurationTest { } } - @Test - fun `legacy logs property emits no warning when absent`() { - val logger = mock() - dsnEnabledRunner - .withPropertyValues("sentry.debug=true") - .withBean(ILogger::class.java, { logger }) - .withUserConfiguration(LoggerConfiguration::class.java) - .run { verify(logger, never()).log(eq(SentryLevel.WARNING), any()) } - } - - @Test - fun `legacy logs property true emits migration warning`() { - val logger = mock() - dsnEnabledRunner - .withPropertyValues("sentry.debug=true", "sentry.logs.enabled=true") - .withBean(ILogger::class.java, { logger }) - .withUserConfiguration(LoggerConfiguration::class.java) - .run { - verify(logger) - .log( - SentryLevel.WARNING, - "The 'sentry.logs.enabled' property is no longer supported. Manual " + - "Sentry.logger() calls no longer require it, and automatic logging " + - "integrations now require their own opt-ins.", - *emptyArray(), - ) - assertThat(it.getBean(SentryProperties::class.java).logging.isEnableLogs).isFalse() - } - } - - @Test - fun `legacy logs property false emits migration warning`() { - val logger = mock() - dsnEnabledRunner - .withPropertyValues("sentry.debug=true", "sentry.logs.enabled=false") - .withBean(ILogger::class.java, { logger }) - .withUserConfiguration(LoggerConfiguration::class.java) - .run { - verify(logger) - .log( - SentryLevel.WARNING, - "The 'sentry.logs.enabled' property no longer disables manual Sentry.logger() " + - "calls. Automatic logging integrations remain disabled unless enabled through " + - "their own opt-ins.", - *emptyArray(), - ) - assertThat(it.getBean(SentryProperties::class.java).logging.isEnableLogs).isFalse() - } - } - @Test fun `properties are applied to SentryOptions`() { contextRunner @@ -1361,13 +1308,6 @@ class SentryAutoConfigurationTest { @Bean open fun sentryTransport() = transport } - @Configuration(proxyBeanMethods = false) - open class LoggerConfiguration { - @Bean - open fun loggerConfiguration(logger: ILogger) = - Sentry.OptionsConfiguration { it.setLogger(logger) } - } - @Configuration(proxyBeanMethods = false) open class NoOpTransportConfiguration { diff --git a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java index 0d63b686291..f89f5c5bb31 100644 --- a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java +++ b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java @@ -10,7 +10,6 @@ import io.sentry.ScopesAdapter; import io.sentry.Sentry; import io.sentry.SentryIntegrationPackageStorage; -import io.sentry.SentryLevel; import io.sentry.SentryOptions; import io.sentry.protocol.SdkVersion; import io.sentry.quartz.SentryJobListener; @@ -161,8 +160,7 @@ static class OpenTelemetryNoAgentConfiguration {} final @NotNull List> optionsConfigurations, final @NotNull SentryProperties options, final @NotNull ObjectProvider spanFactory, - final @NotNull ObjectProvider gitProperties, - final @NotNull Environment environment) { + final @NotNull ObjectProvider gitProperties) { optionsConfigurations.forEach( optionsConfiguration -> optionsConfiguration.configure(options)); gitProperties.ifAvailable( @@ -184,35 +182,9 @@ static class OpenTelemetryNoAgentConfiguration {} // here we make sure that only classes that extend throwable are set on this field options.getIgnoredExceptionsForType().removeIf(it -> !Throwable.class.isAssignableFrom(it)); Sentry.init(options); - warnForLegacyLogsConfiguration(environment, options); return ScopesAdapter.getInstance(); } - private void warnForLegacyLogsConfiguration( - final @NotNull Environment environment, final @NotNull SentryOptions options) { - if (environment.containsProperty("sentry.logs.enabled")) { - final boolean enableLogs = - Boolean.TRUE.equals(environment.getProperty("sentry.logs.enabled", Boolean.class)); - if (enableLogs) { - options - .getLogger() - .log( - SentryLevel.WARNING, - "The 'sentry.logs.enabled' property is no longer supported. Manual " - + "Sentry.logger() calls no longer require it, and automatic logging " - + "integrations now require their own opt-ins."); - } else { - options - .getLogger() - .log( - SentryLevel.WARNING, - "The 'sentry.logs.enabled' property no longer disables manual Sentry.logger() " - + "calls. Automatic logging integrations remain disabled unless enabled " - + "through their own opt-ins."); - } - } - } - @Configuration(proxyBeanMethods = false) @ConditionalOnClass(MDC.class) @Open diff --git a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt index 16b646e3786..bdc166c15e3 100644 --- a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt +++ b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt @@ -9,7 +9,6 @@ import io.sentry.EventProcessor import io.sentry.FilterString import io.sentry.Hint import io.sentry.IContinuousProfiler -import io.sentry.ILogger import io.sentry.IProfileConverter import io.sentry.IScopes import io.sentry.ITransportFactory @@ -61,9 +60,7 @@ import org.assertj.core.api.Assertions.assertThat import org.mockito.internal.util.MockUtil.isMock import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull -import org.mockito.kotlin.eq import org.mockito.kotlin.mock -import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.quartz.JobExecutionContext @@ -199,56 +196,6 @@ class SentryAutoConfigurationTest { } } - @Test - fun `legacy logs property emits no warning when absent`() { - val logger = mock() - dsnEnabledRunner - .withPropertyValues("sentry.debug=true") - .withBean(ILogger::class.java, { logger }) - .withUserConfiguration(LoggerConfiguration::class.java) - .run { verify(logger, never()).log(eq(SentryLevel.WARNING), any()) } - } - - @Test - fun `legacy logs property true emits migration warning`() { - val logger = mock() - dsnEnabledRunner - .withPropertyValues("sentry.debug=true", "sentry.logs.enabled=true") - .withBean(ILogger::class.java, { logger }) - .withUserConfiguration(LoggerConfiguration::class.java) - .run { - verify(logger) - .log( - SentryLevel.WARNING, - "The 'sentry.logs.enabled' property is no longer supported. Manual " + - "Sentry.logger() calls no longer require it, and automatic logging " + - "integrations now require their own opt-ins.", - *emptyArray(), - ) - assertThat(it.getBean(SentryProperties::class.java).logging.isEnableLogs).isFalse() - } - } - - @Test - fun `legacy logs property false emits migration warning`() { - val logger = mock() - dsnEnabledRunner - .withPropertyValues("sentry.debug=true", "sentry.logs.enabled=false") - .withBean(ILogger::class.java, { logger }) - .withUserConfiguration(LoggerConfiguration::class.java) - .run { - verify(logger) - .log( - SentryLevel.WARNING, - "The 'sentry.logs.enabled' property no longer disables manual Sentry.logger() " + - "calls. Automatic logging integrations remain disabled unless enabled through " + - "their own opt-ins.", - *emptyArray(), - ) - assertThat(it.getBean(SentryProperties::class.java).logging.isEnableLogs).isFalse() - } - } - @Test fun `properties are applied to SentryOptions`() { contextRunner @@ -1294,13 +1241,6 @@ class SentryAutoConfigurationTest { @Bean open fun sentryTransport() = transport } - @Configuration(proxyBeanMethods = false) - open class LoggerConfiguration { - @Bean - open fun loggerConfiguration(logger: ILogger) = - Sentry.OptionsConfiguration { it.setLogger(logger) } - } - @Configuration(proxyBeanMethods = false) open class NoOpTransportConfiguration {