From ae8f80434b506a6ba1393ea547641b0b973de05b Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 12 Aug 2026 16:34:27 +0200 Subject: [PATCH 1/3] feat(spring): Add logging Logs opt-in Add a Spring Boot logging property that controls Sentry Logs forwarding from auto-configured Logback appenders. Keep appender installation, events, and breadcrumbs independent from the new opt-in. Co-Authored-By: Claude --- .../src/main/resources/application.properties | 1 + .../src/main/resources/application.properties | 1 + .../src/main/resources/application.properties | 1 + .../src/main/resources/application.properties | 1 + .../src/main/resources/application.properties | 1 + .../src/main/resources/application.properties | 1 + .../src/main/resources/application.properties | 1 + .../src/main/resources/application.properties | 1 + .../src/main/resources/application.properties | 1 + .../src/main/resources/application.properties | 1 + .../src/main/resources/application.properties | 1 + .../src/main/resources/application.properties | 1 + .../src/main/resources/application.properties | 1 + sentry-spring-boot-4/api/sentry-spring-boot-4.api | 2 ++ .../sentry/spring/boot4/SentryLogbackInitializer.java | 1 + .../java/io/sentry/spring/boot4/SentryProperties.java | 11 +++++++++++ .../spring/boot4/SentryAutoConfigurationTest.kt | 2 ++ .../SentryLogbackAppenderAutoConfigurationTest.kt | 11 +++++++++++ .../api/sentry-spring-boot-jakarta.api | 2 ++ .../spring/boot/jakarta/SentryLogbackInitializer.java | 1 + .../sentry/spring/boot/jakarta/SentryProperties.java | 11 +++++++++++ .../boot/jakarta/SentryAutoConfigurationTest.kt | 2 ++ .../SentryLogbackAppenderAutoConfigurationTest.kt | 11 +++++++++++ sentry-spring-boot/api/sentry-spring-boot.api | 2 ++ .../sentry/spring/boot/SentryLogbackInitializer.java | 1 + .../java/io/sentry/spring/boot/SentryProperties.java | 11 +++++++++++ .../sentry/spring/boot/SentryAutoConfigurationTest.kt | 2 ++ .../SentryLogbackAppenderAutoConfigurationTest.kt | 11 +++++++++++ 28 files changed, 94 insertions(+) diff --git a/sentry-samples/sentry-samples-spring-boot-4-opentelemetry-noagent/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring-boot-4-opentelemetry-noagent/src/main/resources/application.properties index d19c33a3d1b..9e53be98324 100644 --- a/sentry-samples/sentry-samples-spring-boot-4-opentelemetry-noagent/src/main/resources/application.properties +++ b/sentry-samples/sentry-samples-spring-boot-4-opentelemetry-noagent/src/main/resources/application.properties @@ -16,6 +16,7 @@ sentry.enable-backpressure-handling=true sentry.enable-spotlight=true sentry.enablePrettySerializationOutput=false sentry.logs.enabled=true +sentry.logging.enable-logs=true sentry.in-app-includes="io.sentry.samples" sentry.profile-session-sample-rate=1.0 sentry.profiling-traces-dir-path=tmp/sentry/profiling-traces diff --git a/sentry-samples/sentry-samples-spring-boot-4-opentelemetry/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring-boot-4-opentelemetry/src/main/resources/application.properties index a0808e04fde..3d19e15d7cb 100644 --- a/sentry-samples/sentry-samples-spring-boot-4-opentelemetry/src/main/resources/application.properties +++ b/sentry-samples/sentry-samples-spring-boot-4-opentelemetry/src/main/resources/application.properties @@ -16,6 +16,7 @@ sentry.enable-backpressure-handling=true sentry.enable-spotlight=true sentry.enablePrettySerializationOutput=false sentry.logs.enabled=true +sentry.logging.enable-logs=true sentry.in-app-includes="io.sentry.samples" sentry.profile-session-sample-rate=1.0 sentry.profiling-traces-dir-path=tmp/sentry/profiling-traces diff --git a/sentry-samples/sentry-samples-spring-boot-4-otlp/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring-boot-4-otlp/src/main/resources/application.properties index f9b35099062..483ae19db6d 100644 --- a/sentry-samples/sentry-samples-spring-boot-4-otlp/src/main/resources/application.properties +++ b/sentry-samples/sentry-samples-spring-boot-4-otlp/src/main/resources/application.properties @@ -17,6 +17,7 @@ sentry.enable-spotlight=true sentry.enablePrettySerializationOutput=false sentry.in-app-includes="io.sentry.samples" sentry.logs.enabled=true +sentry.logging.enable-logs=true sentry.profile-session-sample-rate=1.0 sentry.profiling-traces-dir-path=tmp/sentry/profiling-traces sentry.profile-lifecycle=TRACE diff --git a/sentry-samples/sentry-samples-spring-boot-4-webflux/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring-boot-4-webflux/src/main/resources/application.properties index 9fc969efd28..130da1d07cb 100644 --- a/sentry-samples/sentry-samples-spring-boot-4-webflux/src/main/resources/application.properties +++ b/sentry-samples/sentry-samples-spring-boot-4-webflux/src/main/resources/application.properties @@ -11,6 +11,7 @@ sentry.reactive.thread-local-accessor-enabled=true sentry.traces-sample-rate=1.0 sentry.enable-backpressure-handling=true sentry.logs.enabled=true +sentry.logging.enable-logs=true sentry.enable-spotlight=true sentry.profile-session-sample-rate=1.0 sentry.profiling-traces-dir-path=tmp/sentry/profiling-traces diff --git a/sentry-samples/sentry-samples-spring-boot-4/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring-boot-4/src/main/resources/application.properties index 8198059343a..d52b74778c0 100644 --- a/sentry-samples/sentry-samples-spring-boot-4/src/main/resources/application.properties +++ b/sentry-samples/sentry-samples-spring-boot-4/src/main/resources/application.properties @@ -17,6 +17,7 @@ sentry.enable-spotlight=true sentry.enablePrettySerializationOutput=false sentry.in-app-includes="io.sentry.samples" sentry.logs.enabled=true +sentry.logging.enable-logs=true sentry.profile-session-sample-rate=1.0 sentry.profiling-traces-dir-path=tmp/sentry/profiling-traces sentry.profile-lifecycle=TRACE diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/main/resources/application.properties index a3a59d290b1..7512b72c554 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/main/resources/application.properties +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/src/main/resources/application.properties @@ -16,6 +16,7 @@ sentry.enable-backpressure-handling=true sentry.enable-spotlight=true sentry.enablePrettySerializationOutput=false sentry.logs.enabled=true +sentry.logging.enable-logs=true sentry.in-app-includes="io.sentry.samples" sentry.profile-session-sample-rate=1.0 sentry.profiling-traces-dir-path=tmp/sentry/profiling-traces diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/resources/application.properties index 12a9ca17269..78b3894a49e 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/resources/application.properties +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/resources/application.properties @@ -16,6 +16,7 @@ sentry.enable-backpressure-handling=true sentry.enable-spotlight=true sentry.enablePrettySerializationOutput=false sentry.logs.enabled=true +sentry.logging.enable-logs=true sentry.in-app-includes="io.sentry.samples" sentry.profile-session-sample-rate=1.0 sentry.profiling-traces-dir-path=tmp/sentry/profiling-traces diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring-boot-jakarta/src/main/resources/application.properties index 20f9463aabc..02c6cf72430 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta/src/main/resources/application.properties +++ b/sentry-samples/sentry-samples-spring-boot-jakarta/src/main/resources/application.properties @@ -17,6 +17,7 @@ sentry.enable-spotlight=false sentry.enablePrettySerializationOutput=false sentry.in-app-includes="io.sentry.samples" sentry.logs.enabled=true +sentry.logging.enable-logs=true sentry.profile-session-sample-rate=1.0 sentry.profiling-traces-dir-path=tmp/sentry/profiling-traces sentry.profile-lifecycle=TRACE diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/main/resources/application.properties index 2225cd5045c..ab866094ebf 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/main/resources/application.properties +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/src/main/resources/application.properties @@ -15,6 +15,7 @@ sentry.graphql.ignored-error-types=SOME_ERROR,ANOTHER_ERROR sentry.enable-backpressure-handling=true sentry.enable-spotlight=true sentry.logs.enabled=true +sentry.logging.enable-logs=true sentry.in-app-includes="io.sentry.samples" sentry.profile-session-sample-rate=1.0 sentry.profiling-traces-dir-path=tmp/sentry/profiling-traces diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/main/resources/application.properties index d39f38d7182..69b87411656 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/main/resources/application.properties +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/src/main/resources/application.properties @@ -15,6 +15,7 @@ sentry.graphql.ignored-error-types=SOME_ERROR,ANOTHER_ERROR sentry.enable-backpressure-handling=true sentry.enable-spotlight=true sentry.logs.enabled=true +sentry.logging.enable-logs=true sentry.in-app-includes="io.sentry.samples" sentry.profile-session-sample-rate=1.0 sentry.profiling-traces-dir-path=tmp/sentry/profiling-traces diff --git a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/main/resources/application.properties index 02eaf0c731c..d228dedf5ac 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/main/resources/application.properties +++ b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/src/main/resources/application.properties @@ -11,6 +11,7 @@ sentry.reactive.thread-local-accessor-enabled=true sentry.traces-sample-rate=1.0 sentry.enable-backpressure-handling=true sentry.logs.enabled=true +sentry.logging.enable-logs=true sentry.enable-spotlight=true sentry.in-app-includes="io.sentry.samples" sentry.profile-session-sample-rate=1.0 diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring-boot-webflux/src/main/resources/application.properties index 6544e24f13b..8e3517eb96a 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/src/main/resources/application.properties +++ b/sentry-samples/sentry-samples-spring-boot-webflux/src/main/resources/application.properties @@ -13,6 +13,7 @@ spring.graphql.websocket.path=/graphql spring.graphql.schema.printer.enabled=true sentry.enable-backpressure-handling=true sentry.logs.enabled=true +sentry.logging.enable-logs=true sentry.enable-spotlight=true sentry.in-app-includes="io.sentry.samples" sentry.profile-session-sample-rate=1.0 diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties index 4e97e7a1eb8..b02107af7e0 100644 --- a/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties +++ b/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties @@ -15,6 +15,7 @@ sentry.graphql.ignored-error-types=SOME_ERROR,ANOTHER_ERROR sentry.enable-backpressure-handling=true sentry.enable-spotlight=true sentry.logs.enabled=true +sentry.logging.enable-logs=true sentry.in-app-includes="io.sentry.samples" sentry.profile-session-sample-rate=1.0 sentry.profiling-traces-dir-path=tmp/sentry/profiling-traces diff --git a/sentry-spring-boot-4/api/sentry-spring-boot-4.api b/sentry-spring-boot-4/api/sentry-spring-boot-4.api index 4c8be990b85..0415ffff76e 100644 --- a/sentry-spring-boot-4/api/sentry-spring-boot-4.api +++ b/sentry-spring-boot-4/api/sentry-spring-boot-4.api @@ -60,7 +60,9 @@ public class io/sentry/spring/boot4/SentryProperties$Logging { public fun getMinimumBreadcrumbLevel ()Lorg/slf4j/event/Level; public fun getMinimumEventLevel ()Lorg/slf4j/event/Level; public fun getMinimumLevel ()Lorg/slf4j/event/Level; + public fun isEnableLogs ()Z public fun isEnabled ()Z + public fun setEnableLogs (Z)V public fun setEnabled (Z)V public fun setLoggers (Ljava/util/List;)V public fun setMinimumBreadcrumbLevel (Lorg/slf4j/event/Level;)V diff --git a/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryLogbackInitializer.java b/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryLogbackInitializer.java index 58de0bc4b26..c51e2cda077 100644 --- a/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryLogbackInitializer.java +++ b/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryLogbackInitializer.java @@ -45,6 +45,7 @@ public void onApplicationEvent(final @NotNull ApplicationEvent event) { if (!isSentryAppenderRegistered(logger)) { final SentryAppender sentryAppender = getSentryAppender(); + sentryAppender.setEnableLogs(sentryProperties.getLogging().isEnableLogs()); Optional.ofNullable(sentryProperties.getLogging().getMinimumBreadcrumbLevel()) .map(slf4jLevel -> Level.toLevel(slf4jLevel.name())) .ifPresent(sentryAppender::setMinimumBreadcrumbLevel); diff --git a/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryProperties.java b/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryProperties.java index edb8d44cdd3..41358f8cc84 100644 --- a/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryProperties.java +++ b/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryProperties.java @@ -129,6 +129,9 @@ public static class Logging { /** Enable/Disable logging auto-configuration. */ private boolean enabled = true; + /** Enable/Disable Sentry Logs capture from the auto-configured appender. */ + private boolean enableLogs = false; + /** Minimum logging level for recording breadcrumbs. */ private @Nullable Level minimumBreadcrumbLevel; @@ -149,6 +152,14 @@ public void setEnabled(boolean enabled) { this.enabled = enabled; } + public boolean isEnableLogs() { + return enableLogs; + } + + public void setEnableLogs(boolean enableLogs) { + this.enableLogs = enableLogs; + } + public @Nullable Level getMinimumBreadcrumbLevel() { return minimumBreadcrumbLevel; } 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 ef1f12aeecf..115df010b95 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 @@ -244,6 +244,7 @@ class SentryAutoConfigurationTest { "sentry.cron.default-failure-issue-threshold=40", "sentry.cron.default-recovery-threshold=50", "sentry.logs.enabled=true", + "sentry.logging.enable-logs=true", "sentry.strict-trace-continuation=true", "sentry.org-id=12345", ) @@ -301,6 +302,7 @@ class SentryAutoConfigurationTest { assertThat(options.cron!!.defaultFailureIssueThreshold).isEqualTo(40L) assertThat(options.cron!!.defaultRecoveryThreshold).isEqualTo(50L) assertThat(options.logs.isEnabled).isEqualTo(true) + assertThat(options.logging.isEnableLogs).isTrue() assertThat(options.isStrictTraceContinuation).isEqualTo(true) assertThat(options.orgId).isEqualTo("12345") } diff --git a/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryLogbackAppenderAutoConfigurationTest.kt b/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryLogbackAppenderAutoConfigurationTest.kt index 681932e6f89..be3f7863b03 100644 --- a/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryLogbackAppenderAutoConfigurationTest.kt +++ b/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryLogbackAppenderAutoConfigurationTest.kt @@ -112,6 +112,7 @@ class SentryLogbackAppenderAutoConfigurationTest { "sentry.logging.minimum-event-level=info", "sentry.logging.minimum-breadcrumb-level=debug", "sentry.logging.minimum-level=error", + "sentry.logging.enable-logs=true", ) .run { val appenders = rootLogger.getAppenders(SentryAppender::class.java) @@ -121,9 +122,19 @@ class SentryLogbackAppenderAutoConfigurationTest { assertThat(sentryAppender.minimumBreadcrumbLevel).isEqualTo(Level.DEBUG) assertThat(sentryAppender.minimumEventLevel).isEqualTo(Level.INFO) assertThat(sentryAppender.minimumLevel).isEqualTo(Level.ERROR) + assertThat(sentryAppender.isEnableLogs).isTrue() } } + @Test + fun `SentryAppender Logs are disabled by default`() { + dsnEnabledRunner.run { + val sentryAppender = rootLogger.getAppenders(SentryAppender::class.java).single() + + assertThat((sentryAppender as SentryAppender).isEnableLogs).isFalse() + } + } + @Test fun `does not configure SentryAppender when logging is disabled`() { contextRunner.withPropertyValues("sentry.logging.enabled=false").run { diff --git a/sentry-spring-boot-jakarta/api/sentry-spring-boot-jakarta.api b/sentry-spring-boot-jakarta/api/sentry-spring-boot-jakarta.api index 197bdbeef72..8ecabe5de92 100644 --- a/sentry-spring-boot-jakarta/api/sentry-spring-boot-jakarta.api +++ b/sentry-spring-boot-jakarta/api/sentry-spring-boot-jakarta.api @@ -60,7 +60,9 @@ public class io/sentry/spring/boot/jakarta/SentryProperties$Logging { public fun getMinimumBreadcrumbLevel ()Lorg/slf4j/event/Level; public fun getMinimumEventLevel ()Lorg/slf4j/event/Level; public fun getMinimumLevel ()Lorg/slf4j/event/Level; + public fun isEnableLogs ()Z public fun isEnabled ()Z + public fun setEnableLogs (Z)V public fun setEnabled (Z)V public fun setLoggers (Ljava/util/List;)V public fun setMinimumBreadcrumbLevel (Lorg/slf4j/event/Level;)V diff --git a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryLogbackInitializer.java b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryLogbackInitializer.java index be222eae1bf..fa6cd7a76ce 100644 --- a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryLogbackInitializer.java +++ b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryLogbackInitializer.java @@ -45,6 +45,7 @@ public void onApplicationEvent(final @NotNull ApplicationEvent event) { if (!isSentryAppenderRegistered(logger)) { final SentryAppender sentryAppender = getSentryAppender(); + sentryAppender.setEnableLogs(sentryProperties.getLogging().isEnableLogs()); Optional.ofNullable(sentryProperties.getLogging().getMinimumBreadcrumbLevel()) .map(slf4jLevel -> Level.toLevel(slf4jLevel.name())) .ifPresent(sentryAppender::setMinimumBreadcrumbLevel); diff --git a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryProperties.java b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryProperties.java index 7813c2e5512..223dcce8696 100644 --- a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryProperties.java +++ b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryProperties.java @@ -129,6 +129,9 @@ public static class Logging { /** Enable/Disable logging auto-configuration. */ private boolean enabled = true; + /** Enable/Disable Sentry Logs capture from the auto-configured appender. */ + private boolean enableLogs = false; + /** Minimum logging level for recording breadcrumbs. */ private @Nullable Level minimumBreadcrumbLevel; @@ -149,6 +152,14 @@ public void setEnabled(boolean enabled) { this.enabled = enabled; } + public boolean isEnableLogs() { + return enableLogs; + } + + public void setEnableLogs(boolean enableLogs) { + this.enableLogs = enableLogs; + } + public @Nullable Level getMinimumBreadcrumbLevel() { return minimumBreadcrumbLevel; } 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 91677d16b4e..710752752aa 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 @@ -246,6 +246,7 @@ class SentryAutoConfigurationTest { "sentry.cron.default-failure-issue-threshold=40", "sentry.cron.default-recovery-threshold=50", "sentry.logs.enabled=true", + "sentry.logging.enable-logs=true", "sentry.profile-session-sample-rate=1.0", "sentry.profiling-traces-dir-path=tmp/sentry/profiling-traces", "sentry.profile-lifecycle=TRACE", @@ -305,6 +306,7 @@ class SentryAutoConfigurationTest { assertThat(options.cron!!.defaultFailureIssueThreshold).isEqualTo(40L) assertThat(options.cron!!.defaultRecoveryThreshold).isEqualTo(50L) assertThat(options.logs.isEnabled).isEqualTo(true) + assertThat(options.logging.isEnableLogs).isTrue() assertThat(options.profileSessionSampleRate).isEqualTo(1.0) assertThat(options.profilingTracesDirPath) .startsWith(File("tmp/sentry/profiling-traces").absolutePath) diff --git a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryLogbackAppenderAutoConfigurationTest.kt b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryLogbackAppenderAutoConfigurationTest.kt index d8982d995c1..5dfcd5cd324 100644 --- a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryLogbackAppenderAutoConfigurationTest.kt +++ b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryLogbackAppenderAutoConfigurationTest.kt @@ -112,6 +112,7 @@ class SentryLogbackAppenderAutoConfigurationTest { "sentry.logging.minimum-event-level=info", "sentry.logging.minimum-breadcrumb-level=debug", "sentry.logging.minimum-level=error", + "sentry.logging.enable-logs=true", ) .run { val appenders = rootLogger.getAppenders(SentryAppender::class.java) @@ -121,9 +122,19 @@ class SentryLogbackAppenderAutoConfigurationTest { assertThat(sentryAppender.minimumBreadcrumbLevel).isEqualTo(Level.DEBUG) assertThat(sentryAppender.minimumEventLevel).isEqualTo(Level.INFO) assertThat(sentryAppender.minimumLevel).isEqualTo(Level.ERROR) + assertThat(sentryAppender.isEnableLogs).isTrue() } } + @Test + fun `SentryAppender Logs are disabled by default`() { + dsnEnabledRunner.run { + val sentryAppender = rootLogger.getAppenders(SentryAppender::class.java).single() + + assertThat((sentryAppender as SentryAppender).isEnableLogs).isFalse() + } + } + @Test fun `does not configure SentryAppender when logging is disabled`() { contextRunner.withPropertyValues("sentry.logging.enabled=false").run { diff --git a/sentry-spring-boot/api/sentry-spring-boot.api b/sentry-spring-boot/api/sentry-spring-boot.api index ef726c4fc25..3a34fcc542d 100644 --- a/sentry-spring-boot/api/sentry-spring-boot.api +++ b/sentry-spring-boot/api/sentry-spring-boot.api @@ -56,7 +56,9 @@ public class io/sentry/spring/boot/SentryProperties$Logging { public fun getMinimumBreadcrumbLevel ()Lorg/slf4j/event/Level; public fun getMinimumEventLevel ()Lorg/slf4j/event/Level; public fun getMinimumLevel ()Lorg/slf4j/event/Level; + public fun isEnableLogs ()Z public fun isEnabled ()Z + public fun setEnableLogs (Z)V public fun setEnabled (Z)V public fun setLoggers (Ljava/util/List;)V public fun setMinimumBreadcrumbLevel (Lorg/slf4j/event/Level;)V diff --git a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryLogbackInitializer.java b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryLogbackInitializer.java index 94ba7b743fe..6997aca3fc8 100644 --- a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryLogbackInitializer.java +++ b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryLogbackInitializer.java @@ -45,6 +45,7 @@ public void onApplicationEvent(final @NotNull ApplicationEvent event) { if (!isSentryAppenderRegistered(logger)) { final SentryAppender sentryAppender = getSentryAppender(); + sentryAppender.setEnableLogs(sentryProperties.getLogging().isEnableLogs()); Optional.ofNullable(sentryProperties.getLogging().getMinimumBreadcrumbLevel()) .map(slf4jLevel -> Level.toLevel(slf4jLevel.name())) .ifPresent(sentryAppender::setMinimumBreadcrumbLevel); diff --git a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryProperties.java b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryProperties.java index f959fc930ba..876cb552571 100644 --- a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryProperties.java +++ b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryProperties.java @@ -103,6 +103,9 @@ public static class Logging { /** Enable/Disable logging auto-configuration. */ private boolean enabled = true; + /** Enable/Disable Sentry Logs capture from the auto-configured appender. */ + private boolean enableLogs = false; + /** Minimum logging level for recording breadcrumbs. */ private @Nullable Level minimumBreadcrumbLevel; @@ -123,6 +126,14 @@ public void setEnabled(boolean enabled) { this.enabled = enabled; } + public boolean isEnableLogs() { + return enableLogs; + } + + public void setEnableLogs(boolean enableLogs) { + this.enableLogs = enableLogs; + } + public @Nullable Level getMinimumBreadcrumbLevel() { return minimumBreadcrumbLevel; } 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 d9e598d0473..8f14b359e6c 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 @@ -244,6 +244,7 @@ class SentryAutoConfigurationTest { "sentry.cron.default-failure-issue-threshold=40", "sentry.cron.default-recovery-threshold=50", "sentry.logs.enabled=true", + "sentry.logging.enable-logs=true", "sentry.profile-session-sample-rate=1.0", "sentry.profiling-traces-dir-path=tmp/sentry/profiling-traces", "sentry.profile-lifecycle=TRACE", @@ -303,6 +304,7 @@ class SentryAutoConfigurationTest { assertThat(options.cron!!.defaultFailureIssueThreshold).isEqualTo(40L) assertThat(options.cron!!.defaultRecoveryThreshold).isEqualTo(50L) assertThat(options.logs.isEnabled).isEqualTo(true) + assertThat(options.logging.isEnableLogs).isTrue() assertThat(options.profileSessionSampleRate).isEqualTo(1.0) assertThat(options.profilingTracesDirPath) .startsWith(File("tmp/sentry/profiling-traces").absolutePath) diff --git a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryLogbackAppenderAutoConfigurationTest.kt b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryLogbackAppenderAutoConfigurationTest.kt index f68cad0ff90..6117ccf3f8d 100644 --- a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryLogbackAppenderAutoConfigurationTest.kt +++ b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryLogbackAppenderAutoConfigurationTest.kt @@ -112,6 +112,7 @@ class SentryLogbackAppenderAutoConfigurationTest { "sentry.logging.minimum-event-level=info", "sentry.logging.minimum-breadcrumb-level=debug", "sentry.logging.minimum-level=error", + "sentry.logging.enable-logs=true", ) .run { val appenders = rootLogger.getAppenders(SentryAppender::class.java) @@ -121,9 +122,19 @@ class SentryLogbackAppenderAutoConfigurationTest { assertThat(sentryAppender.minimumBreadcrumbLevel).isEqualTo(Level.DEBUG) assertThat(sentryAppender.minimumEventLevel).isEqualTo(Level.INFO) assertThat(sentryAppender.minimumLevel).isEqualTo(Level.ERROR) + assertThat(sentryAppender.isEnableLogs).isTrue() } } + @Test + fun `SentryAppender Logs are disabled by default`() { + dsnEnabledRunner.run { + val sentryAppender = rootLogger.getAppenders(SentryAppender::class.java).single() + + assertThat((sentryAppender as SentryAppender).isEnableLogs).isFalse() + } + } + @Test fun `does not configure SentryAppender when logging is disabled`() { contextRunner.withPropertyValues("sentry.logging.enabled=false").run { From 381242ff16f61edac0d435fd09316b95168d040b Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 12 Aug 2026 16:35:08 +0200 Subject: [PATCH 2/3] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd43f77bbbb..63d43b7ddbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- Add an explicit Logs opt-in to Spring Boot logging auto-configuration ([#5946](https://github.com/getsentry/sentry-java/pull/5946)) - Add an explicit Logs opt-in to the Android Logcat integration ([#5945](https://github.com/getsentry/sentry-java/pull/5945)) - Add an explicit Logs opt-in to the Android Timber integration ([#5943](https://github.com/getsentry/sentry-java/pull/5943)) - Add an explicit Logs opt-in to the JUL handler ([#5942](https://github.com/getsentry/sentry-java/pull/5942)) From 2293fcd256d086e87e7433a1dc01540e8ad3beb0 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 30 Sep 2026 14:58:54 +0200 Subject: [PATCH 3/3] fix(spring): Forward Logs option to Log4j2 appender Pass the Spring logging Logs opt-in to auto-configured Log4j2 appenders in the Jakarta and Spring Boot 4 integrations. Keep automatic Logs disabled unless the property is explicitly enabled. Co-Authored-By: Claude --- .../spring/boot4/SentryLog4j2Initializer.java | 1 + ...ntryLog4j2AppenderAutoConfigurationTest.kt | 56 +++++++++++++++++++ .../boot/jakarta/SentryLog4j2Initializer.java | 1 + ...ntryLog4j2AppenderAutoConfigurationTest.kt | 56 +++++++++++++++++++ 4 files changed, 114 insertions(+) diff --git a/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryLog4j2Initializer.java b/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryLog4j2Initializer.java index efdf9dc255a..fe2d8a301a8 100644 --- a/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryLog4j2Initializer.java +++ b/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryLog4j2Initializer.java @@ -95,6 +95,7 @@ public void onApplicationEvent(final @NotNull ApplicationEvent event) { toLog4jLevel(sentryProperties.getLogging().getMinimumBreadcrumbLevel()), toLog4jLevel(sentryProperties.getLogging().getMinimumEventLevel()), toLog4jLevel(sentryProperties.getLogging().getMinimumLevel()), + sentryProperties.getLogging().isEnableLogs(), null, null, ScopesAdapter.getInstance(), diff --git a/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryLog4j2AppenderAutoConfigurationTest.kt b/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryLog4j2AppenderAutoConfigurationTest.kt index b9bcee11d38..2518b11a402 100644 --- a/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryLog4j2AppenderAutoConfigurationTest.kt +++ b/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryLog4j2AppenderAutoConfigurationTest.kt @@ -6,7 +6,10 @@ import ch.qos.logback.core.read.ListAppender import io.sentry.ITransportFactory import io.sentry.NoOpTransportFactory import io.sentry.ScopesAdapter +import io.sentry.Sentry +import io.sentry.checkLogs import io.sentry.log4j2.SentryAppender +import io.sentry.transport.ITransport import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test @@ -17,6 +20,11 @@ import org.apache.logging.log4j.core.LoggerContext import org.apache.logging.log4j.core.config.DefaultConfiguration import org.apache.logging.log4j.core.config.LoggerConfig import org.assertj.core.api.Assertions.assertThat +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 import org.slf4j.LoggerFactory import org.springframework.boot.autoconfigure.AutoConfigurations import org.springframework.boot.test.context.FilteredClassLoader @@ -63,6 +71,16 @@ class SentryLog4j2AppenderAutoConfigurationTest { private val dsnEnabledRunner = dsnOnlyRunner.withPropertyValues("sentry.logging.enabled=true") + private val logsRunner = + baseContextRunner + .withLog4j2CoreProvider() + .withPropertyValues( + "sentry.dsn=http://key@localhost/proj", + "sentry.logging.enabled=true", + "sentry.logs.enabled=true", + ) + .withUserConfiguration(MockTransportConfiguration::class.java) + // Hide the Log4j2 Core provider so LogManager uses the Log4j-to-SLF4J bridge. private val log4j2BridgeDsnEnabledRunner = baseContextRunner @@ -181,6 +199,29 @@ class SentryLog4j2AppenderAutoConfigurationTest { } } + @Test + fun `forwards Sentry Logs when enabled`() { + logsRunner.withPropertyValues("sentry.logging.enable-logs=true").run { + LogManager.getLogger("io.sentry.spring.boot4.logs-enabled").error("enabled log") + Sentry.flush(1000) + + val transport = it.getBean(ITransport::class.java) + verify(transport) + .send(checkLogs { logs -> assertThat(logs.items.single().body).isEqualTo("enabled log") }) + } + } + + @Test + fun `does not forward Sentry Logs by default`() { + logsRunner.run { + LogManager.getLogger("io.sentry.spring.boot4.logs-disabled").error("disabled log") + Sentry.flush(1000) + + val transport = it.getBean(ITransport::class.java) + verify(transport, never()).send(checkLogs {}) + } + } + @Test fun `does not configure SentryAppender when logging is disabled`() { dsnEnabledRunner.withPropertyValues("sentry.logging.enabled=false").run { @@ -251,6 +292,21 @@ class SentryLog4j2AppenderAutoConfigurationTest { .run { assertThat(rootLogger.getAppenders(SentryAppender::class.java)).isEmpty() } } + @Configuration(proxyBeanMethods = false) + open class MockTransportConfiguration { + + private val transport = mock() + + @Bean + open fun mockTransportFactory(): ITransportFactory { + val factory = mock() + whenever(factory.create(any(), any())).thenReturn(transport) + return factory + } + + @Bean open fun sentryTransport() = transport + } + @Configuration(proxyBeanMethods = false) open class NoOpTransportConfiguration { diff --git a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryLog4j2Initializer.java b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryLog4j2Initializer.java index 141f6a8ba00..4b2588da8e7 100644 --- a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryLog4j2Initializer.java +++ b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryLog4j2Initializer.java @@ -95,6 +95,7 @@ public void onApplicationEvent(final @NotNull ApplicationEvent event) { toLog4jLevel(sentryProperties.getLogging().getMinimumBreadcrumbLevel()), toLog4jLevel(sentryProperties.getLogging().getMinimumEventLevel()), toLog4jLevel(sentryProperties.getLogging().getMinimumLevel()), + sentryProperties.getLogging().isEnableLogs(), null, null, ScopesAdapter.getInstance(), diff --git a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryLog4j2AppenderAutoConfigurationTest.kt b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryLog4j2AppenderAutoConfigurationTest.kt index 08eb6ac45c7..993398923be 100644 --- a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryLog4j2AppenderAutoConfigurationTest.kt +++ b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryLog4j2AppenderAutoConfigurationTest.kt @@ -6,7 +6,10 @@ import ch.qos.logback.core.read.ListAppender import io.sentry.ITransportFactory import io.sentry.NoOpTransportFactory import io.sentry.ScopesAdapter +import io.sentry.Sentry +import io.sentry.checkLogs import io.sentry.log4j2.SentryAppender +import io.sentry.transport.ITransport import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test @@ -17,6 +20,11 @@ import org.apache.logging.log4j.core.LoggerContext import org.apache.logging.log4j.core.config.DefaultConfiguration import org.apache.logging.log4j.core.config.LoggerConfig import org.assertj.core.api.Assertions.assertThat +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 import org.slf4j.LoggerFactory import org.springframework.boot.autoconfigure.AutoConfigurations import org.springframework.boot.test.context.FilteredClassLoader @@ -63,6 +71,16 @@ class SentryLog4j2AppenderAutoConfigurationTest { private val dsnEnabledRunner = dsnOnlyRunner.withPropertyValues("sentry.logging.enabled=true") + private val logsRunner = + baseContextRunner + .withLog4j2CoreProvider() + .withPropertyValues( + "sentry.dsn=http://key@localhost/proj", + "sentry.logging.enabled=true", + "sentry.logs.enabled=true", + ) + .withUserConfiguration(MockTransportConfiguration::class.java) + // Hide the Log4j2 Core provider so LogManager uses the Log4j-to-SLF4J bridge. private val log4j2BridgeDsnEnabledRunner = baseContextRunner @@ -181,6 +199,29 @@ class SentryLog4j2AppenderAutoConfigurationTest { } } + @Test + fun `forwards Sentry Logs when enabled`() { + logsRunner.withPropertyValues("sentry.logging.enable-logs=true").run { + LogManager.getLogger("io.sentry.spring.boot.jakarta.logs-enabled").error("enabled log") + Sentry.flush(1000) + + val transport = it.getBean(ITransport::class.java) + verify(transport) + .send(checkLogs { logs -> assertThat(logs.items.single().body).isEqualTo("enabled log") }) + } + } + + @Test + fun `does not forward Sentry Logs by default`() { + logsRunner.run { + LogManager.getLogger("io.sentry.spring.boot.jakarta.logs-disabled").error("disabled log") + Sentry.flush(1000) + + val transport = it.getBean(ITransport::class.java) + verify(transport, never()).send(checkLogs {}) + } + } + @Test fun `does not configure SentryAppender when logging is disabled`() { dsnEnabledRunner.withPropertyValues("sentry.logging.enabled=false").run { @@ -251,6 +292,21 @@ class SentryLog4j2AppenderAutoConfigurationTest { .run { assertThat(rootLogger.getAppenders(SentryAppender::class.java)).isEmpty() } } + @Configuration(proxyBeanMethods = false) + open class MockTransportConfiguration { + + private val transport = mock() + + @Bean + open fun mockTransportFactory(): ITransportFactory { + val factory = mock() + whenever(factory.create(any(), any())).thenReturn(transport) + return factory + } + + @Bean open fun sentryTransport() = transport + } + @Configuration(proxyBeanMethods = false) open class NoOpTransportConfiguration {