Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -163,7 +164,8 @@ static class OpenTelemetryNoAgentConfiguration {}
final @NotNull List<Sentry.OptionsConfiguration<SentryOptions>> optionsConfigurations,
final @NotNull SentryProperties options,
final @NotNull ObjectProvider<ISpanFactory> spanFactory,
final @NotNull ObjectProvider<GitProperties> gitProperties) {
final @NotNull ObjectProvider<GitProperties> gitProperties,
final @NotNull Environment environment) {
optionsConfigurations.forEach(
optionsConfiguration -> optionsConfiguration.configure(options));
gitProperties.ifAvailable(
Expand All @@ -185,9 +187,35 @@ 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -194,6 +197,56 @@ class SentryAutoConfigurationTest {
}
}

@Test
fun `legacy logs property emits no warning when absent`() {
val logger = mock<ILogger>()
dsnEnabledRunner
.withPropertyValues("sentry.debug=true")
.withBean(ILogger::class.java, { logger })
.withUserConfiguration(LoggerConfiguration::class.java)
.run { verify(logger, never()).log(eq(SentryLevel.WARNING), any<String>()) }
}

@Test
fun `legacy logs property true emits migration warning`() {
val logger = mock<ILogger>()
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<ILogger>()
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
Expand Down Expand Up @@ -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<SentryOptions> { it.setLogger(logger) }
}

@Configuration(proxyBeanMethods = false)
open class NoOpTransportConfiguration {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -165,7 +166,8 @@ static class OpenTelemetryNoAgentConfiguration {}
final @NotNull List<Sentry.OptionsConfiguration<SentryOptions>> optionsConfigurations,
final @NotNull SentryProperties options,
final @NotNull ObjectProvider<ISpanFactory> spanFactory,
final @NotNull ObjectProvider<GitProperties> gitProperties) {
final @NotNull ObjectProvider<GitProperties> gitProperties,
final @NotNull Environment environment) {
optionsConfigurations.forEach(
optionsConfiguration -> optionsConfiguration.configure(options));
gitProperties.ifAvailable(
Expand All @@ -187,9 +189,35 @@ 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) {
Comment on lines +200 to +201

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The application will crash on startup if sentry.logs.enabled is set to a non-boolean value, as the call to environment.getProperty lacks error handling for invalid conversions.
Severity: HIGH

Suggested Fix

Wrap the environment.getProperty("sentry.logs.enabled", Boolean.class) call in a try-catch block to handle ConversionFailedException. Log a warning if the property value is invalid and default to a sensible value (e.g., false) to allow the application to start successfully. This makes the configuration more robust against user error.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location:
sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java#L200-L201

Potential issue: The code checks for the existence of the `sentry.logs.enabled` property
but does not validate its value before attempting to convert it to a `Boolean`. If a
user provides a non-boolean string (e.g., "enabled" instead of "true"), Spring's
`StringToBooleanConverter` will throw a `ConversionFailedException`. Since this
exception is not caught during the creation of the `sentryHub` bean, it will propagate
up and cause the entire Spring application context to fail to initialize, preventing the
application from starting.

Also affects:

  • sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java:195~196
  • sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryAutoConfiguration.java:198~199

Did we get this right? 👍 / 👎 to inform future reviews.

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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -197,6 +200,56 @@ class SentryAutoConfigurationTest {
}
}

@Test
fun `legacy logs property emits no warning when absent`() {
val logger = mock<ILogger>()
dsnEnabledRunner
.withPropertyValues("sentry.debug=true")
.withBean(ILogger::class.java, { logger })
.withUserConfiguration(LoggerConfiguration::class.java)
.run { verify(logger, never()).log(eq(SentryLevel.WARNING), any<String>()) }
}

@Test
fun `legacy logs property true emits migration warning`() {
val logger = mock<ILogger>()
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<ILogger>()
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
Expand Down Expand Up @@ -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<SentryOptions> { it.setLogger(logger) }
}

@Configuration(proxyBeanMethods = false)
open class NoOpTransportConfiguration {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -160,7 +161,8 @@ static class OpenTelemetryNoAgentConfiguration {}
final @NotNull List<Sentry.OptionsConfiguration<SentryOptions>> optionsConfigurations,
final @NotNull SentryProperties options,
final @NotNull ObjectProvider<ISpanFactory> spanFactory,
final @NotNull ObjectProvider<GitProperties> gitProperties) {
final @NotNull ObjectProvider<GitProperties> gitProperties,
final @NotNull Environment environment) {
optionsConfigurations.forEach(
optionsConfiguration -> optionsConfiguration.configure(options));
gitProperties.ifAvailable(
Expand All @@ -182,9 +184,35 @@ 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
Expand Down
Loading
Loading