From 419986b50bd89516e61d6e30c341d1b3efbd3bd1 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 13 Aug 2026 05:35:55 +0200 Subject: [PATCH 1/2] 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 8c2f35fda514494682780363b13c287930b49852 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 13 Aug 2026 14:15:49 +0200 Subject: [PATCH 2/2] 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(); }