From aeb796b3f5e1d3ff72e1f785fff7d584625e01c1 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 12 Aug 2026 15:43:34 +0200 Subject: [PATCH 01/44] collection: logs and metrics enable flags From ae1f43afa6a947a44e225e2594d9302038f1e62d Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 12 Aug 2026 15:52:38 +0200 Subject: [PATCH 02/44] feat(logback): Add Logs opt-in Require an explicit Logback appender opt-in before forwarding records as Sentry Logs. Keep event and breadcrumb capture independent while the aggregate core Logs flag remains in place. Co-Authored-By: Claude --- sentry-logback/api/sentry-logback.api | 2 + .../io/sentry/logback/SentryAppender.java | 12 +++- .../io/sentry/logback/SentryAppenderTest.kt | 56 ++++++++++++++++++- .../src/main/resources/logback.xml | 3 +- 4 files changed, 70 insertions(+), 3 deletions(-) diff --git a/sentry-logback/api/sentry-logback.api b/sentry-logback/api/sentry-logback.api index 84a3c90d5cf..8d697f1b945 100644 --- a/sentry-logback/api/sentry-logback.api +++ b/sentry-logback/api/sentry-logback.api @@ -14,6 +14,8 @@ public class io/sentry/logback/SentryAppender : ch/qos/logback/core/Unsynchroniz public fun getMinimumBreadcrumbLevel ()Lch/qos/logback/classic/Level; public fun getMinimumEventLevel ()Lch/qos/logback/classic/Level; public fun getMinimumLevel ()Lch/qos/logback/classic/Level; + public fun isEnableLogs ()Z + public fun setEnableLogs (Z)V public fun setEncoder (Lch/qos/logback/core/encoder/Encoder;)V public fun setMinimumBreadcrumbLevel (Lch/qos/logback/classic/Level;)V public fun setMinimumEventLevel (Lch/qos/logback/classic/Level;)V diff --git a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java index 20fdf304bda..f36d988c97b 100644 --- a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java +++ b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java @@ -52,6 +52,7 @@ public class SentryAppender extends UnsynchronizedAppenderBase { private @NotNull Level minimumBreadcrumbLevel = Level.INFO; private @NotNull Level minimumEventLevel = Level.ERROR; private @NotNull Level minimumLevel = Level.INFO; + private boolean enableLogs = false; private @Nullable Encoder encoder; static { @@ -87,7 +88,8 @@ public void start() { @Override protected void append(@NotNull ILoggingEvent eventObject) { - if (ScopesAdapter.getInstance().getOptions().getLogs().isEnabled() + if (enableLogs + && ScopesAdapter.getInstance().getOptions().getLogs().isEnabled() && eventObject.getLevel().isGreaterOrEqual(minimumLevel)) { captureLog(eventObject); } @@ -323,6 +325,14 @@ public void setMinimumLevel(final @Nullable Level minimumLevel) { return minimumLevel; } + public void setEnableLogs(final boolean enableLogs) { + this.enableLogs = enableLogs; + } + + public boolean isEnableLogs() { + return enableLogs; + } + @ApiStatus.Internal void setTransportFactory(final @Nullable ITransportFactory transportFactory) { this.transportFactory = transportFactory; diff --git a/sentry-logback/src/test/kotlin/io/sentry/logback/SentryAppenderTest.kt b/sentry-logback/src/test/kotlin/io/sentry/logback/SentryAppenderTest.kt index 877d2a23d75..7aafe705277 100644 --- a/sentry-logback/src/test/kotlin/io/sentry/logback/SentryAppenderTest.kt +++ b/sentry-logback/src/test/kotlin/io/sentry/logback/SentryAppenderTest.kt @@ -38,6 +38,7 @@ import kotlin.test.assertTrue import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.mock +import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.slf4j.Logger @@ -55,6 +56,7 @@ class SentryAppenderTest { encoder: Encoder? = null, sendDefaultPii: Boolean = false, enableLogs: Boolean = false, + enableGlobalLogs: Boolean = enableLogs, options: SentryOptions = SentryOptions(), startLater: Boolean = false, ) { @@ -71,7 +73,7 @@ class SentryAppenderTest { this.encoder = encoder options.dsn = dsn options.isSendDefaultPii = sendDefaultPii - options.logs.isEnabled = enableLogs + options.logs.isEnabled = enableGlobalLogs options.logs.loggerBatchProcessorFactory = ILoggerBatchProcessorFactory { options, client -> LoggerBatchProcessor(options, client, ImmediateExecutorService()) } @@ -81,6 +83,7 @@ class SentryAppenderTest { appender.setMinimumBreadcrumbLevel(minimumBreadcrumbLevel) appender.setMinimumEventLevel(minimumEventLevel) appender.setMinimumLevel(minimumLevel) + appender.setEnableLogs(enableLogs) appender.context = loggerContext appender.setTransportFactory(transportFactory) encoder?.context = loggerContext @@ -322,6 +325,57 @@ class SentryAppenderTest { ) } + @Test + fun `does not capture logs by default when aggregate logs are enabled`() { + fixture = Fixture(enableGlobalLogs = true) + + assertFalse(fixture.appender.isEnableLogs) + fixture.logger.info("this should not be captured as a log") + Sentry.flush(10) + + verify(fixture.transport, never()).send(checkLogs {}) + } + + @Test + fun `captures logs when local and aggregate logs are enabled`() { + fixture = Fixture(enableLogs = true) + + assertTrue(fixture.appender.isEnableLogs) + fixture.logger.info("this should be captured as a log") + Sentry.flush(10) + + verify(fixture.transport) + .send( + checkLogs { logs -> + assertEquals("this should be captured as a log", logs.items.first().body) + } + ) + } + + @Test + fun `captures events and breadcrumbs when local logs are disabled`() { + fixture = + Fixture( + minimumBreadcrumbLevel = Level.INFO, + minimumEventLevel = Level.ERROR, + enableGlobalLogs = true, + ) + + fixture.logger.info("this should be a breadcrumb") + fixture.logger.error("this should be an event") + Sentry.flush(10) + + verify(fixture.transport) + .send( + checkEvent { event -> + assertEquals("this should be an event", event.message?.formatted) + assertEquals("this should be a breadcrumb", event.breadcrumbs?.single()?.message) + }, + anyOrNull(), + ) + verify(fixture.transport, never()).send(checkLogs {}) + } + @Test fun `converts trace log level to Sentry log level`() { fixture = Fixture(minimumLevel = Level.TRACE, enableLogs = true) diff --git a/sentry-samples/sentry-samples-logback/src/main/resources/logback.xml b/sentry-samples/sentry-samples-logback/src/main/resources/logback.xml index 8082af4483b..7b70bcda1c0 100644 --- a/sentry-samples/sentry-samples-logback/src/main/resources/logback.xml +++ b/sentry-samples/sentry-samples-logback/src/main/resources/logback.xml @@ -17,12 +17,13 @@ true + true WARN DEBUG - + INFO From 7419f011f194dac37e39d158cdd9e1f20d976897 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 12 Aug 2026 15:54:46 +0200 Subject: [PATCH 03/44] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50e013db548..e6dd7a0301e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- Add an explicit Logs opt-in to the Logback appender ([#5940](https://github.com/getsentry/sentry-java/pull/5940)) - Make `ISpan.startChild` overloads with `SpanOptions` public ([#5927](https://github.com/getsentry/sentry-java/pull/5927)) ### Improvements From 8b994c1a965f8c63295d998096e80cc81404c50a Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 12 Aug 2026 16:01:48 +0200 Subject: [PATCH 04/44] feat(log4j2): Add Logs opt-in Require an explicit Log4j2 appender opt-in before forwarding records as Sentry Logs. Preserve existing constructors and factory methods with Logs disabled by default while keeping events and breadcrumbs unchanged. Co-Authored-By: Claude --- sentry-log4j2/api/sentry-log4j2.api | 2 + .../java/io/sentry/log4j2/SentryAppender.java | 54 +++++- .../io/sentry/log4j2/SentryAppenderTest.kt | 171 ++++++++++++++++++ .../src/main/resources/log4j2.xml | 1 + 4 files changed, 227 insertions(+), 1 deletion(-) diff --git a/sentry-log4j2/api/sentry-log4j2.api b/sentry-log4j2/api/sentry-log4j2.api index 2a5d4bf7895..4906f5f3ed4 100644 --- a/sentry-log4j2/api/sentry-log4j2.api +++ b/sentry-log4j2/api/sentry-log4j2.api @@ -7,8 +7,10 @@ public class io/sentry/log4j2/SentryAppender : org/apache/logging/log4j/core/app public static final field MECHANISM_TYPE Ljava/lang/String; public fun (Ljava/lang/String;Lorg/apache/logging/log4j/core/Filter;Ljava/lang/String;Lorg/apache/logging/log4j/Level;Lorg/apache/logging/log4j/Level;Ljava/lang/Boolean;Lio/sentry/ITransportFactory;Lio/sentry/IScopes;[Ljava/lang/String;)V public fun (Ljava/lang/String;Lorg/apache/logging/log4j/core/Filter;Ljava/lang/String;Lorg/apache/logging/log4j/Level;Lorg/apache/logging/log4j/Level;Lorg/apache/logging/log4j/Level;Ljava/lang/Boolean;Lio/sentry/ITransportFactory;Lio/sentry/IScopes;[Ljava/lang/String;)V + public fun (Ljava/lang/String;Lorg/apache/logging/log4j/core/Filter;Ljava/lang/String;Lorg/apache/logging/log4j/Level;Lorg/apache/logging/log4j/Level;Lorg/apache/logging/log4j/Level;ZLjava/lang/Boolean;Lio/sentry/ITransportFactory;Lio/sentry/IScopes;[Ljava/lang/String;)V public fun append (Lorg/apache/logging/log4j/core/LogEvent;)V protected fun captureLog (Lorg/apache/logging/log4j/core/LogEvent;)V + public static fun createAppender (Ljava/lang/String;Lorg/apache/logging/log4j/Level;Lorg/apache/logging/log4j/Level;Lorg/apache/logging/log4j/Level;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Lorg/apache/logging/log4j/core/Filter;Ljava/lang/String;)Lio/sentry/log4j2/SentryAppender; public static fun createAppender (Ljava/lang/String;Lorg/apache/logging/log4j/Level;Lorg/apache/logging/log4j/Level;Lorg/apache/logging/log4j/Level;Ljava/lang/String;Ljava/lang/Boolean;Lorg/apache/logging/log4j/core/Filter;Ljava/lang/String;)Lio/sentry/log4j2/SentryAppender; protected fun createBreadcrumb (Lorg/apache/logging/log4j/core/LogEvent;)Lio/sentry/Breadcrumb; protected fun createEvent (Lorg/apache/logging/log4j/core/LogEvent;)Lio/sentry/SentryEvent; diff --git a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java index 0218b53518d..78cfcd97005 100644 --- a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java +++ b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java @@ -55,6 +55,7 @@ public class SentryAppender extends AbstractAppender { private @NotNull Level minimumBreadcrumbLevel = Level.INFO; private @NotNull Level minimumEventLevel = Level.ERROR; private @NotNull Level minimumLevel = Level.INFO; + private final boolean enableLogs; private final @Nullable Boolean debug; private final @NotNull IScopes scopes; private final @Nullable List contextTags; @@ -104,6 +105,32 @@ public SentryAppender( final @Nullable ITransportFactory transportFactory, final @NotNull IScopes scopes, final @Nullable String[] contextTags) { + this( + name, + filter, + dsn, + minimumBreadcrumbLevel, + minimumEventLevel, + minimumLevel, + false, + debug, + transportFactory, + scopes, + contextTags); + } + + public SentryAppender( + final @NotNull String name, + final @Nullable Filter filter, + final @Nullable String dsn, + final @Nullable Level minimumBreadcrumbLevel, + final @Nullable Level minimumEventLevel, + final @Nullable Level minimumLevel, + final boolean enableLogs, + final @Nullable Boolean debug, + final @Nullable ITransportFactory transportFactory, + final @NotNull IScopes scopes, + final @Nullable String[] contextTags) { super(name, filter, null, true, null); this.dsn = dsn; if (minimumBreadcrumbLevel != null) { @@ -115,6 +142,7 @@ public SentryAppender( if (minimumLevel != null) { this.minimumLevel = minimumLevel; } + this.enableLogs = enableLogs; this.debug = debug; this.transportFactory = transportFactory; this.scopes = scopes; @@ -133,12 +161,34 @@ public SentryAppender( * @param filter The filter, if any, to use. * @return The SentryAppender. */ + public static @Nullable SentryAppender createAppender( + final @Nullable String name, + final @Nullable Level minimumBreadcrumbLevel, + final @Nullable Level minimumEventLevel, + final @Nullable Level minimumLevel, + final @Nullable String dsn, + final @Nullable Boolean debug, + final @Nullable Filter filter, + final @Nullable String contextTags) { + return createAppender( + name, + minimumBreadcrumbLevel, + minimumEventLevel, + minimumLevel, + false, + dsn, + debug, + filter, + contextTags); + } + @PluginFactory public static @Nullable SentryAppender createAppender( @Nullable @PluginAttribute("name") final String name, @Nullable @PluginAttribute("minimumBreadcrumbLevel") final Level minimumBreadcrumbLevel, @Nullable @PluginAttribute("minimumEventLevel") final Level minimumEventLevel, @Nullable @PluginAttribute("minimumLevel") final Level minimumLevel, + @Nullable @PluginAttribute("enableLogs") final Boolean enableLogs, @Nullable @PluginAttribute("dsn") final String dsn, @Nullable @PluginAttribute("debug") final Boolean debug, @Nullable @PluginElement("filter") final Filter filter, @@ -155,6 +205,7 @@ public SentryAppender( minimumBreadcrumbLevel, minimumEventLevel, minimumLevel, + Boolean.TRUE.equals(enableLogs), debug, null, ScopesAdapter.getInstance(), @@ -206,7 +257,8 @@ void start(final @NotNull Sentry.OptionsConfiguration optionsConf @Override public void append(final @NotNull LogEvent eventObject) { - if (scopes.getOptions().getLogs().isEnabled() + if (enableLogs + && scopes.getOptions().getLogs().isEnabled() && eventObject.getLevel().isMoreSpecificThan(minimumLevel)) { captureLog(eventObject); } diff --git a/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt b/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt index c32459ea022..cb33115e647 100644 --- a/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt +++ b/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt @@ -1,5 +1,6 @@ package io.sentry.log4j2 +import io.sentry.IScopes import io.sentry.ITransportFactory import io.sentry.InitPriority import io.sentry.ScopesAdapter @@ -28,6 +29,7 @@ import org.apache.logging.log4j.Level import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.MarkerManager import org.apache.logging.log4j.ThreadContext +import org.apache.logging.log4j.core.LogEvent import org.apache.logging.log4j.core.LoggerContext import org.apache.logging.log4j.core.config.AppenderRef import org.apache.logging.log4j.core.config.Configuration @@ -35,7 +37,10 @@ import org.apache.logging.log4j.core.config.LoggerConfig import org.apache.logging.log4j.spi.ExtendedLogger import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.doNothing import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.spy import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -57,6 +62,8 @@ class SentryAppenderTest { minimumLevel: Level? = null, debug: Boolean? = null, contextTags: List? = null, + enableLogs: Boolean = true, + enableGlobalLogs: Boolean = true, ): ExtendedLogger { if (transportFactory != null) { this.transportFactory = transportFactory @@ -71,6 +78,7 @@ class SentryAppenderTest { minimumBreadcrumbLevel, minimumEventLevel, minimumLevel, + enableLogs, debug, this.transportFactory, ScopesAdapter.getInstance(), @@ -98,6 +106,7 @@ class SentryAppenderTest { appender.start( appender.getOptionsConfiguration { options -> + options.logs.isEnabled = enableGlobalLogs options.logs.loggerBatchProcessorFactory = ILoggerBatchProcessorFactory { options, client -> LoggerBatchProcessor(options, client, ImmediateExecutorService()) @@ -253,6 +262,168 @@ class SentryAppenderTest { .send(checkEvent { event -> assertEquals(SentryLevel.FATAL, event.level) }, anyOrNull()) } + @Test + fun `does not capture logs when local logs are disabled`() { + val logger = fixture.getSut(enableLogs = false, enableGlobalLogs = true) + + logger.info("this should not be captured as a log") + Sentry.flush(10) + + verify(fixture.transport, never()).send(checkLogs {}) + } + + @Test + fun `captures logs when local and aggregate logs are enabled`() { + val logger = fixture.getSut(enableLogs = true, enableGlobalLogs = true) + + logger.info("this should be captured as a log") + Sentry.flush(10) + + verify(fixture.transport) + .send( + checkLogs { logs -> + assertEquals("this should be captured as a log", logs.items.first().body) + } + ) + } + + @Test + fun `captures events and breadcrumbs when local logs are disabled`() { + val logger = + fixture.getSut( + minimumBreadcrumbLevel = Level.INFO, + minimumEventLevel = Level.ERROR, + enableLogs = false, + enableGlobalLogs = true, + ) + + logger.info("this should be a breadcrumb") + logger.error("this should be an event") + Sentry.flush(10) + + verify(fixture.transport) + .send( + checkEvent { event -> + assertEquals("this should be an event", event.message?.formatted) + assertEquals("this should be a breadcrumb", event.breadcrumbs?.single()?.message) + }, + anyOrNull(), + ) + verify(fixture.transport, never()).send(checkLogs {}) + } + + @Test + fun `existing constructors default logs to disabled`() { + val scopes = mock() + val event = mock() + whenever(event.level).thenReturn(Level.INFO) + + val deprecatedAppender = + SentryAppender( + "deprecated", + null, + null, + Level.OFF, + Level.OFF, + null, + null, + scopes, + null, + ) + val existingAppender = + SentryAppender( + "existing", + null, + null, + Level.OFF, + Level.OFF, + Level.INFO, + null, + null, + scopes, + null, + ) + + deprecatedAppender.append(event) + existingAppender.append(event) + + verify(scopes, never()).logger() + } + + @Test + fun `existing factory and plugin attribute default logs to disabled`() { + val event = mock() + whenever(event.level).thenReturn(Level.INFO) + val existingAppender = + spy( + assertNotNull( + SentryAppender.createAppender( + "existing", + Level.OFF, + Level.OFF, + Level.INFO, + null, + null, + null, + null, + ) + ) + ) + val pluginDefaultAppender = + spy( + assertNotNull( + SentryAppender.createAppender( + "plugin-default", + Level.OFF, + Level.OFF, + Level.INFO, + null, + null, + null, + null, + null, + ) + ) + ) + + existingAppender.append(event) + pluginDefaultAppender.append(event) + + verify(existingAppender, never()).captureLog(event) + verify(pluginDefaultAppender, never()).captureLog(event) + } + + @Test + fun `plugin attribute enables logs with explicit opt in`() { + initForTest { + it.dsn = "http://key@localhost/proj" + it.logs.isEnabled = true + } + val event = mock() + whenever(event.level).thenReturn(Level.INFO) + val appender = + spy( + assertNotNull( + SentryAppender.createAppender( + "enabled", + Level.OFF, + Level.OFF, + Level.INFO, + true, + null, + null, + null, + null, + ) + ) + ) + doNothing().whenever(appender).captureLog(event) + + appender.append(event) + + verify(appender).captureLog(event) + } + @Test fun `converts trace log level to Sentry log level`() { val logger = fixture.getSut(minimumLevel = Level.TRACE) diff --git a/sentry-samples/sentry-samples-log4j2/src/main/resources/log4j2.xml b/sentry-samples/sentry-samples-log4j2/src/main/resources/log4j2.xml index 51428b0f1cc..028e449b06a 100644 --- a/sentry-samples/sentry-samples-log4j2/src/main/resources/log4j2.xml +++ b/sentry-samples/sentry-samples-log4j2/src/main/resources/log4j2.xml @@ -12,6 +12,7 @@ minimumBreadcrumbLevel="DEBUG" minimumEventLevel="WARN" minimumLevel="DEBUG" + enableLogs="true" debug="true" contextTags="userId,requestId" /> From 11568c021d243daeb2228677096f5354d3cbc878 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 12 Aug 2026 16:02:25 +0200 Subject: [PATCH 05/44] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6dd7a0301e..b113faacfef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- Add an explicit Logs opt-in to the Log4j2 appender ([#5941](https://github.com/getsentry/sentry-java/pull/5941)) - Add an explicit Logs opt-in to the Logback appender ([#5940](https://github.com/getsentry/sentry-java/pull/5940)) - Make `ISpan.startChild` overloads with `SpanOptions` public ([#5927](https://github.com/getsentry/sentry-java/pull/5927)) From abe6ede24887f02a6127b006b64257d5fc5a503d Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 12 Aug 2026 16:07:01 +0200 Subject: [PATCH 06/44] feat(jul): Add Logs opt-in Require an explicit JUL handler opt-in before forwarding records as Sentry Logs. Support Java and LogManager configuration while keeping event and breadcrumb capture unchanged. Co-Authored-By: Claude --- sentry-jul/api/sentry-jul.api | 2 + .../java/io/sentry/jul/SentryHandler.java | 14 +++- .../kotlin/io/sentry/jul/SentryHandlerTest.kt | 72 ++++++++++++++++++- .../src/test/resources/logging.properties | 1 + .../src/main/resources/logging.properties | 1 + 5 files changed, 88 insertions(+), 2 deletions(-) diff --git a/sentry-jul/api/sentry-jul.api b/sentry-jul/api/sentry-jul.api index 7a7f6a26d32..1fdf4895971 100644 --- a/sentry-jul/api/sentry-jul.api +++ b/sentry-jul/api/sentry-jul.api @@ -15,8 +15,10 @@ public class io/sentry/jul/SentryHandler : java/util/logging/Handler { public fun getMinimumBreadcrumbLevel ()Ljava/util/logging/Level; public fun getMinimumEventLevel ()Ljava/util/logging/Level; public fun getMinimumLevel ()Ljava/util/logging/Level; + public fun isEnableLogs ()Z public fun isPrintfStyle ()Z public fun publish (Ljava/util/logging/LogRecord;)V + public fun setEnableLogs (Z)V public fun setMinimumBreadcrumbLevel (Ljava/util/logging/Level;)V public fun setMinimumEventLevel (Ljava/util/logging/Level;)V public fun setMinimumLevel (Ljava/util/logging/Level;)V diff --git a/sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java b/sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java index 4442052dd41..37ce146d3c6 100644 --- a/sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java +++ b/sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java @@ -53,6 +53,8 @@ public class SentryHandler extends Handler { */ private boolean printfStyle; + private boolean enableLogs; + private @NotNull Level minimumBreadcrumbLevel = Level.INFO; private @NotNull Level minimumEventLevel = Level.SEVERE; private @NotNull Level minimumLevel = Level.INFO; @@ -112,7 +114,8 @@ public void publish(final @NotNull LogRecord record) { return; } try { - if (ScopesAdapter.getInstance().getOptions().getLogs().isEnabled() + if (enableLogs + && ScopesAdapter.getInstance().getOptions().getLogs().isEnabled() && record.getLevel().intValue() >= minimumLevel.intValue()) { captureLog(record); } @@ -191,6 +194,7 @@ private void retrieveProperties() { final LogManager manager = LogManager.getLogManager(); final String className = SentryHandler.class.getName(); setPrintfStyle(Boolean.parseBoolean(manager.getProperty(className + ".printfStyle"))); + setEnableLogs(Boolean.parseBoolean(manager.getProperty(className + ".enableLogs"))); setLevel(parseLevelOrDefault(manager.getProperty(className + ".level"))); final String minimumBreadCrumbLevel = manager.getProperty(className + ".minimumBreadcrumbLevel"); @@ -390,6 +394,14 @@ public void setPrintfStyle(final boolean printfStyle) { this.printfStyle = printfStyle; } + public void setEnableLogs(final boolean enableLogs) { + this.enableLogs = enableLogs; + } + + public boolean isEnableLogs() { + return enableLogs; + } + public void setMinimumBreadcrumbLevel(final @Nullable Level minimumBreadcrumbLevel) { if (minimumBreadcrumbLevel != null) { this.minimumBreadcrumbLevel = minimumBreadcrumbLevel; diff --git a/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt b/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt index ab160faac40..a169c710b0a 100644 --- a/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt +++ b/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt @@ -28,6 +28,7 @@ import kotlin.test.assertNull import kotlin.test.assertTrue import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.mock +import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.slf4j.MDC @@ -40,6 +41,8 @@ class SentryHandlerTest { val transport: ITransport = mock(), contextTags: List? = null, printfStyle: Boolean? = null, + enableLogs: Boolean? = true, + enableGlobalLogs: Boolean = true, ) { var logger: Logger var handler: SentryHandler @@ -48,6 +51,7 @@ class SentryHandlerTest { val options = SentryOptions() options.dsn = "http://key@localhost/proj" options.setTransportFactory { _, _ -> transport } + options.logs.isEnabled = enableGlobalLogs options.logs.loggerBatchProcessorFactory = ILoggerBatchProcessorFactory { options, client -> LoggerBatchProcessor(options, client, ImmediateExecutorService()) } @@ -58,6 +62,9 @@ class SentryHandlerTest { handler.setMinimumBreadcrumbLevel(minimumBreadcrumbLevel) handler.setMinimumEventLevel(minimumEventLevel) handler.setMinimumLevel(minimumLevel) + if (enableLogs != null) { + handler.setEnableLogs(enableLogs) + } if (printfStyle == true) { handler.setPrintfStyle(printfStyle) } @@ -318,11 +325,22 @@ class SentryHandlerTest { @Test fun `fetches configuration from logging dot properties`() { - fixture = Fixture(configureWithLogManager = true) + fixture = Fixture(configureWithLogManager = true, enableLogs = null) assertEquals(Level.CONFIG, fixture.handler.minimumBreadcrumbLevel) assertEquals(Level.WARNING, fixture.handler.minimumEventLevel) assertEquals(Level.ALL, fixture.handler.level) assertTrue(fixture.handler.isPrintfStyle) + assertTrue(fixture.handler.isEnableLogs) + + fixture.logger.info("this should be captured as a log") + Sentry.flush(10) + + verify(fixture.transport) + .send( + checkLogs { logs -> + assertEquals("this should be captured as a log", logs.items.first().body) + } + ) } @Test @@ -418,6 +436,58 @@ class SentryHandlerTest { ) } + @Test + fun `does not capture logs by default`() { + fixture = Fixture(enableLogs = null, enableGlobalLogs = true) + + assertFalse(fixture.handler.isEnableLogs) + fixture.logger.info("this should not be captured as a log") + Sentry.flush(10) + + verify(fixture.transport, never()).send(checkLogs {}) + } + + @Test + fun `captures logs when enabled through Java`() { + fixture = Fixture(enableLogs = true, enableGlobalLogs = true) + + assertTrue(fixture.handler.isEnableLogs) + fixture.logger.info("this should be captured as a log") + Sentry.flush(10) + + verify(fixture.transport) + .send( + checkLogs { logs -> + assertEquals("this should be captured as a log", logs.items.first().body) + } + ) + } + + @Test + fun `captures events and breadcrumbs when logs are disabled`() { + fixture = + Fixture( + minimumBreadcrumbLevel = Level.INFO, + minimumEventLevel = Level.SEVERE, + enableLogs = false, + enableGlobalLogs = true, + ) + + fixture.logger.info("this should be a breadcrumb") + fixture.logger.severe("this should be an event") + Sentry.flush(10) + + verify(fixture.transport) + .send( + checkEvent { event -> + assertEquals("this should be an event", event.message?.message) + assertEquals("this should be a breadcrumb", event.breadcrumbs?.single()?.message) + }, + anyOrNull(), + ) + verify(fixture.transport, never()).send(checkLogs {}) + } + @Test fun `converts finest log level to Sentry log level`() { fixture = Fixture(minimumLevel = Level.FINEST) diff --git a/sentry-jul/src/test/resources/logging.properties b/sentry-jul/src/test/resources/logging.properties index 9ac994b722a..25ac65e1f66 100644 --- a/sentry-jul/src/test/resources/logging.properties +++ b/sentry-jul/src/test/resources/logging.properties @@ -3,5 +3,6 @@ io.sentry.jul.SentryHandler.minimumEventLevel=WARNING io.sentry.jul.SentryHandler.minimumBreadcrumbLevel=CONFIG io.sentry.jul.SentryHandler.minimumLevel=CONFIG io.sentry.jul.SentryHandler.printfStyle=true +io.sentry.jul.SentryHandler.enableLogs=true jul.SentryHandlerTest.handlers=java.util.logging.ConsoleHandler, io.sentry.jul.SentryHandler diff --git a/sentry-samples/sentry-samples-jul/src/main/resources/logging.properties b/sentry-samples/sentry-samples-jul/src/main/resources/logging.properties index 0bdab173235..db3026bfb9f 100644 --- a/sentry-samples/sentry-samples-jul/src/main/resources/logging.properties +++ b/sentry-samples/sentry-samples-jul/src/main/resources/logging.properties @@ -2,6 +2,7 @@ io.sentry.jul.SentryHandler.minimumEventLevel=INFO io.sentry.jul.SentryHandler.minimumBreadcrumbLevel=CONFIG io.sentry.jul.SentryHandler.minimumLevel=INFO io.sentry.jul.SentryHandler.printfStyle=true +io.sentry.jul.SentryHandler.enableLogs=true io.sentry.jul.SentryHandler.level=FINEST java.util.logging.ConsoleHandler.level = FINE handlers=io.sentry.jul.SentryHandler From 17f088129f617ac241bdf66332c0d5d86d251af5 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 12 Aug 2026 16:07:50 +0200 Subject: [PATCH 07/44] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b113faacfef..7af676f676a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- Add an explicit Logs opt-in to the JUL handler ([#5942](https://github.com/getsentry/sentry-java/pull/5942)) - Add an explicit Logs opt-in to the Log4j2 appender ([#5941](https://github.com/getsentry/sentry-java/pull/5941)) - Add an explicit Logs opt-in to the Logback appender ([#5940](https://github.com/getsentry/sentry-java/pull/5940)) - Make `ISpan.startChild` overloads with `SpanOptions` public ([#5927](https://github.com/getsentry/sentry-java/pull/5927)) From dd174d9947a1c7e9044c80d659ee84c192422563 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 12 Aug 2026 16:18:29 +0200 Subject: [PATCH 08/44] feat(android): Add Timber Logs opt-in Require an explicit Timber-local opt-in before forwarding Timber messages as Sentry Logs. Add Android options and manifest configuration for auto-installed integrations while preserving event and breadcrumb capture. Co-Authored-By: Claude --- .../api/sentry-android-core.api | 2 + .../core/AndroidOptionsInitializer.java | 2 +- .../android/core/ManifestMetadataReader.java | 5 ++ .../android/core/SentryAndroidOptions.java | 11 ++++ .../core/AndroidOptionsInitializerTest.kt | 17 +++++- .../core/ManifestMetadataReaderTest.kt | 19 +++++++ .../android/core/SentryAndroidOptionsTest.kt | 15 ++++++ .../api/sentry-android-timber.api | 4 ++ .../android/timber/SentryTimberIntegration.kt | 18 ++++++- .../sentry/android/timber/SentryTimberTree.kt | 16 +++++- .../timber/SentryTimberIntegrationTest.kt | 54 +++++++++++++++++-- .../android/timber/SentryTimberTreeTest.kt | 18 ++++++- 12 files changed, 170 insertions(+), 11 deletions(-) diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api index da80a74e32c..9e3b0fcf174 100644 --- a/sentry-android-core/api/sentry-android-core.api +++ b/sentry-android-core/api/sentry-android-core.api @@ -439,6 +439,7 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr public fun isEnableStandaloneAppStartTracing ()Z public fun isEnableSystemEventBreadcrumbs ()Z public fun isEnableSystemEventBreadcrumbsExtras ()Z + public fun isEnableTimberLogs ()Z public fun isReportHistoricalAnrs ()Z public fun isReportHistoricalTombstones ()Z public fun isTombstoneEnabled ()Z @@ -472,6 +473,7 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr public fun setEnableStandaloneAppStartTracing (Z)V public fun setEnableSystemEventBreadcrumbs (Z)V public fun setEnableSystemEventBreadcrumbsExtras (Z)V + public fun setEnableTimberLogs (Z)V public fun setFrameMetricsCollector (Lio/sentry/android/core/internal/util/SentryFrameMetricsCollector;)V public fun setNativeHandlerStrategy (Lio/sentry/android/core/NdkHandlerStrategy;)V public fun setNativeSdkName (Ljava/lang/String;)V diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java index a0547a78b34..d31d925810f 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java @@ -473,7 +473,7 @@ static void installDefaultIntegrations( } if (isTimberAvailable) { - options.addIntegration(new SentryTimberIntegration()); + options.addIntegration(new SentryTimberIntegration(options.isEnableTimberLogs())); } options.addIntegration(new AppComponentsBreadcrumbsIntegration(context)); options.addIntegration(new SystemEventsBreadcrumbsIntegration(context)); 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 f21d4c801a3..ddebd1b5eef 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 @@ -163,6 +163,8 @@ final class ManifestMetadataReader { static final String ENABLE_LOGS = "io.sentry.logs.enabled"; + static final String ENABLE_TIMBER_LOGS = "io.sentry.timber.logs.enabled"; + static final String ENABLE_METRICS = "io.sentry.metrics.enabled"; static final String ENABLE_AUTO_TRACE_ID_GENERATION = @@ -706,6 +708,9 @@ static void applyMetadata( .getLogs() .setEnabled(readBool(metadata, logger, ENABLE_LOGS, options.getLogs().isEnabled())); + options.setEnableTimberLogs( + readBool(metadata, logger, ENABLE_TIMBER_LOGS, options.isEnableTimberLogs())); + options .getMetrics() .setEnabled( diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java index 615db97a28d..a0ca7a36c24 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java @@ -71,6 +71,9 @@ public final class SentryAndroidOptions extends SentryOptions { /** Enable or disable automatic breadcrumbs for Network Events Using NetworkCallback */ private boolean enableNetworkEventBreadcrumbs = true; + /** Enable or disable automatic Sentry Logs capture from Timber. Default is disabled. */ + private boolean enableTimberLogs = false; + /** * Enables the Auto instrumentation for Activity lifecycle tracing. * @@ -457,6 +460,14 @@ public void setEnableNetworkEventBreadcrumbs(boolean enableNetworkEventBreadcrum this.enableNetworkEventBreadcrumbs = enableNetworkEventBreadcrumbs; } + public boolean isEnableTimberLogs() { + return enableTimberLogs; + } + + public void setEnableTimberLogs(boolean enableTimberLogs) { + this.enableTimberLogs = enableTimberLogs; + } + /** * Enable or disable all the automatic breadcrumbs * diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt index 6df1ed7167e..224b66bbfb1 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt @@ -679,8 +679,21 @@ class AndroidOptionsInitializerTest { fun `SentryTimberIntegration added to the integration list if available on classpath`() { fixture.initSutWithClassLoader(isTimberAvailable = true) - val actual = fixture.sentryOptions.integrations.firstOrNull { it is SentryTimberIntegration } - assertNotNull(actual) + val actual = + fixture.sentryOptions.integrations.firstOrNull { it is SentryTimberIntegration } + as SentryTimberIntegration + assertFalse(actual.enableLogs) + } + + @Test + fun `SentryTimberIntegration receives Timber logs option`() { + fixture.sentryOptions.isEnableTimberLogs = true + fixture.initSutWithClassLoader(isTimberAvailable = true) + + val actual = + fixture.sentryOptions.integrations.firstOrNull { it is SentryTimberIntegration } + as SentryTimberIntegration + assertTrue(actual.enableLogs) } @Test 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 d67a869eff0..f0d6e6f562d 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 @@ -1967,6 +1967,25 @@ class ManifestMetadataReaderTest { assertTrue(fixture.options.logs.isEnabled) } + @Test + fun `applyMetadata keeps Timber logs disabled if not found`() { + val context = fixture.getContext() + + ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) + + assertFalse(fixture.options.isEnableTimberLogs) + } + + @Test + fun `applyMetadata reads Timber logs enabled to options`() { + val bundle = bundleOf(ManifestMetadataReader.ENABLE_TIMBER_LOGS to true) + val context = fixture.getContext(metaData = bundle) + + ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) + + assertTrue(fixture.options.isEnableTimberLogs) + } + @Test fun `applyMetadata reads metrics enabled and keep default value if not found`() { // Arrange diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidOptionsTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidOptionsTest.kt index 94857b91058..2e5fa0ea45d 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidOptionsTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidOptionsTest.kt @@ -93,6 +93,21 @@ class SentryAndroidOptionsTest { assertTrue(sentryOptions.isEnableScopeSync) } + @Test + fun `Timber logs are disabled by default`() { + val sentryOptions = SentryAndroidOptions() + + assertFalse(sentryOptions.isEnableTimberLogs) + } + + @Test + fun `Timber logs can be enabled`() { + val sentryOptions = SentryAndroidOptions() + sentryOptions.isEnableTimberLogs = true + + assertTrue(sentryOptions.isEnableTimberLogs) + } + @Test fun `attach screenshots disabled by default for Android`() { val sentryOptions = SentryAndroidOptions() diff --git a/sentry-android-timber/api/sentry-android-timber.api b/sentry-android-timber/api/sentry-android-timber.api index 8ae2f49c28d..ad5c909836e 100644 --- a/sentry-android-timber/api/sentry-android-timber.api +++ b/sentry-android-timber/api/sentry-android-timber.api @@ -11,7 +11,10 @@ public final class io/sentry/android/timber/SentryTimberIntegration : io/sentry/ public fun ()V public fun (Lio/sentry/SentryLevel;Lio/sentry/SentryLevel;Lio/sentry/SentryLogLevel;)V public synthetic fun (Lio/sentry/SentryLevel;Lio/sentry/SentryLevel;Lio/sentry/SentryLogLevel;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/sentry/SentryLevel;Lio/sentry/SentryLevel;Lio/sentry/SentryLogLevel;Z)V + public fun (Z)V public fun close ()V + public final fun getEnableLogs ()Z public final fun getMinBreadcrumbLevel ()Lio/sentry/SentryLevel; public final fun getMinEventLevel ()Lio/sentry/SentryLevel; public final fun getMinLogsLevel ()Lio/sentry/SentryLogLevel; @@ -21,6 +24,7 @@ public final class io/sentry/android/timber/SentryTimberIntegration : io/sentry/ public final class io/sentry/android/timber/SentryTimberTree : timber/log/Timber$Tree { public fun (Lio/sentry/IScopes;Lio/sentry/SentryLevel;Lio/sentry/SentryLevel;Lio/sentry/SentryLogLevel;)V public synthetic fun (Lio/sentry/IScopes;Lio/sentry/SentryLevel;Lio/sentry/SentryLevel;Lio/sentry/SentryLogLevel;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/sentry/IScopes;Lio/sentry/SentryLevel;Lio/sentry/SentryLevel;Lio/sentry/SentryLogLevel;Z)V public fun d (Ljava/lang/String;[Ljava/lang/Object;)V public fun d (Ljava/lang/Throwable;)V public fun d (Ljava/lang/Throwable;Ljava/lang/String;[Ljava/lang/Object;)V diff --git a/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberIntegration.kt b/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberIntegration.kt index 521fe15127a..aa71fa74710 100644 --- a/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberIntegration.kt +++ b/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberIntegration.kt @@ -18,6 +18,22 @@ public class SentryTimberIntegration( public val minBreadcrumbLevel: SentryLevel = SentryLevel.INFO, public val minLogsLevel: SentryLogLevel = SentryLogLevel.INFO, ) : Integration, Closeable { + public var enableLogs: Boolean = false + private set + + public constructor(enableLogs: Boolean) : this() { + this.enableLogs = enableLogs + } + + public constructor( + minEventLevel: SentryLevel, + minBreadcrumbLevel: SentryLevel, + minLogsLevel: SentryLogLevel, + enableLogs: Boolean, + ) : this(minEventLevel, minBreadcrumbLevel, minLogsLevel) { + this.enableLogs = enableLogs + } + private lateinit var tree: SentryTimberTree private lateinit var logger: ILogger @@ -31,7 +47,7 @@ public class SentryTimberIntegration( override fun register(scopes: IScopes, options: SentryOptions) { logger = options.logger - tree = SentryTimberTree(scopes, minEventLevel, minBreadcrumbLevel, minLogsLevel) + tree = SentryTimberTree(scopes, minEventLevel, minBreadcrumbLevel, minLogsLevel, enableLogs) Timber.plant(tree) logger.log(SentryLevel.DEBUG, "SentryTimberIntegration installed.") diff --git a/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberTree.kt b/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberTree.kt index 61b1f99fb16..f63dd7b66d0 100644 --- a/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberTree.kt +++ b/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberTree.kt @@ -20,6 +20,18 @@ public class SentryTimberTree( private val minBreadcrumbLevel: SentryLevel, private val minLogLevel: SentryLogLevel = SentryLogLevel.INFO, ) : Timber.Tree() { + private var enableLogs: Boolean = false + + public constructor( + scopes: IScopes, + minEventLevel: SentryLevel, + minBreadcrumbLevel: SentryLevel, + minLogLevel: SentryLogLevel, + enableLogs: Boolean, + ) : this(scopes, minEventLevel, minBreadcrumbLevel, minLogLevel) { + this.enableLogs = enableLogs + } + private val pendingTag = ThreadLocal() private fun retrieveTag(): String? { @@ -185,7 +197,9 @@ public class SentryTimberTree( captureEvent(level, tag, sentryMessage, throwable) addBreadcrumb(level, sentryMessage, throwable) - addLog(logLevel, message, tag, throwable, *args) + if (enableLogs) { + addLog(logLevel, message, tag, throwable, *args) + } } /** do not log if it's lower than min. required level. */ diff --git a/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberIntegrationTest.kt b/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberIntegrationTest.kt index 7c21eca8ef0..5aae1be93fe 100644 --- a/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberIntegrationTest.kt +++ b/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberIntegrationTest.kt @@ -1,5 +1,6 @@ package io.sentry.android.timber +import io.sentry.Breadcrumb import io.sentry.IScopes import io.sentry.ITransportFactory import io.sentry.ScopesAdapter @@ -7,33 +8,52 @@ import io.sentry.Sentry import io.sentry.SentryLevel import io.sentry.SentryLogLevel import io.sentry.SentryOptions +import io.sentry.logger.ILoggerApi +import io.sentry.logger.SentryLogParameters import io.sentry.protocol.SdkVersion import io.sentry.transport.ITransport import kotlin.test.BeforeTest 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.mock import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.whenever import timber.log.Timber class SentryTimberIntegrationTest { private class Fixture { val scopes = mock() + val logs = mock() val options = SentryOptions().apply { sdkVersion = SdkVersion("test", "1.2.3") } + init { + whenever(scopes.logger()).thenReturn(logs) + } + fun getSut( minEventLevel: SentryLevel = SentryLevel.ERROR, minBreadcrumbLevel: SentryLevel = SentryLevel.INFO, minLogsLevel: SentryLogLevel = SentryLogLevel.INFO, + enableLogs: Boolean? = null, ): SentryTimberIntegration = - SentryTimberIntegration( - minEventLevel = minEventLevel, - minBreadcrumbLevel = minBreadcrumbLevel, - minLogsLevel = minLogsLevel, - ) + if (enableLogs == null) { + SentryTimberIntegration( + minEventLevel = minEventLevel, + minBreadcrumbLevel = minBreadcrumbLevel, + minLogsLevel = minLogsLevel, + ) + } else { + SentryTimberIntegration( + minEventLevel = minEventLevel, + minBreadcrumbLevel = minBreadcrumbLevel, + minLogsLevel = minLogsLevel, + enableLogs = enableLogs, + ) + } } private val fixture = Fixture() @@ -64,6 +84,30 @@ class SentryTimberIntegrationTest { verify(fixture.scopes).captureEvent(any()) } + @Test + fun `Manual integration defaults logs to disabled while capturing events and breadcrumbs`() { + val sut = fixture.getSut() + sut.register(fixture.scopes, fixture.options) + + assertFalse(sut.enableLogs) + Timber.e("message") + + verify(fixture.scopes).captureEvent(any()) + verify(fixture.scopes).addBreadcrumb(any()) + verifyNoInteractions(fixture.logs) + } + + @Test + fun `Manual integration captures logs when enabled`() { + val sut = fixture.getSut(enableLogs = true) + sut.register(fixture.scopes, fixture.options) + + assertTrue(sut.enableLogs) + Timber.i("message") + + verify(fixture.logs).log(any(), any(), any()) + } + @Test fun `Integrations removes a tree from Timber on close integration`() { val sut = fixture.getSut() diff --git a/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberTreeTest.kt b/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberTreeTest.kt index f1d6d5a51bd..457ab31d33c 100644 --- a/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberTreeTest.kt +++ b/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberTreeTest.kt @@ -31,11 +31,16 @@ class SentryTimberTreeTest { minEventLevel: SentryLevel = SentryLevel.ERROR, minBreadcrumbLevel: SentryLevel = SentryLevel.INFO, minLogsLevel: SentryLogLevel = SentryLogLevel.INFO, + enableLogs: Boolean? = true, ): SentryTimberTree { logs = mock() scopes = mock() whenever(scopes.logger()).thenReturn(logs) - return SentryTimberTree(scopes, minEventLevel, minBreadcrumbLevel, minLogsLevel) + return if (enableLogs == null) { + SentryTimberTree(scopes, minEventLevel, minBreadcrumbLevel, minLogsLevel) + } else { + SentryTimberTree(scopes, minEventLevel, minBreadcrumbLevel, minLogsLevel, enableLogs) + } } } @@ -296,6 +301,17 @@ class SentryTimberTreeTest { sut.d("test %s, %s", 1, 1) } + @Test + fun `Tree defaults logs to disabled while capturing events and breadcrumbs`() { + val sut = fixture.getSut(enableLogs = null) + + sut.e("message") + + verify(fixture.scopes).captureEvent(any()) + verify(fixture.scopes).addBreadcrumb(any()) + verifyNoInteractions(fixture.logs) + } + @Test fun `Tree adds a log with message and arguments, when provided`() { val sut = fixture.getSut() From 4f677fbb9d33e4f27174e64b4d6d65e346640796 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 12 Aug 2026 16:21:04 +0200 Subject: [PATCH 09/44] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7af676f676a..8ff42f9fe41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- 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)) - Add an explicit Logs opt-in to the Log4j2 appender ([#5941](https://github.com/getsentry/sentry-java/pull/5941)) - Add an explicit Logs opt-in to the Logback appender ([#5940](https://github.com/getsentry/sentry-java/pull/5940)) From 6a65f6f936f3caa27721be8c46db682179148c4e Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 12 Aug 2026 16:28:36 +0200 Subject: [PATCH 10/44] feat(android): Add Logcat Logs opt-in Require an explicit Android option before forwarding Logcat calls as Sentry Logs. Support Java and manifest configuration while keeping breadcrumbs and Android Log calls unchanged. Co-Authored-By: Claude --- .../api/sentry-android-core.api | 2 + .../android/core/ManifestMetadataReader.java | 5 +++ .../android/core/SentryAndroidOptions.java | 11 +++++ .../android/core/SentryLogcatAdapter.java | 7 ++- .../core/ManifestMetadataReaderTest.kt | 31 +++++++++++++ .../android/core/SentryAndroidOptionsTest.kt | 14 ++++++ .../android/core/SentryLogcatAdapterTest.kt | 45 +++++++++++++++++-- 7 files changed, 110 insertions(+), 5 deletions(-) diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api index 9e3b0fcf174..16b9caabc48 100644 --- a/sentry-android-core/api/sentry-android-core.api +++ b/sentry-android-core/api/sentry-android-core.api @@ -430,6 +430,7 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr public fun isEnableAutoActivityLifecycleTracing ()Z public fun isEnableAutoTraceIdGeneration ()Z public fun isEnableFramesTracking ()Z + public fun isEnableLogcatLogs ()Z public fun isEnableNdk ()Z public fun isEnableNdkAppHangTracking ()Z public fun isEnableNetworkEventBreadcrumbs ()Z @@ -464,6 +465,7 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr public fun setEnableAutoActivityLifecycleTracing (Z)V public fun setEnableAutoTraceIdGeneration (Z)V public fun setEnableFramesTracking (Z)V + public fun setEnableLogcatLogs (Z)V public fun setEnableNdk (Z)V public fun setEnableNdkAppHangTracking (Z)V public fun setEnableNetworkEventBreadcrumbs (Z)V 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 ddebd1b5eef..ff6c3cbcd78 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 @@ -165,6 +165,8 @@ final class ManifestMetadataReader { static final String ENABLE_TIMBER_LOGS = "io.sentry.timber.logs.enabled"; + static final String ENABLE_LOGCAT_LOGS = "io.sentry.logcat.logs.enabled"; + static final String ENABLE_METRICS = "io.sentry.metrics.enabled"; static final String ENABLE_AUTO_TRACE_ID_GENERATION = @@ -711,6 +713,9 @@ static void applyMetadata( options.setEnableTimberLogs( readBool(metadata, logger, ENABLE_TIMBER_LOGS, options.isEnableTimberLogs())); + options.setEnableLogcatLogs( + readBool(metadata, logger, ENABLE_LOGCAT_LOGS, options.isEnableLogcatLogs())); + options .getMetrics() .setEnabled( diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java index a0ca7a36c24..ade7a5fc953 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroidOptions.java @@ -74,6 +74,9 @@ public final class SentryAndroidOptions extends SentryOptions { /** Enable or disable automatic Sentry Logs capture from Timber. Default is disabled. */ private boolean enableTimberLogs = false; + /** Enable or disable automatic Sentry Logs capture from Logcat. Default is disabled. */ + private boolean enableLogcatLogs = false; + /** * Enables the Auto instrumentation for Activity lifecycle tracing. * @@ -468,6 +471,14 @@ public void setEnableTimberLogs(boolean enableTimberLogs) { this.enableTimberLogs = enableTimberLogs; } + public boolean isEnableLogcatLogs() { + return enableLogcatLogs; + } + + public void setEnableLogcatLogs(boolean enableLogcatLogs) { + this.enableLogcatLogs = enableLogcatLogs; + } + /** * Enable or disable all the automatic breadcrumbs * diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java index 1e649c1783f..bab26e9c3e7 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java @@ -6,6 +6,7 @@ import io.sentry.Sentry; import io.sentry.SentryLevel; import io.sentry.SentryLogLevel; +import io.sentry.SentryOptions; import io.sentry.logger.SentryLogParameters; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -52,8 +53,10 @@ private static void addAsLog( @Nullable final String msg, @Nullable final Throwable tr) { final @NotNull ScopesAdapter scopes = ScopesAdapter.getInstance(); - // Check if logs are enabled before doing expensive operations - if (!scopes.getOptions().getLogs().isEnabled()) { + final @NotNull SentryOptions options = scopes.getOptions(); + if (!(options instanceof SentryAndroidOptions) + || !((SentryAndroidOptions) options).isEnableLogcatLogs() + || !options.getLogs().isEnabled()) { return; } final @Nullable String trMessage = tr != null ? tr.getMessage() : null; 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 f0d6e6f562d..b8cf2224b22 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 @@ -4,6 +4,7 @@ import android.content.Context import android.os.Bundle import androidx.core.os.bundleOf import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat import io.sentry.FilterString import io.sentry.ILogger import io.sentry.ProfileLifecycle @@ -1986,6 +1987,36 @@ class ManifestMetadataReaderTest { assertTrue(fixture.options.isEnableTimberLogs) } + @Test + fun `applyMetadata keeps Logcat logs disabled if not found`() { + val context = fixture.getContext() + + ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) + + assertThat(fixture.options.isEnableLogcatLogs).isFalse() + } + + @Test + fun `applyMetadata reads Logcat logs enabled to options`() { + val bundle = bundleOf(ManifestMetadataReader.ENABLE_LOGCAT_LOGS to true) + val context = fixture.getContext(metaData = bundle) + + ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) + + assertThat(fixture.options.isEnableLogcatLogs).isTrue() + } + + @Test + fun `applyMetadata reads Logcat logs disabled to options`() { + fixture.options.isEnableLogcatLogs = true + val bundle = bundleOf(ManifestMetadataReader.ENABLE_LOGCAT_LOGS to false) + val context = fixture.getContext(metaData = bundle) + + ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) + + assertThat(fixture.options.isEnableLogcatLogs).isFalse() + } + @Test fun `applyMetadata reads metrics enabled and keep default value if not found`() { // Arrange diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidOptionsTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidOptionsTest.kt index 2e5fa0ea45d..4d1fbad0dd7 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidOptionsTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidOptionsTest.kt @@ -1,5 +1,6 @@ package io.sentry.android.core +import com.google.common.truth.Truth.assertThat import io.sentry.ITransactionProfiler import io.sentry.NoOpTransactionProfiler import io.sentry.protocol.DebugImage @@ -108,6 +109,19 @@ class SentryAndroidOptionsTest { assertTrue(sentryOptions.isEnableTimberLogs) } + @Test + fun `Logcat logs are disabled by default`() { + assertThat(SentryAndroidOptions().isEnableLogcatLogs).isFalse() + } + + @Test + fun `Logcat logs can be enabled`() { + val sentryOptions = SentryAndroidOptions() + sentryOptions.isEnableLogcatLogs = true + + assertThat(sentryOptions.isEnableLogcatLogs).isTrue() + } + @Test fun `attach screenshots disabled by default for Android`() { val sentryOptions = SentryAndroidOptions() diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt index 0c0c03d71d2..423df279f1e 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt @@ -2,6 +2,7 @@ package io.sentry.android.core import android.os.Bundle import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat import io.sentry.Breadcrumb import io.sentry.Sentry import io.sentry.SentryLevel @@ -15,6 +16,7 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue import org.junit.runner.RunWith +import org.robolectric.shadows.ShadowLog @RunWith(AndroidJUnit4::class) class SentryLogcatAdapterTest { @@ -26,9 +28,12 @@ class SentryLogcatAdapterTest { val breadcrumbs = mutableListOf() val logs = mutableListOf() - fun initSut(options: Sentry.OptionsConfiguration? = null) { - val metadata = - Bundle().apply { putString(ManifestMetadataReader.DSN, "https://key@sentry.io/123") } + fun initSut( + enableLogcatLogs: Boolean? = true, + metadata: Bundle = Bundle(), + options: Sentry.OptionsConfiguration? = null, + ) { + metadata.putString(ManifestMetadataReader.DSN, "https://key@sentry.io/123") val mockContext = ContextUtilsTestHelper.mockMetaData(metaData = metadata) initForTest(mockContext) { it.beforeBreadcrumb = SentryOptions.BeforeBreadcrumbCallback { breadcrumb, _ -> @@ -36,6 +41,9 @@ class SentryLogcatAdapterTest { breadcrumb } it.logs.isEnabled = true + if (enableLogcatLogs != null) { + it.isEnableLogcatLogs = enableLogcatLogs + } it.logs.beforeSend = SentryOptions.Logs.BeforeSendLogCallback { logEvent -> logs.add(logEvent) @@ -55,6 +63,37 @@ class SentryLogcatAdapterTest { Sentry.close() fixture.breadcrumbs.clear() fixture.logs.clear() + ShadowLog.clear() + } + + @Test + fun `Logcat logs are disabled by default while breadcrumbs and Android Log remain enabled`() { + fixture.initSut(enableLogcatLogs = null) + + SentryLogcatAdapter.d(tag, commonMsg) + + assertThat(fixture.logs).isEmpty() + assertThat(fixture.breadcrumbs).hasSize(1) + assertThat(ShadowLog.getLogs().any { it.tag == tag && it.msg == commonMsg }).isTrue() + } + + @Test + fun `Logcat logs can be enabled through Android options`() { + fixture.initSut(enableLogcatLogs = true) + + SentryLogcatAdapter.d(tag, commonMsg) + + assertThat(fixture.logs).hasSize(1) + } + + @Test + fun `Logcat logs can be enabled through manifest metadata`() { + val metadata = Bundle().apply { putBoolean(ManifestMetadataReader.ENABLE_LOGCAT_LOGS, true) } + fixture.initSut(enableLogcatLogs = null, metadata = metadata) + + SentryLogcatAdapter.d(tag, commonMsg) + + assertThat(fixture.logs).hasSize(1) } @Test From c432ec554aa61235def4b6a0aa59aca7ffa4017e Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 12 Aug 2026 16:29:14 +0200 Subject: [PATCH 11/44] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ff42f9fe41..fd43f77bbbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- 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)) - Add an explicit Logs opt-in to the Log4j2 appender ([#5941](https://github.com/getsentry/sentry-java/pull/5941)) From ae8f80434b506a6ba1393ea547641b0b973de05b Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 12 Aug 2026 16:34:27 +0200 Subject: [PATCH 12/44] 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 13/44] 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 6a22345980e2e0d7aab1710b1e8cdf6c78c812ee Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 12 Aug 2026 16:43:11 +0200 Subject: [PATCH 14/44] feat(core): Remove Logs enable flag Capture manual Sentry Logs without an aggregate enable option and always create the configured logger batch processor. Keep automatic logging integrations controlled by their local opt-ins and remove legacy Logs configuration from active samples and fixtures. Co-Authored-By: Claude --- .../android/core/ManifestMetadataReader.java | 4 -- .../android/core/SentryLogcatAdapter.java | 3 +- .../core/ManifestMetadataReaderTest.kt | 25 ------- .../android/core/SentryLogcatAdapterTest.kt | 22 ------- .../java/io/sentry/jul/SentryHandler.java | 4 +- .../kotlin/io/sentry/jul/SentryHandlerTest.kt | 7 +- .../src/test/resources/sentry.properties | 1 - .../java/io/sentry/log4j2/SentryAppender.java | 4 +- .../io/sentry/log4j2/SentryAppenderTest.kt | 10 +-- .../src/test/resources/sentry.properties | 1 - .../io/sentry/logback/SentryAppender.java | 4 +- .../io/sentry/logback/SentryAppenderTest.kt | 10 ++- .../src/main/AndroidManifest.xml | 9 ++- .../java/io/sentry/samples/console/Main.java | 1 - .../src/main/resources/sentry.properties | 1 - .../src/main/resources/sentry.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 - .../src/main/resources/application.properties | 1 - .../boot4/SentryAutoConfigurationTest.kt | 2 - .../jakarta/SentryAutoConfigurationTest.kt | 2 - .../boot/SentryAutoConfigurationTest.kt | 2 - sentry/api/sentry.api | 2 - .../src/main/java/io/sentry/SentryClient.java | 8 +-- .../main/java/io/sentry/SentryOptions.java | 25 ------- .../main/java/io/sentry/logger/LoggerApi.java | 7 -- sentry/src/test/java/io/sentry/ScopesTest.kt | 65 +++++++------------ .../test/java/io/sentry/SentryClientTest.kt | 6 +- .../test/java/io/sentry/SentryOptionsTest.kt | 1 - 39 files changed, 48 insertions(+), 192 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 ff6c3cbcd78..3161db3720e 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,10 +706,6 @@ static void applyMetadata( } } - options - .getLogs() - .setEnabled(readBool(metadata, logger, ENABLE_LOGS, options.getLogs().isEnabled())); - options.setEnableTimberLogs( readBool(metadata, logger, ENABLE_TIMBER_LOGS, options.isEnableTimberLogs())); diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java index bab26e9c3e7..998ce58b3db 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java @@ -55,8 +55,7 @@ private static void addAsLog( final @NotNull ScopesAdapter scopes = ScopesAdapter.getInstance(); final @NotNull SentryOptions options = scopes.getOptions(); if (!(options instanceof SentryAndroidOptions) - || !((SentryAndroidOptions) options).isEnableLogcatLogs() - || !options.getLogs().isEnabled()) { + || !((SentryAndroidOptions) options).isEnableLogcatLogs()) { return; } final @Nullable String trMessage = tr != null ? tr.getMessage() : null; 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 b8cf2224b22..b7ea7b962af 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 @@ -1943,31 +1943,6 @@ class ManifestMetadataReaderTest { assertTrue(fixture.options.inAppExcludes.isEmpty()) } - @Test - fun `applyMetadata reads logs enabled and keep default value if not found`() { - // Arrange - val context = fixture.getContext() - - // Act - ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) - - // Assert - assertFalse(fixture.options.logs.isEnabled) - } - - @Test - fun `applyMetadata reads logs enabled to options`() { - // Arrange - val bundle = bundleOf(ManifestMetadataReader.ENABLE_LOGS to true) - val context = fixture.getContext(metaData = bundle) - - // Act - ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) - - // Assert - assertTrue(fixture.options.logs.isEnabled) - } - @Test fun `applyMetadata keeps Timber logs disabled if not found`() { val context = fixture.getContext() diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt index 423df279f1e..582f475f68d 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt @@ -14,7 +14,6 @@ import java.lang.RuntimeException import kotlin.test.AfterTest import kotlin.test.Test import kotlin.test.assertEquals -import kotlin.test.assertTrue import org.junit.runner.RunWith import org.robolectric.shadows.ShadowLog @@ -40,7 +39,6 @@ class SentryLogcatAdapterTest { breadcrumbs.add(breadcrumb) breadcrumb } - it.logs.isEnabled = true if (enableLogcatLogs != null) { it.isEnableLogcatLogs = enableLogcatLogs } @@ -205,26 +203,6 @@ class SentryLogcatAdapterTest { .assert("$commonMsg wtf exception\n${throwable.message}", SentryLogLevel.FATAL) } - @Test - fun `do not send logs if logs is disabled`() { - fixture.initSut { it.logs.isEnabled = false } - - SentryLogcatAdapter.v(tag, "$commonMsg verbose") - SentryLogcatAdapter.i(tag, "$commonMsg info") - SentryLogcatAdapter.d(tag, "$commonMsg debug") - SentryLogcatAdapter.w(tag, "$commonMsg warning") - SentryLogcatAdapter.e(tag, "$commonMsg error") - SentryLogcatAdapter.wtf(tag, "$commonMsg wtf") - SentryLogcatAdapter.e(tag, "$commonMsg error exception", throwable) - SentryLogcatAdapter.v(tag, "$commonMsg verbose exception", throwable) - SentryLogcatAdapter.i(tag, "$commonMsg info exception", throwable) - SentryLogcatAdapter.d(tag, "$commonMsg debug exception", throwable) - SentryLogcatAdapter.w(tag, "$commonMsg warning exception", throwable) - SentryLogcatAdapter.wtf(tag, "$commonMsg wtf exception", throwable) - - assertTrue(fixture.logs.isEmpty()) - } - @Test fun `logs add correct number of breadcrumb`() { fixture.initSut() diff --git a/sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java b/sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java index 37ce146d3c6..c606bdf88b6 100644 --- a/sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java +++ b/sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java @@ -114,9 +114,7 @@ public void publish(final @NotNull LogRecord record) { return; } try { - if (enableLogs - && ScopesAdapter.getInstance().getOptions().getLogs().isEnabled() - && record.getLevel().intValue() >= minimumLevel.intValue()) { + if (enableLogs && record.getLevel().intValue() >= minimumLevel.intValue()) { captureLog(record); } if (record.getLevel().intValue() >= minimumEventLevel.intValue()) { diff --git a/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt b/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt index a169c710b0a..2cd10a81cd4 100644 --- a/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt +++ b/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt @@ -42,7 +42,6 @@ class SentryHandlerTest { contextTags: List? = null, printfStyle: Boolean? = null, enableLogs: Boolean? = true, - enableGlobalLogs: Boolean = true, ) { var logger: Logger var handler: SentryHandler @@ -51,7 +50,6 @@ class SentryHandlerTest { val options = SentryOptions() options.dsn = "http://key@localhost/proj" options.setTransportFactory { _, _ -> transport } - options.logs.isEnabled = enableGlobalLogs options.logs.loggerBatchProcessorFactory = ILoggerBatchProcessorFactory { options, client -> LoggerBatchProcessor(options, client, ImmediateExecutorService()) } @@ -438,7 +436,7 @@ class SentryHandlerTest { @Test fun `does not capture logs by default`() { - fixture = Fixture(enableLogs = null, enableGlobalLogs = true) + fixture = Fixture(enableLogs = null) assertFalse(fixture.handler.isEnableLogs) fixture.logger.info("this should not be captured as a log") @@ -449,7 +447,7 @@ class SentryHandlerTest { @Test fun `captures logs when enabled through Java`() { - fixture = Fixture(enableLogs = true, enableGlobalLogs = true) + fixture = Fixture(enableLogs = true) assertTrue(fixture.handler.isEnableLogs) fixture.logger.info("this should be captured as a log") @@ -470,7 +468,6 @@ class SentryHandlerTest { minimumBreadcrumbLevel = Level.INFO, minimumEventLevel = Level.SEVERE, enableLogs = false, - enableGlobalLogs = true, ) fixture.logger.info("this should be a breadcrumb") diff --git a/sentry-jul/src/test/resources/sentry.properties b/sentry-jul/src/test/resources/sentry.properties index 0163b4f2f84..12c5db4eb9d 100644 --- a/sentry-jul/src/test/resources/sentry.properties +++ b/sentry-jul/src/test/resources/sentry.properties @@ -1,2 +1 @@ release=release from sentry.properties -logs.enabled=true diff --git a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java index 78cfcd97005..39d17a30c11 100644 --- a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java +++ b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java @@ -257,9 +257,7 @@ void start(final @NotNull Sentry.OptionsConfiguration optionsConf @Override public void append(final @NotNull LogEvent eventObject) { - if (enableLogs - && scopes.getOptions().getLogs().isEnabled() - && eventObject.getLevel().isMoreSpecificThan(minimumLevel)) { + if (enableLogs && eventObject.getLevel().isMoreSpecificThan(minimumLevel)) { captureLog(eventObject); } if (eventObject.getLevel().isMoreSpecificThan(minimumEventLevel)) { diff --git a/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt b/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt index cb33115e647..fe5d3f4112c 100644 --- a/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt +++ b/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt @@ -63,7 +63,6 @@ class SentryAppenderTest { debug: Boolean? = null, contextTags: List? = null, enableLogs: Boolean = true, - enableGlobalLogs: Boolean = true, ): ExtendedLogger { if (transportFactory != null) { this.transportFactory = transportFactory @@ -106,7 +105,6 @@ class SentryAppenderTest { appender.start( appender.getOptionsConfiguration { options -> - options.logs.isEnabled = enableGlobalLogs options.logs.loggerBatchProcessorFactory = ILoggerBatchProcessorFactory { options, client -> LoggerBatchProcessor(options, client, ImmediateExecutorService()) @@ -264,7 +262,7 @@ class SentryAppenderTest { @Test fun `does not capture logs when local logs are disabled`() { - val logger = fixture.getSut(enableLogs = false, enableGlobalLogs = true) + val logger = fixture.getSut(enableLogs = false) logger.info("this should not be captured as a log") Sentry.flush(10) @@ -273,8 +271,8 @@ class SentryAppenderTest { } @Test - fun `captures logs when local and aggregate logs are enabled`() { - val logger = fixture.getSut(enableLogs = true, enableGlobalLogs = true) + fun `captures logs when local logs are enabled`() { + val logger = fixture.getSut(enableLogs = true) logger.info("this should be captured as a log") Sentry.flush(10) @@ -294,7 +292,6 @@ class SentryAppenderTest { minimumBreadcrumbLevel = Level.INFO, minimumEventLevel = Level.ERROR, enableLogs = false, - enableGlobalLogs = true, ) logger.info("this should be a breadcrumb") @@ -397,7 +394,6 @@ class SentryAppenderTest { fun `plugin attribute enables logs with explicit opt in`() { initForTest { it.dsn = "http://key@localhost/proj" - it.logs.isEnabled = true } val event = mock() whenever(event.level).thenReturn(Level.INFO) diff --git a/sentry-log4j2/src/test/resources/sentry.properties b/sentry-log4j2/src/test/resources/sentry.properties index 9845650aace..ec87ba75304 100644 --- a/sentry-log4j2/src/test/resources/sentry.properties +++ b/sentry-log4j2/src/test/resources/sentry.properties @@ -1,4 +1,3 @@ release=release from sentry.properties -logs.enabled=true shutdown-timeout-millis=0 session-flush-timeout-millis=0 diff --git a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java index f36d988c97b..2beca04e82f 100644 --- a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java +++ b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java @@ -88,9 +88,7 @@ public void start() { @Override protected void append(@NotNull ILoggingEvent eventObject) { - if (enableLogs - && ScopesAdapter.getInstance().getOptions().getLogs().isEnabled() - && eventObject.getLevel().isGreaterOrEqual(minimumLevel)) { + if (enableLogs && eventObject.getLevel().isGreaterOrEqual(minimumLevel)) { captureLog(eventObject); } if (eventObject.getLevel().isGreaterOrEqual(minimumEventLevel)) { diff --git a/sentry-logback/src/test/kotlin/io/sentry/logback/SentryAppenderTest.kt b/sentry-logback/src/test/kotlin/io/sentry/logback/SentryAppenderTest.kt index 7aafe705277..153fcb2f635 100644 --- a/sentry-logback/src/test/kotlin/io/sentry/logback/SentryAppenderTest.kt +++ b/sentry-logback/src/test/kotlin/io/sentry/logback/SentryAppenderTest.kt @@ -56,7 +56,6 @@ class SentryAppenderTest { encoder: Encoder? = null, sendDefaultPii: Boolean = false, enableLogs: Boolean = false, - enableGlobalLogs: Boolean = enableLogs, options: SentryOptions = SentryOptions(), startLater: Boolean = false, ) { @@ -73,7 +72,6 @@ class SentryAppenderTest { this.encoder = encoder options.dsn = dsn options.isSendDefaultPii = sendDefaultPii - options.logs.isEnabled = enableGlobalLogs options.logs.loggerBatchProcessorFactory = ILoggerBatchProcessorFactory { options, client -> LoggerBatchProcessor(options, client, ImmediateExecutorService()) } @@ -326,8 +324,8 @@ class SentryAppenderTest { } @Test - fun `does not capture logs by default when aggregate logs are enabled`() { - fixture = Fixture(enableGlobalLogs = true) + fun `does not capture logs by default`() { + fixture = Fixture(enableLogs = false) assertFalse(fixture.appender.isEnableLogs) fixture.logger.info("this should not be captured as a log") @@ -337,7 +335,7 @@ class SentryAppenderTest { } @Test - fun `captures logs when local and aggregate logs are enabled`() { + fun `captures logs when local logs are enabled`() { fixture = Fixture(enableLogs = true) assertTrue(fixture.appender.isEnableLogs) @@ -358,7 +356,7 @@ class SentryAppenderTest { Fixture( minimumBreadcrumbLevel = Level.INFO, minimumEventLevel = Level.ERROR, - enableGlobalLogs = true, + enableLogs = false, ) fixture.logger.info("this should be a breadcrumb") diff --git a/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml b/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml index ac53c538de5..77d197e244c 100644 --- a/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml +++ b/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml @@ -131,9 +131,14 @@ android:name="io.sentry.debug" android:value="${sentryDebug}" /> - + + + + diff --git a/sentry-samples/sentry-samples-console-otlp/src/main/java/io/sentry/samples/console/Main.java b/sentry-samples/sentry-samples-console-otlp/src/main/java/io/sentry/samples/console/Main.java index d973a68a907..3a21e8220c6 100644 --- a/sentry-samples/sentry-samples-console-otlp/src/main/java/io/sentry/samples/console/Main.java +++ b/sentry-samples/sentry-samples-console-otlp/src/main/java/io/sentry/samples/console/Main.java @@ -133,7 +133,6 @@ public static void main(String[] args) throws InterruptedException { // } // }); - options.getLogs().setEnabled(true); }); Sentry.addBreadcrumb( diff --git a/sentry-samples/sentry-samples-jul/src/main/resources/sentry.properties b/sentry-samples/sentry-samples-jul/src/main/resources/sentry.properties index ac73ce04179..390771a4403 100644 --- a/sentry-samples/sentry-samples-jul/src/main/resources/sentry.properties +++ b/sentry-samples/sentry-samples-jul/src/main/resources/sentry.properties @@ -4,4 +4,3 @@ debug=true environment=staging in-app-includes=io.sentry.samples context-tags=userId,requestId -logs.enabled=true diff --git a/sentry-samples/sentry-samples-log4j2/src/main/resources/sentry.properties b/sentry-samples/sentry-samples-log4j2/src/main/resources/sentry.properties index b2310e08f89..a7dca6edc4e 100644 --- a/sentry-samples/sentry-samples-log4j2/src/main/resources/sentry.properties +++ b/sentry-samples/sentry-samples-log4j2/src/main/resources/sentry.properties @@ -1,3 +1,2 @@ in-app-includes="io.sentry.samples" -logs.enabled=true debug=true 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 9e53be98324..d8b1bcd2bb6 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 @@ -15,7 +15,6 @@ sentry.graphql.ignored-error-types=SOME_ERROR,ANOTHER_ERROR 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 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 3d19e15d7cb..bf302c6dd05 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 @@ -15,7 +15,6 @@ sentry.graphql.ignored-error-types=SOME_ERROR,ANOTHER_ERROR 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 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 483ae19db6d..05a35327d86 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 @@ -16,7 +16,6 @@ sentry.enable-backpressure-handling=true 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 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 130da1d07cb..2e897e5c714 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 @@ -10,7 +10,6 @@ sentry.logging.minimum-breadcrumb-level=debug 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 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 d52b74778c0..40a5843c134 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 @@ -16,7 +16,6 @@ sentry.enable-backpressure-handling=true 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 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 7512b72c554..7f5880b741a 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 @@ -15,7 +15,6 @@ sentry.graphql.ignored-error-types=SOME_ERROR,ANOTHER_ERROR 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 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 78b3894a49e..4b80755d846 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 @@ -15,7 +15,6 @@ sentry.graphql.ignored-error-types=SOME_ERROR,ANOTHER_ERROR 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 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 02c6cf72430..d71c2c433ab 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 @@ -16,7 +16,6 @@ sentry.enable-backpressure-handling=true 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 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 ab866094ebf..af217277c78 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 @@ -14,7 +14,6 @@ sentry.debug=true 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 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 69b87411656..404549c12da 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 @@ -14,7 +14,6 @@ sentry.debug=true 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 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 d228dedf5ac..45d04440f88 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 @@ -10,7 +10,6 @@ sentry.logging.minimum-breadcrumb-level=debug 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" 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 8e3517eb96a..5e85915a9c9 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 @@ -12,7 +12,6 @@ spring.graphql.graphiql.enabled=true 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" 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 b02107af7e0..bce0ce41f53 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 @@ -14,7 +14,6 @@ sentry.debug=true 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 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 115df010b95..da87abd5ee9 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 @@ -243,7 +243,6 @@ class SentryAutoConfigurationTest { "sentry.cron.default-timezone=America/New_York", "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,7 +300,6 @@ class SentryAutoConfigurationTest { assertThat(options.cron!!.defaultTimezone).isEqualTo("America/New_York") 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-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 710752752aa..4887ef837ff 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 @@ -245,7 +245,6 @@ class SentryAutoConfigurationTest { "sentry.cron.default-timezone=America/New_York", "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", @@ -305,7 +304,6 @@ class SentryAutoConfigurationTest { assertThat(options.cron!!.defaultTimezone).isEqualTo("America/New_York") 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) 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 8f14b359e6c..6a4a251474a 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 @@ -243,7 +243,6 @@ class SentryAutoConfigurationTest { "sentry.cron.default-timezone=America/New_York", "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", @@ -303,7 +302,6 @@ class SentryAutoConfigurationTest { assertThat(options.cron!!.defaultTimezone).isEqualTo("America/New_York") 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) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 774bfd90bf3..4f3980b9f7d 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -3964,9 +3964,7 @@ public final class io/sentry/SentryOptions$Logs { public fun ()V public fun getBeforeSend ()Lio/sentry/SentryOptions$Logs$BeforeSendLogCallback; public fun getLoggerBatchProcessorFactory ()Lio/sentry/logger/ILoggerBatchProcessorFactory; - public fun isEnabled ()Z public fun setBeforeSend (Lio/sentry/SentryOptions$Logs$BeforeSendLogCallback;)V - public fun setEnabled (Z)V public fun setLoggerBatchProcessorFactory (Lio/sentry/logger/ILoggerBatchProcessorFactory;)V } diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 92037f6690b..04ef439ba6b 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -9,7 +9,6 @@ import io.sentry.hints.DiskFlushNotification; import io.sentry.hints.TransactionEnd; import io.sentry.logger.ILoggerBatchProcessor; -import io.sentry.logger.NoOpLoggerBatchProcessor; import io.sentry.metrics.IMetricsBatchProcessor; import io.sentry.metrics.NoOpMetricsBatchProcessor; import io.sentry.protocol.Contexts; @@ -62,12 +61,7 @@ public SentryClient(final @NotNull SentryOptions options) { final RequestDetailsResolver requestDetailsResolver = new RequestDetailsResolver(options); transport = transportFactory.create(options, requestDetailsResolver.resolve()); - if (options.getLogs().isEnabled()) { - loggerBatchProcessor = - options.getLogs().getLoggerBatchProcessorFactory().create(options, this); - } else { - loggerBatchProcessor = NoOpLoggerBatchProcessor.getInstance(); - } + loggerBatchProcessor = options.getLogs().getLoggerBatchProcessorFactory().create(options, this); if (options.getMetrics().isEnabled()) { metricsBatchProcessor = options.getMetrics().getMetricsBatchProcessorFactory().create(options, this); diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index dc9d9521dbb..16748005af2 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -3733,10 +3733,6 @@ public void merge(final @NotNull ExternalOptions options) { } } - if (options.isEnableLogs() != null) { - getLogs().setEnabled(options.isEnableLogs()); - } - if (options.isEnableMetrics() != null) { getMetrics().setEnabled(options.isEnableMetrics()); } @@ -3954,9 +3950,6 @@ public void setDefaultRecoveryThreshold(@Nullable Long defaultRecoveryThreshold) public static final class Logs { - /** Whether Sentry Logs feature is enabled and Sentry.logger() usages are sent to Sentry. */ - private boolean enable = false; - /** * This function is called with an SDK specific log event object and can return a modified event * object or nothing to skip reporting the log item @@ -3966,24 +3959,6 @@ public static final class Logs { private @NotNull ILoggerBatchProcessorFactory loggerBatchProcessorFactory = new DefaultLoggerBatchProcessorFactory(); - /** - * Whether Sentry Logs feature is enabled and Sentry.logger() usages are sent to Sentry. - * - * @return true if Sentry Logs should be enabled - */ - public boolean isEnabled() { - return enable; - } - - /** - * Whether Sentry Logs feature is enabled and Sentry.logger() usages are sent to Sentry. - * - * @param enableLogs true if Sentry Logs should be enabled - */ - public void setEnabled(boolean enableLogs) { - this.enable = enableLogs; - } - /** * Returns the BeforeSendLog callback * diff --git a/sentry/src/main/java/io/sentry/logger/LoggerApi.java b/sentry/src/main/java/io/sentry/logger/LoggerApi.java index c203dcbfb8f..3741ddc6209 100644 --- a/sentry/src/main/java/io/sentry/logger/LoggerApi.java +++ b/sentry/src/main/java/io/sentry/logger/LoggerApi.java @@ -104,13 +104,6 @@ private void captureLog( return; } - if (!options.getLogs().isEnabled()) { - options - .getLogger() - .log(SentryLevel.WARNING, "Sentry Log is disabled and this 'logger' call is a no-op."); - return; - } - if (message == null) { return; } diff --git a/sentry/src/test/java/io/sentry/ScopesTest.kt b/sentry/src/test/java/io/sentry/ScopesTest.kt index 9d598aec885..9e44c9ae847 100644 --- a/sentry/src/test/java/io/sentry/ScopesTest.kt +++ b/sentry/src/test/java/io/sentry/ScopesTest.kt @@ -2517,16 +2517,8 @@ class ScopesTest { @Test fun `when captureLog is called on disabled client, do nothing`() { - val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } - sut.close() - - sut.logger().warn("test message") - verify(mockClient, never()).captureLog(any(), anyOrNull()) - } - - @Test - fun `when logging is not enabled, do nothing`() { val (sut, mockClient) = getEnabledScopes() + sut.close() sut.logger().warn("test message") verify(mockClient, never()).captureLog(any(), anyOrNull()) @@ -2534,7 +2526,7 @@ class ScopesTest { @Test fun `capturing null log does nothing`() { - val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() sut.logger().warn(null) verify(mockClient, never()).captureLog(any(), anyOrNull()) @@ -2542,7 +2534,7 @@ class ScopesTest { @Test fun `creating trace log works`() { - val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() sut.logger().trace("trace log message") @@ -2559,7 +2551,7 @@ class ScopesTest { @Test fun `creating debug log works`() { - val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() sut.logger().debug("debug log message") @@ -2576,7 +2568,7 @@ class ScopesTest { @Test fun `creating a info log works`() { - val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() sut.logger().info("info log message") @@ -2593,7 +2585,7 @@ class ScopesTest { @Test fun `creating warn log works`() { - val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() sut.logger().warn("warn log message") @@ -2610,7 +2602,7 @@ class ScopesTest { @Test fun `creating error log works`() { - val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() sut.logger().error("error log message") @@ -2627,7 +2619,7 @@ class ScopesTest { @Test fun `creating fatal log works`() { - val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() sut.logger().fatal("fatal log message") @@ -2644,7 +2636,7 @@ class ScopesTest { @Test fun `creating log works`() { - val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() sut.logger().log(SentryLogLevel.WARN, "log message") @@ -2661,7 +2653,7 @@ class ScopesTest { @Test fun `log with manual origin does not have origin attribute`() { - val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() sut.logger().log(SentryLogLevel.WARN, "log message") @@ -2677,7 +2669,7 @@ class ScopesTest { @Test fun `log with non manual origin does have origin attribute`() { - val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() sut .logger() @@ -2700,7 +2692,6 @@ class ScopesTest { fun `creating log with format string works`() { val (sut, mockClient) = getEnabledScopes { - it.logs.isEnabled = true it.environment = "testenv" it.release = "1.0" it.serverName = "srv1" @@ -2741,7 +2732,7 @@ class ScopesTest { @Test fun `creating log with timestamp works`() { - val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() sut.logger().log(SentryLogLevel.WARN, SentryLongDate(123), "log message") @@ -2759,7 +2750,7 @@ class ScopesTest { @Test fun `creating log with attributes from map works`() { - val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() sut .logger() @@ -2786,7 +2777,7 @@ class ScopesTest { @Test fun `creating log with attributes works`() { - val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() sut .logger() @@ -2862,7 +2853,7 @@ class ScopesTest { @Test fun `creating log with attributes and timestamp works`() { - val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() sut .logger() @@ -2893,7 +2884,7 @@ class ScopesTest { @Test fun `creating log with attributes and timestamp and format string works`() { - val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() sut .logger() @@ -2948,7 +2939,7 @@ class ScopesTest { @Test fun `creating log with without args does not add template attribute`() { - val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() sut.logger().log(SentryLogLevel.WARN, "log %s") @@ -2971,7 +2962,7 @@ class ScopesTest { @Test fun `captures format string on format error`() { - val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() sut.logger().log(SentryLogLevel.WARN, "log %d", "arg1") @@ -2998,7 +2989,6 @@ class ScopesTest { fun `adds user fields to log attributes if sendDefaultPii is true`() { val (sut, mockClient) = getEnabledScopes { - it.logs.isEnabled = true it.distinctId = "distinctId" it.isSendDefaultPii = true } @@ -3038,7 +3028,6 @@ class ScopesTest { fun `adds user fields to log attributes even if sendDefaultPii is false`() { val (sut, mockClient) = getEnabledScopes { - it.logs.isEnabled = true it.distinctId = "distinctId" } @@ -3077,7 +3066,6 @@ class ScopesTest { fun `unset user does provide distinct-id as user-id`() { val (sut, mockClient) = getEnabledScopes { - it.logs.isEnabled = true it.distinctId = "distinctId" } @@ -3100,7 +3088,6 @@ class ScopesTest { fun `unset user does provide null user-id when distinct-id is missing`() { val (sut, mockClient) = getEnabledScopes { - it.logs.isEnabled = true it.distinctId = null } @@ -3122,7 +3109,6 @@ class ScopesTest { fun `missing user fields do not break attributes`() { val (sut, mockClient) = getEnabledScopes { - it.logs.isEnabled = true it.isSendDefaultPii = true it.distinctId = "distinctId" } @@ -3145,7 +3131,7 @@ class ScopesTest { @Test fun `adds session replay id to log attributes`() { - val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() val replayId = SentryId() sut.scope.replayId = replayId sut.logger().log(SentryLogLevel.WARN, "log message") @@ -3163,7 +3149,7 @@ class ScopesTest { @Test fun `missing session replay id do not break attributes`() { - val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() sut.logger().log(SentryLogLevel.WARN, "log message") verify(mockClient) @@ -3179,7 +3165,7 @@ class ScopesTest { @Test fun `does not add session replay buffering to log attributes if no replay id in scope and in controller`() { - val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() sut.logger().log(SentryLogLevel.WARN, "log message") assertEquals(SentryId.EMPTY_ID, sut.options.replayController.replayId) @@ -3199,7 +3185,7 @@ class ScopesTest { @Test fun `does not add session replay buffering to log attributes if replay id in scope`() { - val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() val replayId = SentryId() sut.scope.replayId = replayId @@ -3223,7 +3209,6 @@ class ScopesTest { val mockReplayController = mock() val (sut, mockClient) = getEnabledScopes { - it.logs.isEnabled = true it.setReplayController(mockReplayController) } val replayId = SentryId() @@ -3247,7 +3232,7 @@ class ScopesTest { @Test fun `log event has spanId from active span`() { - val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() val transaction = sut.startTransaction( @@ -3273,7 +3258,7 @@ class ScopesTest { @Test fun `log event has spanId from propagation context when no active span`() { - val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() var propagationContext: PropagationContext? = null sut.configureScope { propagationContext = it.propagationContext } @@ -3385,7 +3370,7 @@ class ScopesTest { @Test fun `metric with non manual origin does have origin attribute`() { - val (sut, mockClient) = getEnabledScopes { it.logs.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() sut .metrics() diff --git a/sentry/src/test/java/io/sentry/SentryClientTest.kt b/sentry/src/test/java/io/sentry/SentryClientTest.kt index 02623556498..008597b428e 100644 --- a/sentry/src/test/java/io/sentry/SentryClientTest.kt +++ b/sentry/src/test/java/io/sentry/SentryClientTest.kt @@ -181,7 +181,7 @@ class SentryClientTest { @Test fun `when client is closed with isRestarting false, transport waits`() { - val sut = fixture.getSut { options -> options.logs.isEnabled = true } + val sut = fixture.getSut() assertTrue(sut.isEnabled) sut.close(false) assertNotEquals(0, fixture.sentryOptions.shutdownTimeoutMillis) @@ -195,7 +195,7 @@ class SentryClientTest { @Test fun `when client is closed with isRestarting true, transport does not wait`() { - val sut = fixture.getSut { options -> options.logs.isEnabled = true } + val sut = fixture.getSut() assertTrue(sut.isEnabled) sut.close(true) verify(fixture.transport).flush(eq(0)) @@ -307,7 +307,6 @@ class SentryClientTest { @Test fun `when beforeSend captures a log, the nested log is dropped`() { val scope = createScope() - fixture.sentryOptions.logs.isEnabled = true lateinit var sut: SentryClient fixture.sentryOptions.setBeforeSend { e, _ -> sut.captureLog( @@ -328,7 +327,6 @@ class SentryClientTest { @Test fun `when beforeSendLog logs again, the nested log is dropped and does not recurse`() { val scope = createScope() - fixture.sentryOptions.logs.isEnabled = true var invocations = 0 lateinit var sut: SentryClient fixture.sentryOptions.logs.setBeforeSend { l -> diff --git a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt index f1ee97459a1..29df56a1669 100644 --- a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt @@ -479,7 +479,6 @@ class SentryOptionsTest { assertTrue(options.isEnableSpotlight) assertEquals("http://local.sentry.io:1234", options.spotlightConnectionUrl) assertTrue(options.isGlobalHubMode!!) - assertTrue(options.logs.isEnabled!!) assertFalse(options.metrics.isEnabled) assertEquals(0.8, options.profileSessionSampleRate) assertEquals("/profiling-traces${File.separator}${hash}", options.profilingTracesDirPath) From 2121c086b05bdb2d7af3a59d25679979e3f92f1d Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 12 Aug 2026 16:44:04 +0200 Subject: [PATCH 15/44] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63d43b7ddbd..f7acf5cc301 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- Remove the aggregate Sentry Logs enable flag; manual `Sentry.logger()` calls now capture Logs by default ([#5947](https://github.com/getsentry/sentry-java/pull/5947)) - 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)) From fcb80405fbf0cb3dd748af1a50eae9693eed8e52 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 12 Aug 2026 16:55:10 +0200 Subject: [PATCH 16/44] 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 17/44] 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 18/44] 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 19/44] 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 0a41b97f83bafbd1d1d2487e78c3e0c4be17d88d Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 13 Aug 2026 05:53:43 +0200 Subject: [PATCH 20/44] feat(core): Remove Metrics enable flag Capture manual Metrics without an aggregate option and always create the configured batch processor. Keep legacy external and Android keys only for subsequent migration warnings. Co-Authored-By: Claude --- CHANGELOG.md | 1 + .../android/core/ManifestMetadataReader.java | 5 --- .../core/ManifestMetadataReaderTest.kt | 39 +++++-------------- sentry/api/sentry.api | 2 - .../src/main/java/io/sentry/SentryClient.java | 9 +---- .../main/java/io/sentry/SentryOptions.java | 25 ------------ .../java/io/sentry/metrics/MetricsApi.java | 9 ----- sentry/src/test/java/io/sentry/ScopesTest.kt | 14 ++++--- .../test/java/io/sentry/SentryClientTest.kt | 7 ++++ .../test/java/io/sentry/SentryOptionsTest.kt | 14 ------- 10 files changed, 28 insertions(+), 97 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7acf5cc301..040701cbb5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- Remove the aggregate Sentry Metrics enable flag; `Sentry.metrics()` calls now capture Metrics by default ([#5953](https://github.com/getsentry/sentry-java/pull/5953)) - Remove the aggregate Sentry Logs enable flag; manual `Sentry.logger()` calls now capture Logs by default ([#5947](https://github.com/getsentry/sentry-java/pull/5947)) - 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)) 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 2aa10e98abc..38ee0c1d347 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 @@ -729,11 +729,6 @@ static void applyMetadata( options.setEnableLogcatLogs( readBool(metadata, logger, ENABLE_LOGCAT_LOGS, options.isEnableLogcatLogs())); - options - .getMetrics() - .setEnabled( - readBool(metadata, logger, ENABLE_METRICS, options.getMetrics().isEnabled())); - final @NotNull SentryFeedbackOptions feedbackOptions = options.getFeedbackOptions(); feedbackOptions.setNameRequired( readBool(metadata, logger, FEEDBACK_NAME_REQUIRED, feedbackOptions.isNameRequired())); 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 6a0b17cff70..c2dfa7d544a 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 @@ -11,6 +11,8 @@ import io.sentry.ProfileLifecycle import io.sentry.SentryLevel import io.sentry.SentryReplayOptions import io.sentry.TransactionOptions +import io.sentry.test.createSentryClientMock +import io.sentry.test.createTestScopes import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -20,6 +22,7 @@ import kotlin.test.assertNull import kotlin.test.assertTrue import org.junit.runner.RunWith 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 @@ -2051,41 +2054,17 @@ class ManifestMetadataReaderTest { } @Test - fun `applyMetadata reads metrics enabled and keep default value if not found`() { - // Arrange - val context = fixture.getContext() - - // Act - ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) - - // Assert - assertTrue(fixture.options.metrics.isEnabled) - } - - @Test - fun `applyMetadata reads metrics enabled to options`() { - // Arrange + fun `legacy metrics metadata does not disable capture`() { val bundle = bundleOf(ManifestMetadataReader.ENABLE_METRICS to false) val context = fixture.getContext(metaData = bundle) + val client = createSentryClientMock() - // Act ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) + fixture.options.dsn = "https://key@sentry.io/proj" + val scopes = createTestScopes(fixture.options).also { it.bindClient(client) } + scopes.metrics().count("metric name") - // Assert - assertFalse(fixture.options.metrics.isEnabled) - } - - @Test - fun `applyMetadata reads metrics enabled to options when set to true`() { - // Arrange - val bundle = bundleOf(ManifestMetadataReader.ENABLE_METRICS to true) - val context = fixture.getContext(metaData = bundle) - - // Act - ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) - - // Assert - assertTrue(fixture.options.metrics.isEnabled) + verify(client).captureMetric(any(), anyOrNull(), anyOrNull()) } @Test diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 4f3980b9f7d..5041ecf13f8 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -3976,9 +3976,7 @@ public final class io/sentry/SentryOptions$Metrics { public fun ()V public fun getBeforeSend ()Lio/sentry/SentryOptions$Metrics$BeforeSendMetricCallback; public fun getMetricsBatchProcessorFactory ()Lio/sentry/metrics/IMetricsBatchProcessorFactory; - public fun isEnabled ()Z public fun setBeforeSend (Lio/sentry/SentryOptions$Metrics$BeforeSendMetricCallback;)V - public fun setEnabled (Z)V public fun setMetricsBatchProcessorFactory (Lio/sentry/metrics/IMetricsBatchProcessorFactory;)V } diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 04ef439ba6b..b17abf3b17f 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -10,7 +10,6 @@ import io.sentry.hints.TransactionEnd; import io.sentry.logger.ILoggerBatchProcessor; import io.sentry.metrics.IMetricsBatchProcessor; -import io.sentry.metrics.NoOpMetricsBatchProcessor; import io.sentry.protocol.Contexts; import io.sentry.protocol.DebugMeta; import io.sentry.protocol.FeatureFlags; @@ -62,12 +61,8 @@ public SentryClient(final @NotNull SentryOptions options) { final RequestDetailsResolver requestDetailsResolver = new RequestDetailsResolver(options); transport = transportFactory.create(options, requestDetailsResolver.resolve()); loggerBatchProcessor = options.getLogs().getLoggerBatchProcessorFactory().create(options, this); - if (options.getMetrics().isEnabled()) { - metricsBatchProcessor = - options.getMetrics().getMetricsBatchProcessorFactory().create(options, this); - } else { - metricsBatchProcessor = NoOpMetricsBatchProcessor.getInstance(); - } + metricsBatchProcessor = + options.getMetrics().getMetricsBatchProcessorFactory().create(options, this); } private boolean shouldApplyScopeData( diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index aa136d176aa..5394c22e2d2 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -3748,10 +3748,6 @@ public void merge(final @NotNull ExternalOptions options) { } } - if (options.isEnableMetrics() != null) { - getMetrics().setEnabled(options.isEnableMetrics()); - } - if (options.getProfileSessionSampleRate() != null) { setProfileSessionSampleRate(options.getProfileSessionSampleRate()); } @@ -4023,9 +4019,6 @@ public interface BeforeSendLogCallback { public static final class Metrics { - /** Whether Sentry Metrics feature is enabled and metrics are sent to Sentry. */ - private boolean enable = true; - /** * This function is called with a metric key and tags and can return false to skip sending the * metric @@ -4035,24 +4028,6 @@ public static final class Metrics { private @NotNull IMetricsBatchProcessorFactory metricsBatchProcessorFactory = new DefaultMetricsBatchProcessorFactory(); - /** - * Whether Sentry Metrics feature is enabled and metrics are sent to Sentry. - * - * @return true if Sentry Metrics should be enabled - */ - public boolean isEnabled() { - return enable; - } - - /** - * Whether Sentry Metrics feature is enabled and metrics are sent to Sentry. - * - * @param enableMetrics true if Sentry Metrics should be enabled - */ - public void setEnabled(final boolean enableMetrics) { - this.enable = enableMetrics; - } - /** * Returns the BeforeSendMetric callback * diff --git a/sentry/src/main/java/io/sentry/metrics/MetricsApi.java b/sentry/src/main/java/io/sentry/metrics/MetricsApi.java index cebcad9735c..d70b4ab5c97 100644 --- a/sentry/src/main/java/io/sentry/metrics/MetricsApi.java +++ b/sentry/src/main/java/io/sentry/metrics/MetricsApi.java @@ -118,15 +118,6 @@ private void captureMetrics( return; } - if (!options.getMetrics().isEnabled()) { - options - .getLogger() - .log( - SentryLevel.WARNING, - "Sentry Metrics is disabled and this 'metrics' call is a no-op."); - return; - } - if (name == null) { return; } diff --git a/sentry/src/test/java/io/sentry/ScopesTest.kt b/sentry/src/test/java/io/sentry/ScopesTest.kt index 9e44c9ae847..980e89353c6 100644 --- a/sentry/src/test/java/io/sentry/ScopesTest.kt +++ b/sentry/src/test/java/io/sentry/ScopesTest.kt @@ -3290,11 +3290,15 @@ class ScopesTest { } @Test - fun `when metrics is not enabled, do nothing`() { - val (sut, mockClient) = getEnabledScopes { it.metrics.isEnabled = false } + fun `legacy external metrics configuration does not disable capture`() { + val (sut, mockClient) = + getEnabledScopes { options -> + options.merge(ExternalOptions().also { it.isEnableMetrics = false }) + } sut.metrics().count("metric name") - verify(mockClient, never()).captureMetric(any(), anyOrNull(), anyOrNull()) + + verify(mockClient).captureMetric(any(), anyOrNull(), anyOrNull()) } @Test @@ -4226,7 +4230,7 @@ class ScopesTest { @Test fun `metric event has spanId from active span`() { - val (sut, mockClient) = getEnabledScopes { it.metrics.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() val transaction = sut.startTransaction( @@ -4253,7 +4257,7 @@ class ScopesTest { @Test fun `metric event has spanId from propagation context when no active span`() { - val (sut, mockClient) = getEnabledScopes { it.metrics.isEnabled = true } + val (sut, mockClient) = getEnabledScopes() var propagationContext: PropagationContext? = null sut.configureScope { propagationContext = it.propagationContext } diff --git a/sentry/src/test/java/io/sentry/SentryClientTest.kt b/sentry/src/test/java/io/sentry/SentryClientTest.kt index 008597b428e..6376a216b98 100644 --- a/sentry/src/test/java/io/sentry/SentryClientTest.kt +++ b/sentry/src/test/java/io/sentry/SentryClientTest.kt @@ -153,6 +153,13 @@ class SentryClientTest { assertTrue(sut.isEnabled) } + @Test + fun `when client is created, metrics batch processor is created`() { + val sut = fixture.getSut() + + verify(fixture.metricsBatchProcessorFactory).create(fixture.sentryOptions, sut) + } + @Test fun `when dsn is an invalid string, client throws`() { fixture.sentryOptions.dsn = "invalid-dsn" diff --git a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt index 17546c62136..d56cbeb41ac 100644 --- a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt @@ -485,7 +485,6 @@ class SentryOptionsTest { assertTrue(options.isEnableSpotlight) assertEquals("http://local.sentry.io:1234", options.spotlightConnectionUrl) assertTrue(options.isGlobalHubMode!!) - assertFalse(options.metrics.isEnabled) assertEquals(0.8, options.profileSessionSampleRate) assertEquals("/profiling-traces${File.separator}${hash}", options.profilingTracesDirPath) assertEquals(ProfileLifecycle.TRACE, options.profileLifecycle) @@ -499,14 +498,6 @@ class SentryOptionsTest { assertTrue(options.isEnableUncaughtExceptionHandler) } - @Test - fun `merging options when enableMetrics is not set preserves the default value`() { - val externalOptions = ExternalOptions() - val options = SentryOptions() - options.merge(externalOptions) - assertTrue(options.metrics.isEnabled) - } - @Test fun `merging options does not warn when legacy logs configuration is absent`() { val logger = mock() @@ -804,11 +795,6 @@ class SentryOptionsTest { assertFalse(SentryOptions().isEnableQueueTracing) } - @Test - fun `when options are initialized, metrics is enabled by default`() { - assertTrue(SentryOptions().metrics.isEnabled) - } - @Test fun `when options are initialized, enableSpotlight is set to false by default`() { assertFalse(SentryOptions().isEnableSpotlight) From 01b74ec352d66720810032357d10cfa42f0d1ff1 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 13 Aug 2026 06:07:07 +0200 Subject: [PATCH 21/44] feat(android): Warn for legacy Metrics metadata Detect explicit io.sentry.metrics.enabled manifest values and emit tailored migration warnings without applying the obsolete option. Co-Authored-By: Claude --- .../android/core/ManifestMetadataReader.java | 15 ++++++ .../core/ManifestMetadataReaderTest.kt | 50 ++++++++++++++++++- 2 files changed, 63 insertions(+), 2 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 38ee0c1d347..0a4ed051bf7 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 @@ -723,6 +723,21 @@ static void applyMetadata( } } + if (metadata.containsKey(ENABLE_METRICS)) { + final boolean enableMetrics = readBool(metadata, logger, ENABLE_METRICS, false); + if (enableMetrics) { + logger.log( + SentryLevel.WARNING, + "The Android manifest option 'io.sentry.metrics.enabled' is no longer supported. " + + "Manual Sentry.metrics() calls no longer require it."); + } else { + logger.log( + SentryLevel.WARNING, + "The Android manifest option 'io.sentry.metrics.enabled' no longer disables " + + "manual Sentry.metrics() calls."); + } + } + 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 c2dfa7d544a..e5b2f1fcd0b 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 @@ -2054,8 +2054,22 @@ class ManifestMetadataReaderTest { } @Test - fun `legacy metrics metadata does not disable capture`() { - val bundle = bundleOf(ManifestMetadataReader.ENABLE_METRICS to false) + fun `applyMetadata does not warn when legacy metrics 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 metrics enabled metadata is true`() { + val bundle = + bundleOf( + ManifestMetadataReader.DEBUG to true, + ManifestMetadataReader.ENABLE_METRICS to true, + ) val context = fixture.getContext(metaData = bundle) val client = createSentryClientMock() @@ -2064,6 +2078,38 @@ class ManifestMetadataReaderTest { val scopes = createTestScopes(fixture.options).also { it.bindClient(client) } scopes.metrics().count("metric name") + verify(fixture.logger) + .log( + SentryLevel.WARNING, + "The Android manifest option 'io.sentry.metrics.enabled' is no longer supported. " + + "Manual Sentry.metrics() calls no longer require it.", + *emptyArray(), + ) + verify(client).captureMetric(any(), anyOrNull(), anyOrNull()) + } + + @Test + fun `applyMetadata warns when legacy metrics enabled metadata is false`() { + val bundle = + bundleOf( + ManifestMetadataReader.DEBUG to true, + ManifestMetadataReader.ENABLE_METRICS to false, + ) + val context = fixture.getContext(metaData = bundle) + val client = createSentryClientMock() + + ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) + fixture.options.dsn = "https://key@sentry.io/proj" + val scopes = createTestScopes(fixture.options).also { it.bindClient(client) } + scopes.metrics().count("metric name") + + verify(fixture.logger) + .log( + SentryLevel.WARNING, + "The Android manifest option 'io.sentry.metrics.enabled' no longer disables manual " + + "Sentry.metrics() calls.", + *emptyArray(), + ) verify(client).captureMetric(any(), anyOrNull(), anyOrNull()) } From 5f068d302b09f5b4d96c80346842205b67bffae2 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 13 Aug 2026 06:11:23 +0200 Subject: [PATCH 22/44] feat(core): Warn for legacy Metrics configuration Emit tailored migration warnings for explicit metrics.enabled values while preserving manual Metrics capture regardless of the obsolete setting. Co-Authored-By: Claude --- .../main/java/io/sentry/SentryOptions.java | 13 ++++ .../test/java/io/sentry/SentryOptionsTest.kt | 66 +++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 5394c22e2d2..33948ead53e 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -3748,6 +3748,19 @@ public void merge(final @NotNull ExternalOptions options) { } } + if (options.isEnableMetrics() != null) { + if (options.isEnableMetrics()) { + logger.log( + SentryLevel.WARNING, + "The 'metrics.enabled' option is no longer supported. Manual Sentry.metrics() calls no " + + "longer require it."); + } else { + logger.log( + SentryLevel.WARNING, + "The 'metrics.enabled' option no longer disables manual Sentry.metrics() calls."); + } + } + if (options.getProfileSessionSampleRate() != null) { setProfileSessionSampleRate(options.getProfileSessionSampleRate()); } diff --git a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt index d56cbeb41ac..398a1de11b0 100644 --- a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt @@ -3,6 +3,7 @@ package io.sentry import io.sentry.SentryOptions.RequestSize import io.sentry.logger.ILoggerBatchProcessorFactory import io.sentry.logger.LoggerApi +import io.sentry.metrics.MetricsApi import io.sentry.test.createSentryClientMock import io.sentry.test.createTestScopes import io.sentry.util.StringUtils @@ -564,6 +565,71 @@ class SentryOptionsTest { verify(client).captureLog(any(), anyOrNull()) } + @Test + fun `merging options does not warn when legacy metrics 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 metrics configuration is true`() { + val logger = mock() + val options = + SentryOptions().also { + it.isDebug = true + it.setLogger(logger) + } + + options.merge(ExternalOptions().apply { isEnableMetrics = true }) + + verify(logger) + .log( + SentryLevel.WARNING, + "The 'metrics.enabled' option is no longer supported. Manual Sentry.metrics() calls no " + + "longer require it.", + *emptyArray(), + ) + assertLegacyMetricsConfigurationDoesNotDisableCapture(options) + } + + @Test + fun `merging options warns when legacy metrics configuration is false`() { + val logger = mock() + val options = + SentryOptions().also { + it.isDebug = true + it.setLogger(logger) + } + + options.merge(ExternalOptions().apply { isEnableMetrics = false }) + + verify(logger) + .log( + SentryLevel.WARNING, + "The 'metrics.enabled' option no longer disables manual Sentry.metrics() calls.", + *emptyArray(), + ) + assertLegacyMetricsConfigurationDoesNotDisableCapture(options) + } + + private fun assertLegacyMetricsConfigurationDoesNotDisableCapture(options: SentryOptions) { + options.dsn = "https://key@sentry.io/proj" + val client = createSentryClientMock() + val scopes = createTestScopes(options).apply { bindClient(client) } + + MetricsApi(scopes).count("test metric") + + verify(client).captureMetric(any(), anyOrNull(), anyOrNull()) + } + @Test fun `merging options merges and overwrites existing tag values`() { val externalOptions = ExternalOptions() From 61f485a949ca7cceaf65301f981430f9b7a63040 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 13 Aug 2026 06:16:48 +0200 Subject: [PATCH 23/44] feat(spring): Warn for legacy Metrics property Inspect the Spring Environment before SDK initialization and emit tailored migration warnings for explicit sentry.metrics.enabled values without binding or applying the obsolete property. Co-Authored-By: Claude --- .../spring/boot4/SentryAutoConfiguration.java | 24 ++++++++++ .../boot4/SentryAutoConfigurationTest.kt | 46 +++++++++++++++++++ .../boot/jakarta/SentryAutoConfiguration.java | 24 ++++++++++ .../jakarta/SentryAutoConfigurationTest.kt | 46 +++++++++++++++++++ .../spring/boot/SentryAutoConfiguration.java | 24 ++++++++++ .../boot/SentryAutoConfigurationTest.kt | 46 +++++++++++++++++++ 6 files changed, 210 insertions(+) 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..d118250f341 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 @@ -187,6 +187,7 @@ 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)); warnForLegacyLogsConfiguration(environment, options); + warnForLegacyMetricsConfiguration(environment, options); Sentry.init(options); return ScopesAdapter.getInstance(); } @@ -216,6 +217,29 @@ private void warnForLegacyLogsConfiguration( } } + private void warnForLegacyMetricsConfiguration( + final @NotNull Environment environment, final @NotNull SentryOptions options) { + if (environment.containsProperty("sentry.metrics.enabled")) { + final boolean enableMetrics = + Boolean.TRUE.equals(environment.getProperty("sentry.metrics.enabled", Boolean.class)); + if (enableMetrics) { + options + .getLogger() + .log( + SentryLevel.WARNING, + "The 'sentry.metrics.enabled' property is no longer supported. Manual " + + "Sentry.metrics() calls no longer require it."); + } else { + options + .getLogger() + .log( + SentryLevel.WARNING, + "The 'sentry.metrics.enabled' property no longer disables manual " + + "Sentry.metrics() calls."); + } + } + } + @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 c3a7556e3ec..5aabbdef929 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 @@ -247,6 +247,52 @@ class SentryAutoConfigurationTest { } } + @Test + fun `legacy metrics 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 metrics property true emits migration warning`() { + val logger = mock() + dsnEnabledRunner + .withPropertyValues("sentry.debug=true", "sentry.metrics.enabled=true") + .withBean(ILogger::class.java, { logger }) + .withUserConfiguration(LoggerConfiguration::class.java) + .run { + verify(logger) + .log( + SentryLevel.WARNING, + "The 'sentry.metrics.enabled' property is no longer supported. Manual " + + "Sentry.metrics() calls no longer require it.", + *emptyArray(), + ) + } + } + + @Test + fun `legacy metrics property false emits migration warning`() { + val logger = mock() + dsnEnabledRunner + .withPropertyValues("sentry.debug=true", "sentry.metrics.enabled=false") + .withBean(ILogger::class.java, { logger }) + .withUserConfiguration(LoggerConfiguration::class.java) + .run { + verify(logger) + .log( + SentryLevel.WARNING, + "The 'sentry.metrics.enabled' property no longer disables manual " + + "Sentry.metrics() calls.", + *emptyArray(), + ) + } + } + @Test fun `properties are applied to SentryOptions`() { contextRunner 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..86ef72a48fb 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 @@ -189,6 +189,7 @@ 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)); warnForLegacyLogsConfiguration(environment, options); + warnForLegacyMetricsConfiguration(environment, options); Sentry.init(options); return ScopesAdapter.getInstance(); } @@ -218,6 +219,29 @@ private void warnForLegacyLogsConfiguration( } } + private void warnForLegacyMetricsConfiguration( + final @NotNull Environment environment, final @NotNull SentryOptions options) { + if (environment.containsProperty("sentry.metrics.enabled")) { + final boolean enableMetrics = + Boolean.TRUE.equals(environment.getProperty("sentry.metrics.enabled", Boolean.class)); + if (enableMetrics) { + options + .getLogger() + .log( + SentryLevel.WARNING, + "The 'sentry.metrics.enabled' property is no longer supported. Manual " + + "Sentry.metrics() calls no longer require it."); + } else { + options + .getLogger() + .log( + SentryLevel.WARNING, + "The 'sentry.metrics.enabled' property no longer disables manual " + + "Sentry.metrics() calls."); + } + } + } + @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 1ce6c8071ea..df994459403 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 @@ -250,6 +250,52 @@ class SentryAutoConfigurationTest { } } + @Test + fun `legacy metrics 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 metrics property true emits migration warning`() { + val logger = mock() + dsnEnabledRunner + .withPropertyValues("sentry.debug=true", "sentry.metrics.enabled=true") + .withBean(ILogger::class.java, { logger }) + .withUserConfiguration(LoggerConfiguration::class.java) + .run { + verify(logger) + .log( + SentryLevel.WARNING, + "The 'sentry.metrics.enabled' property is no longer supported. Manual " + + "Sentry.metrics() calls no longer require it.", + *emptyArray(), + ) + } + } + + @Test + fun `legacy metrics property false emits migration warning`() { + val logger = mock() + dsnEnabledRunner + .withPropertyValues("sentry.debug=true", "sentry.metrics.enabled=false") + .withBean(ILogger::class.java, { logger }) + .withUserConfiguration(LoggerConfiguration::class.java) + .run { + verify(logger) + .log( + SentryLevel.WARNING, + "The 'sentry.metrics.enabled' property no longer disables manual " + + "Sentry.metrics() calls.", + *emptyArray(), + ) + } + } + @Test fun `properties are applied to SentryOptions`() { contextRunner 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..83d342de521 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 @@ -184,6 +184,7 @@ 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)); warnForLegacyLogsConfiguration(environment, options); + warnForLegacyMetricsConfiguration(environment, options); Sentry.init(options); return ScopesAdapter.getInstance(); } @@ -213,6 +214,29 @@ private void warnForLegacyLogsConfiguration( } } + private void warnForLegacyMetricsConfiguration( + final @NotNull Environment environment, final @NotNull SentryOptions options) { + if (environment.containsProperty("sentry.metrics.enabled")) { + final boolean enableMetrics = + Boolean.TRUE.equals(environment.getProperty("sentry.metrics.enabled", Boolean.class)); + if (enableMetrics) { + options + .getLogger() + .log( + SentryLevel.WARNING, + "The 'sentry.metrics.enabled' property is no longer supported. Manual " + + "Sentry.metrics() calls no longer require it."); + } else { + options + .getLogger() + .log( + SentryLevel.WARNING, + "The 'sentry.metrics.enabled' property no longer disables manual " + + "Sentry.metrics() calls."); + } + } + } + @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 b0ee03d6577..2315a616d28 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 @@ -248,6 +248,52 @@ class SentryAutoConfigurationTest { } } + @Test + fun `legacy metrics 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 metrics property true emits migration warning`() { + val logger = mock() + dsnEnabledRunner + .withPropertyValues("sentry.debug=true", "sentry.metrics.enabled=true") + .withBean(ILogger::class.java, { logger }) + .withUserConfiguration(LoggerConfiguration::class.java) + .run { + verify(logger) + .log( + SentryLevel.WARNING, + "The 'sentry.metrics.enabled' property is no longer supported. Manual " + + "Sentry.metrics() calls no longer require it.", + *emptyArray(), + ) + } + } + + @Test + fun `legacy metrics property false emits migration warning`() { + val logger = mock() + dsnEnabledRunner + .withPropertyValues("sentry.debug=true", "sentry.metrics.enabled=false") + .withBean(ILogger::class.java, { logger }) + .withUserConfiguration(LoggerConfiguration::class.java) + .run { + verify(logger) + .log( + SentryLevel.WARNING, + "The 'sentry.metrics.enabled' property no longer disables manual " + + "Sentry.metrics() calls.", + *emptyArray(), + ) + } + } + @Test fun `properties are applied to SentryOptions`() { contextRunner From 170abc921699aa49a1e4c6380edcdeae393c0d5c Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 13 Aug 2026 06:29:55 +0200 Subject: [PATCH 24/44] perf(core): Start Metrics batch worker on first use Avoid scheduling Metrics processor work for empty flush and close operations until the processor accepts its first item. Preserve existing batching, restart, and Android background behavior after first use. Co-Authored-By: Claude --- .../core/AndroidMetricsBatchProcessorTest.kt | 14 +++ sentry/api/sentry.api | 1 + .../sentry/metrics/MetricsBatchProcessor.java | 30 ++++-- .../metrics/MetricsBatchProcessorTest.kt | 91 +++++++++++++++++++ 4 files changed, 129 insertions(+), 7 deletions(-) diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidMetricsBatchProcessorTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidMetricsBatchProcessorTest.kt index 7d85502d149..9c35808d0c7 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidMetricsBatchProcessorTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidMetricsBatchProcessorTest.kt @@ -1,11 +1,14 @@ 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.SentryMetricsEvent 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 @@ -14,6 +17,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 @@ -54,6 +58,16 @@ class AndroidMetricsBatchProcessorTest { 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()).captureBatchedMetricsEvents(any()) + } + @Test fun `onBackground schedules flush`() { val sut = fixture.getSut(useImmediateExecutor = true) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 5041ecf13f8..d595cd603a0 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -5490,6 +5490,7 @@ public class io/sentry/metrics/MetricsBatchProcessor : io/sentry/metrics/IMetric public static final field MAX_QUEUE_SIZE I protected final field options Lio/sentry/SentryOptions; public fun (Lio/sentry/SentryOptions;Lio/sentry/ISentryClient;)V + public fun (Lio/sentry/SentryOptions;Lio/sentry/ISentryClient;Lio/sentry/ISentryExecutorService;)V public fun add (Lio/sentry/SentryMetricsEvent;)V public fun close (Z)V public fun flush (J)V diff --git a/sentry/src/main/java/io/sentry/metrics/MetricsBatchProcessor.java b/sentry/src/main/java/io/sentry/metrics/MetricsBatchProcessor.java index 3c744dbe3c5..e2e71d1daa3 100644 --- a/sentry/src/main/java/io/sentry/metrics/MetricsBatchProcessor.java +++ b/sentry/src/main/java/io/sentry/metrics/MetricsBatchProcessor.java @@ -19,8 +19,10 @@ import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; @Open public class MetricsBatchProcessor implements IMetricsBatchProcessor { @@ -34,16 +36,26 @@ public class MetricsBatchProcessor implements IMetricsBatchProcessor { 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(); public MetricsBatchProcessor( final @NotNull SentryOptions options, final @NotNull ISentryClient client) { + this(options, client, new SentryExecutorService(options)); + } + + @ApiStatus.Internal + @TestOnly + public MetricsBatchProcessor( + final @NotNull SentryOptions options, + final @NotNull ISentryClient client, + final @NotNull ISentryExecutorService executorService) { this.options = options; this.client = client; this.queue = new ConcurrentLinkedQueue<>(); - this.executorService = new SentryExecutorService(options); + this.executorService = executorService; } @Override @@ -65,6 +77,7 @@ public void add(final @NotNull SentryMetricsEvent metricsEvent) { } pendingCount.increment(); queue.offer(metricsEvent); + hasAcceptedItem = true; maybeSchedule(false); } @@ -72,14 +85,14 @@ public void add(final @NotNull SentryMetricsEvent metricsEvent) { @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(); } } @@ -106,6 +119,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/metrics/MetricsBatchProcessorTest.kt b/sentry/src/test/java/io/sentry/metrics/MetricsBatchProcessorTest.kt index d8320d9b1a6..99b8deba2a0 100644 --- a/sentry/src/test/java/io/sentry/metrics/MetricsBatchProcessorTest.kt +++ b/sentry/src/test/java/io/sentry/metrics/MetricsBatchProcessorTest.kt @@ -3,6 +3,7 @@ package io.sentry.metrics import com.google.common.truth.Truth.assertThat import io.sentry.DataCategory import io.sentry.ISentryClient +import io.sentry.ISentryExecutorService import io.sentry.SentryMetricsEvent import io.sentry.SentryMetricsEvents import io.sentry.SentryNanotimeDate @@ -12,19 +13,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 MetricsBatchProcessorTest { + @Test + fun `constructor does not submit processor work`() { + val mockExecutor = mock() + + MetricsBatchProcessor(SentryOptions(), mock(), mockExecutor) + + verifyNoInteractions(mockExecutor) + } + + @Test + fun `empty flush does not submit processor work`() { + val mockExecutor = mock() + val processor = MetricsBatchProcessor(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 = MetricsBatchProcessor(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 = MetricsBatchProcessor(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 = MetricsBatchProcessor(SentryOptions(), mock(), mockExecutor) + processor.close(false) + + processor.add(metricsEvent("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 = MetricsBatchProcessor(SentryOptions(), mock(), mockExecutor) + val pendingCount = processor.getProperty("pendingCount") + repeat(MetricsBatchProcessor.MAX_QUEUE_SIZE) { pendingCount.increment() } + + processor.add(metricsEvent("rejected")) + processor.flush(0) + + verifyNoInteractions(mockExecutor) + } + + @Test + fun `flush and restart close submit processor work after first accepted item`() { + val mockExecutor = mock() + val processor = MetricsBatchProcessor(SentryOptions(), mock(), mockExecutor) + processor.add(metricsEvent("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 MetricsBatchProcessorTest { .inOrder() } + private fun metricsEvent(name: String) = + SentryMetricsEvent(SentryId(), SentryNanotimeDate(), name, "gauge", 1.0) + @Test fun `drops metrics events after reaching MAX_QUEUE_SIZE limit`() { // given From 6f13618a88a07669b36b94f7cc5088ce45b1b22a Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 13 Aug 2026 11:41:39 +0200 Subject: [PATCH 25/44] fix(jul): Preserve null-message events and breadcrumbs Skip only Sentry Logs conversion when a valid JUL record has no message, allowing its event and breadcrumb capture paths to continue. Co-Authored-By: Claude --- .../java/io/sentry/jul/SentryHandler.java | 11 +++++--- .../kotlin/io/sentry/jul/SentryHandlerTest.kt | 25 +++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java b/sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java index c606bdf88b6..bb1cce04445 100644 --- a/sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java +++ b/sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java @@ -150,10 +150,15 @@ protected void captureLog(@NotNull LogRecord loggingEvent) { final @Nullable Object[] arguments = loggingEvent.getParameters(); final @NotNull SentryAttributes attributes = SentryAttributes.of(); - @NotNull String message = loggingEvent.getMessage(); + final @Nullable String messageTemplate = loggingEvent.getMessage(); + if (messageTemplate == null) { + return; + } + + @NotNull String message = messageTemplate; if (loggingEvent.getResourceBundle() != null - && loggingEvent.getResourceBundle().containsKey(loggingEvent.getMessage())) { - message = loggingEvent.getResourceBundle().getString(loggingEvent.getMessage()); + && loggingEvent.getResourceBundle().containsKey(messageTemplate)) { + message = loggingEvent.getResourceBundle().getString(messageTemplate); } final @NotNull String formattedMessage = maybeFormatted(arguments, message); diff --git a/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt b/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt index 2cd10a81cd4..29dbaeb3688 100644 --- a/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt +++ b/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt @@ -485,6 +485,31 @@ class SentryHandlerTest { verify(fixture.transport, never()).send(checkLogs {}) } + @Test + fun `captures null message as event and breadcrumb when logs are enabled`() { + fixture = + Fixture( + minimumBreadcrumbLevel = Level.INFO, + minimumEventLevel = Level.SEVERE, + enableLogs = true, + ) + + fixture.logger.info(null as String?) + fixture.logger.severe(null as String?) + Sentry.flush(10) + + verify(fixture.transport) + .send( + checkEvent { event -> + assertNull(event.message?.message) + assertEquals(1, event.breadcrumbs?.size) + assertNull(event.breadcrumbs?.single()?.message) + }, + anyOrNull(), + ) + verify(fixture.transport, never()).send(checkLogs {}) + } + @Test fun `converts finest log level to Sentry log level`() { fixture = Fixture(minimumLevel = Level.FINEST) From b9d76ce26be54745689419efde932893a18c6a29 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 13 Aug 2026 12:07:31 +0200 Subject: [PATCH 26/44] fix(samples): Remove obsolete Logback Logs option Keep the integration-local enableLogs setting while removing the aggregate Logs configuration that no longer exists. Co-Authored-By: Claude --- .../sentry-samples-logback/src/main/resources/logback.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/sentry-samples/sentry-samples-logback/src/main/resources/logback.xml b/sentry-samples/sentry-samples-logback/src/main/resources/logback.xml index 7b70bcda1c0..196486cf807 100644 --- a/sentry-samples/sentry-samples-logback/src/main/resources/logback.xml +++ b/sentry-samples/sentry-samples-logback/src/main/resources/logback.xml @@ -13,9 +13,6 @@ https://502f25099c204a2fbf4cb16edc5975d1@o447951.ingest.sentry.io/5428563 userId requestId - - true - true From b87e3315f048e14126345fcbcf9610661f31e935 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 13 Aug 2026 12:20:36 +0200 Subject: [PATCH 27/44] fix(core): Keep Metrics test constructor internal Use package visibility for executor injection so the test-only constructor does not expand the published API. Co-Authored-By: Claude --- sentry/api/sentry.api | 1 - .../main/java/io/sentry/metrics/MetricsBatchProcessor.java | 6 +----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index d595cd603a0..5041ecf13f8 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -5490,7 +5490,6 @@ public class io/sentry/metrics/MetricsBatchProcessor : io/sentry/metrics/IMetric public static final field MAX_QUEUE_SIZE I protected final field options Lio/sentry/SentryOptions; public fun (Lio/sentry/SentryOptions;Lio/sentry/ISentryClient;)V - public fun (Lio/sentry/SentryOptions;Lio/sentry/ISentryClient;Lio/sentry/ISentryExecutorService;)V public fun add (Lio/sentry/SentryMetricsEvent;)V public fun close (Z)V public fun flush (J)V diff --git a/sentry/src/main/java/io/sentry/metrics/MetricsBatchProcessor.java b/sentry/src/main/java/io/sentry/metrics/MetricsBatchProcessor.java index e2e71d1daa3..8df9dc653f4 100644 --- a/sentry/src/main/java/io/sentry/metrics/MetricsBatchProcessor.java +++ b/sentry/src/main/java/io/sentry/metrics/MetricsBatchProcessor.java @@ -19,10 +19,8 @@ import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; @Open public class MetricsBatchProcessor implements IMetricsBatchProcessor { @@ -46,9 +44,7 @@ public MetricsBatchProcessor( this(options, client, new SentryExecutorService(options)); } - @ApiStatus.Internal - @TestOnly - public MetricsBatchProcessor( + MetricsBatchProcessor( final @NotNull SentryOptions options, final @NotNull ISentryClient client, final @NotNull ISentryExecutorService executorService) { From 3b52f013a0c9613778ed38160c8f258fdf62f947 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 13 Aug 2026 12:25:02 +0200 Subject: [PATCH 28/44] docs: Update options guidance for Logs and Metrics Document always-available deliberate APIs, source-local automatic Logs opt-ins, and warning-only handling of obsolete aggregate settings. Co-Authored-By: Claude --- .cursor/rules/options.mdc | 79 +++++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/.cursor/rules/options.mdc b/.cursor/rules/options.mdc index 2d239da7813..24e1dadc586 100644 --- a/.cursor/rules/options.mdc +++ b/.cursor/rules/options.mdc @@ -4,7 +4,10 @@ description: Adding and modifying SDK options --- # Adding Options to the SDK -New features must be **opt-in by default**. Options control whether a feature is enabled and how it behaves. +New automatic capture features must be **opt-in by default**. Deliberate API calls such as +`Sentry.logger()` and `Sentry.metrics()` capture whenever the SDK is enabled and do not have +aggregate signal enable flags. Options control signal behavior and whether individual automatic +sources are enabled. ## Namespaced Options @@ -12,12 +15,12 @@ Newer features use namespaced option classes nested inside `SentryOptions`, e.g. - `SentryOptions.getLogs()` → `SentryOptions.Logs` - `SentryOptions.getMetrics()` → `SentryOptions.Metrics` -Each namespaced options class is a `public static final class` inside `SentryOptions` with its own fields, getters/setters, and callbacks (e.g. `BeforeSendLogCallback`, `BeforeSendMetricCallback`). +Each namespaced options class is a `public static final class` inside `SentryOptions` with its own fields, getters/setters, factories, and callbacks (e.g. `BeforeSendLogCallback`, `BeforeSendMetricCallback`). -A typical namespaced options class contains: -- `enabled` boolean (default `false` for opt-in) -- `sampleRate` double (if the feature supports sampling) -- `beforeSend` callback interface (nested inside the options class) +Do not assume that a namespaced signal needs an aggregate `enabled` field. In particular, Logs and +Metrics are available through deliberate API calls whenever the SDK is enabled. Their namespaced +options contain behavior such as sampling, `beforeSend`, limits, and processor factories. Automatic +capture integrations use source-local enable options that default to `false`. To add a new namespaced options class: 1. Create the `public static final class` inside `SentryOptions` with fields, getters/setters, and any callback interfaces @@ -47,21 +50,19 @@ The core options class. Add the field (or nested class) with getter/setter here. Allows setting options via `sentry.properties` file or system properties. Fields use nullable wrapper types (`@Nullable Boolean`, `@Nullable Double`) since unset means "don't override the default." **File:** `sentry/src/main/java/io/sentry/ExternalOptions.java` -- Add `@Nullable` fields with getter/setter for each externally configurable option (e.g. `enableMetrics`, `logsSampleRate`) -- Wire them in the static `from(PropertiesProvider)` method: - - Boolean: `propertiesProvider.getBooleanProperty("metrics.enabled")` - - Double: `propertiesProvider.getDoubleProperty("logs.sample-rate")` +- Add `@Nullable` fields with getter/setter for each externally configurable option (e.g. `logsSampleRate`) +- Wire them in the static `from(PropertiesProvider)` method, for example: + `propertiesProvider.getDoubleProperty("logs.sample-rate")` **File:** `sentry/src/main/java/io/sentry/SentryOptions.java` — `merge()` method - Add null-check blocks to apply each external option onto the namespaced options class: ```java - if (options.isEnableMetrics() != null) { - getMetrics().setEnabled(options.isEnableMetrics()); - } if (options.getLogsSampleRate() != null) { getLogs().setSampleRate(options.getLogsSampleRate()); } ``` +- Do not add or restore `logs.enabled` or `metrics.enabled`. Legacy values are retained only long + enough to emit presence-aware migration warnings and must not be applied. **Tests:** - `sentry/src/test/java/io/sentry/ExternalOptionsTest.kt` — test true/false/null for booleans, valid values and null for doubles @@ -72,9 +73,12 @@ Allows setting options via `sentry.properties` file or system properties. Fields Allows setting options via `AndroidManifest.xml` `` tags. **File:** `sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java` -- Add a `static final String` constant for the key (e.g. `"io.sentry.metrics.enabled"`) +- Add a `static final String` constant for the key - Read it in `applyMetadata()` using `readBool(metadata, logger, CONSTANT, defaultValue)` -- Apply to the namespaced options, e.g. `options.getMetrics().setEnabled(...)` +- Apply automatic-source options directly, for example + `options.setEnableLogcatLogs(...)` for `io.sentry.logcat.logs.enabled` +- Do not add or restore `io.sentry.logs.enabled` or `io.sentry.metrics.enabled`; those aggregate + keys are obsolete and are read only to emit migration warnings. **Tests:** `sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt` - Test default value preserved when not in manifest @@ -83,21 +87,37 @@ Allows setting options via `AndroidManifest.xml` `` tags. ### 4. Spring Boot Properties (Spring Boot only) -`SentryProperties` extends `SentryOptions`, so namespaced options (nested classes) are automatically available as Spring Boot properties without extra code. For example, `SentryOptions.Logs` is automatically mapped to `sentry.logs.enabled` in `application.properties`. +`SentryProperties` extends `SentryOptions`, so bindable namespaced behavior options are available +through the `SentryOptions` class hierarchy. Spring-owned integration controls belong to a Spring +namespace instead. For example, `SentryProperties.Logging.enableLogs` binds to +`sentry.logging.enable-logs` and controls Logs forwarding from the auto-configured Logback +appender. `sentry.logging.enabled` separately controls whether that appender is installed. -No additional code is needed for namespaced options — Spring Boot auto-configuration handles this via property binding on the `SentryOptions` class hierarchy. +Do not add or restore `sentry.logs.enabled` or `sentry.metrics.enabled`. Spring detects those legacy +properties through `Environment` only to emit migration warnings; it does not bind or apply them. **Tests:** `sentry-spring-boot*/src/test/kotlin/.../SentryAutoConfigurationTest.kt` -- Add the property (e.g. `"sentry.logs.enabled=true"`) to the existing `resolves all properties` test +- Add the new property to the existing binding test - Assert the value is set on the resolved `SentryProperties` bean +- Test default, explicit `true`, explicit `false`, and propagation into the owning integration - There are three Spring Boot modules with separate test files: `sentry-spring-boot`, `sentry-spring-boot-jakarta`, `sentry-spring-boot-4` ### 5. Reading Options at Runtime -Features check their options at usage time. For namespaced features the check typically happens in the feature's API class (e.g. `LoggerApi`, `MetricsApi`): -- Check `options.getLogs().isEnabled()` early and return if disabled -- Apply sampling via `options.getLogs().getSampleRate()` if applicable -- Apply `beforeSend` callback in `SentryClient` before sending +Deliberate APIs such as `LoggerApi` and `MetricsApi` do not check aggregate signal enable flags. +They capture whenever their scopes are enabled, then apply signal behavior such as sampling and +`beforeSend`. + +Automatic integrations must check their source-local opt-in without affecting their existing event +or breadcrumb paths. Current Logs controls are: +- Logback: appender `enableLogs` +- Log4j2: appender `enableLogs` +- JUL: handler `enableLogs` +- Spring Boot Logback: `sentry.logging.enable-logs` +- Timber: `enableTimberLogs` / `io.sentry.timber.logs.enabled` +- Logcat: `enableLogcatLogs` / `io.sentry.logcat.logs.enabled` + +All source-local options default to `false` and gate only Sentry Logs forwarding. When a feature has its own capture path (e.g. `captureLog`), the relevant classes are: - `ISentryClient` — add the capture method signature @@ -106,10 +126,11 @@ When a feature has its own capture path (e.g. `captureLog`), the relevant classe ## Checklist for Adding a New Namespaced Option -1. `SentryOptions.java` — nested options class + getter/setter on `SentryOptions` -2. `ExternalOptions.java` — `@Nullable` fields + wiring in `from()` -3. `SentryOptions.java` `merge()` — apply external options to namespaced class -4. `ManifestMetadataReader.java` — Android manifest support (if Android-relevant) -5. `SentryAutoConfigurationTest.kt` — Spring Boot property binding tests (all three Spring Boot modules) -6. Tests for all of the above (`SentryOptionsTest`, `ExternalOptionsTest`, `ManifestMetadataReaderTest`) -7. Run `./gradlew apiDump` — the nested class and its methods appear in `sentry.api` +1. Decide whether the option controls deliberate API behavior or an automatic capture source +2. `SentryOptions.java` — nested behavior option + getter/setter where core ownership is appropriate +3. `ExternalOptions.java` and `SentryOptions.merge()` — add external support if applicable +4. `ManifestMetadataReader.java` — add Android support if applicable +5. Spring properties — use the owning integration namespace and test all three Spring Boot modules +6. Test defaults and every supported configuration layer +7. Verify automatic-source opt-ins default to `false` and do not gate events or breadcrumbs +8. Run `./gradlew spotlessApply apiDump` From 6e791e51c008ca37f52f65991ed60584ea5f3a25 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 13 Aug 2026 13:42:15 +0200 Subject: [PATCH 29/44] fix(android): Read Timber Logs option after configuration Resolve the auto-installed Timber integration's Logs opt-in lazily when the integration registers. This honors programmatic configuration and manifest overrides applied after default integrations are constructed. Co-Authored-By: Claude --- sentry-android-core/build.gradle.kts | 2 +- .../core/AndroidOptionsInitializer.java | 2 +- .../sentry/android/core/SentryAndroidTest.kt | 42 +++++++++++++++++++ .../api/sentry-android-timber.api | 1 + .../android/timber/SentryTimberIntegration.kt | 24 ++++++++--- .../timber/SentryTimberIntegrationTest.kt | 13 ++++++ 6 files changed, 77 insertions(+), 7 deletions(-) diff --git a/sentry-android-core/build.gradle.kts b/sentry-android-core/build.gradle.kts index 0e3708a89bf..957244b8523 100644 --- a/sentry-android-core/build.gradle.kts +++ b/sentry-android-core/build.gradle.kts @@ -122,6 +122,7 @@ dependencies { testImplementation(projects.sentrySpotlight) testImplementation(projects.sentryAndroidFragment) testImplementation(projects.sentryAndroidTimber) + testImplementation(libs.timber) testImplementation(projects.sentryAndroidReplay) testImplementation(projects.sentryCompose) testImplementation(projects.sentryAndroidNdk) @@ -132,5 +133,4 @@ dependencies { testImplementation(libs.androidx.compose.foundation.layout) testImplementation(libs.androidx.compose.material3) testRuntimeOnly(libs.androidx.fragment.ktx) - testRuntimeOnly(libs.timber) } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java index d31d925810f..cd47f96d87b 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java @@ -473,7 +473,7 @@ static void installDefaultIntegrations( } if (isTimberAvailable) { - options.addIntegration(new SentryTimberIntegration(options.isEnableTimberLogs())); + options.addIntegration(new SentryTimberIntegration(options::isEnableTimberLogs)); } options.addIntegration(new AppComponentsBreadcrumbsIntegration(context)); options.addIntegration(new SystemEventsBreadcrumbsIntegration(context)); diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt index 2bd26051c07..cacdaf08348 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt @@ -23,6 +23,7 @@ import io.sentry.SentryEnvelope import io.sentry.SentryLevel import io.sentry.SentryLevel.DEBUG import io.sentry.SentryLevel.FATAL +import io.sentry.SentryLogEvent import io.sentry.SentryOptions import io.sentry.SentryOptions.BeforeSendCallback import io.sentry.Session @@ -84,6 +85,7 @@ import org.robolectric.annotation.Config import org.robolectric.shadow.api.Shadow import org.robolectric.shadows.ShadowActivityManager import org.robolectric.shadows.ShadowActivityManager.ApplicationExitInfoBuilder +import timber.log.Timber @RunWith(AndroidJUnit4::class) @Config(sdk = [Build.VERSION_CODES.N], shadows = [SentryShadowProcess::class]) @@ -238,6 +240,46 @@ class SentryAndroidTest { assertNotEquals(0, AppStartMetrics.getInstance().appStartTimeSpan.durationMs) } + @Test + fun `auto-installed Timber integration uses Logs option set in configuration callback`() { + val logs = mutableListOf() + fixture.initSut { options -> + options.isEnableTimberLogs = true + options.logs.beforeSend = + SentryOptions.Logs.BeforeSendLogCallback { log -> + logs.add(log) + log + } + } + + Timber.i("message") + + assertEquals(1, logs.size) + } + + @Test + fun `auto-installed Timber integration uses configuration callback override of manifest option`() { + val metadata = + Bundle().apply { + putString(ManifestMetadataReader.DSN, "https://key@sentry.io/123") + putBoolean(ManifestMetadataReader.ENABLE_TIMBER_LOGS, true) + } + val mockContext = ContextUtilsTestHelper.mockMetaData(metaData = metadata) + val logs = mutableListOf() + + initForTest(mockContext) { options -> + options.isEnableTimberLogs = false + options.logs.beforeSend = + SentryOptions.Logs.BeforeSendLogCallback { log -> + logs.add(log) + log + } + } + Timber.i("message") + + assertTrue(logs.isEmpty()) + } + @Test fun `deduplicates fragment, timber and system events integrations`() { var refOptions: SentryAndroidOptions? = null diff --git a/sentry-android-timber/api/sentry-android-timber.api b/sentry-android-timber/api/sentry-android-timber.api index ad5c909836e..275460a0346 100644 --- a/sentry-android-timber/api/sentry-android-timber.api +++ b/sentry-android-timber/api/sentry-android-timber.api @@ -12,6 +12,7 @@ public final class io/sentry/android/timber/SentryTimberIntegration : io/sentry/ public fun (Lio/sentry/SentryLevel;Lio/sentry/SentryLevel;Lio/sentry/SentryLogLevel;)V public synthetic fun (Lio/sentry/SentryLevel;Lio/sentry/SentryLevel;Lio/sentry/SentryLogLevel;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Lio/sentry/SentryLevel;Lio/sentry/SentryLevel;Lio/sentry/SentryLogLevel;Z)V + public fun (Lio/sentry/util/LazyEvaluator$Evaluator;)V public fun (Z)V public fun close ()V public final fun getEnableLogs ()Z diff --git a/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberIntegration.kt b/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberIntegration.kt index aa71fa74710..ba8956696d0 100644 --- a/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberIntegration.kt +++ b/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberIntegration.kt @@ -9,6 +9,7 @@ import io.sentry.SentryLogLevel import io.sentry.SentryOptions import io.sentry.android.timber.BuildConfig.VERSION_NAME import io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion +import io.sentry.util.LazyEvaluator.Evaluator import java.io.Closeable import timber.log.Timber @@ -18,11 +19,13 @@ public class SentryTimberIntegration( public val minBreadcrumbLevel: SentryLevel = SentryLevel.INFO, public val minLogsLevel: SentryLogLevel = SentryLogLevel.INFO, ) : Integration, Closeable { - public var enableLogs: Boolean = false - private set + public val enableLogs: Boolean + get() = enableLogsProvider.evaluate() + + private var enableLogsProvider: Evaluator = Evaluator { false } public constructor(enableLogs: Boolean) : this() { - this.enableLogs = enableLogs + enableLogsProvider = Evaluator { enableLogs } } public constructor( @@ -31,7 +34,11 @@ public class SentryTimberIntegration( minLogsLevel: SentryLogLevel, enableLogs: Boolean, ) : this(minEventLevel, minBreadcrumbLevel, minLogsLevel) { - this.enableLogs = enableLogs + enableLogsProvider = Evaluator { enableLogs } + } + + public constructor(enableLogsProvider: Evaluator) : this() { + this.enableLogsProvider = enableLogsProvider } private lateinit var tree: SentryTimberTree @@ -47,7 +54,14 @@ public class SentryTimberIntegration( override fun register(scopes: IScopes, options: SentryOptions) { logger = options.logger - tree = SentryTimberTree(scopes, minEventLevel, minBreadcrumbLevel, minLogsLevel, enableLogs) + tree = + SentryTimberTree( + scopes, + minEventLevel, + minBreadcrumbLevel, + minLogsLevel, + enableLogsProvider.evaluate(), + ) Timber.plant(tree) logger.log(SentryLevel.DEBUG, "SentryTimberIntegration installed.") diff --git a/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberIntegrationTest.kt b/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberIntegrationTest.kt index 5aae1be93fe..6597d528f87 100644 --- a/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberIntegrationTest.kt +++ b/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberIntegrationTest.kt @@ -12,6 +12,7 @@ import io.sentry.logger.ILoggerApi import io.sentry.logger.SentryLogParameters import io.sentry.protocol.SdkVersion import io.sentry.transport.ITransport +import io.sentry.util.LazyEvaluator.Evaluator import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -108,6 +109,18 @@ class SentryTimberIntegrationTest { verify(fixture.logs).log(any(), any(), any()) } + @Test + fun `Integration evaluates Logs provider when registered`() { + var enableLogs = false + val sut = SentryTimberIntegration(Evaluator { enableLogs }) + enableLogs = true + + sut.register(fixture.scopes, fixture.options) + Timber.i("message") + + verify(fixture.logs).log(any(), any(), any()) + } + @Test fun `Integrations removes a tree from Timber on close integration`() { val sut = fixture.getSut() From 8c2f35fda514494682780363b13c287930b49852 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 13 Aug 2026 14:15:49 +0200 Subject: [PATCH 30/44] 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 31/44] 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 8cf1b6ab245c75f69a4532b437f2bddadbbfb517 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Tue, 29 Sep 2026 16:09:59 +0200 Subject: [PATCH 32/44] fix(android): Use metadata helper for legacy Metrics option Keep legacy Metrics 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 10f4cd80a7b..0247301cf0a 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 @@ -771,7 +771,7 @@ static void applyMetadata( } } - if (metadata.containsKey(ENABLE_METRICS)) { + if (containsKey(metadata, ENABLE_METRICS)) { final boolean enableMetrics = readBool(metadata, logger, ENABLE_METRICS, false); if (enableMetrics) { logger.log( From 0b37db624e7ed475b4165d66835fbba91ab43fe5 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 30 Sep 2026 10:38:30 +0200 Subject: [PATCH 33/44] ref(core): Remove legacy Logs warning inputs Remove obsolete external and Android manifest parsing retained only for migration warnings that will not ship in v9. Co-Authored-By: Claude --- .../sentry/android/core/ManifestMetadataReader.java | 2 -- sentry/api/sentry.api | 2 -- sentry/src/main/java/io/sentry/ExternalOptions.java | 11 ----------- sentry/src/test/java/io/sentry/ExternalOptionsTest.kt | 5 ----- sentry/src/test/java/io/sentry/SentryOptionsTest.kt | 1 - 5 files changed, 21 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 8c5eb40aaf7..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 @@ -192,8 +192,6 @@ final class ManifestMetadataReader { static final String IN_APP_EXCLUDES = "io.sentry.in-app-excludes"; - static final String ENABLE_LOGS = "io.sentry.logs.enabled"; - static final String ENABLE_TIMBER_LOGS = "io.sentry.timber.logs.enabled"; static final String ENABLE_LOGCAT_LOGS = "io.sentry.logcat.logs.enabled"; diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 2d103fb27aa..36fd271257f 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -590,7 +590,6 @@ public final class io/sentry/ExternalOptions { public fun isEnableBackpressureHandling ()Ljava/lang/Boolean; public fun isEnableCacheTracing ()Ljava/lang/Boolean; public fun isEnableDatabaseTransactionTracing ()Ljava/lang/Boolean; - public fun isEnableLogs ()Ljava/lang/Boolean; public fun isEnableMetrics ()Ljava/lang/Boolean; public fun isEnablePrettySerializationOutput ()Ljava/lang/Boolean; public fun isEnableQueueTracing ()Ljava/lang/Boolean; @@ -611,7 +610,6 @@ public final class io/sentry/ExternalOptions { public fun setEnableCacheTracing (Ljava/lang/Boolean;)V public fun setEnableDatabaseTransactionTracing (Ljava/lang/Boolean;)V public fun setEnableDeduplication (Ljava/lang/Boolean;)V - public fun setEnableLogs (Ljava/lang/Boolean;)V public fun setEnableMetrics (Ljava/lang/Boolean;)V public fun setEnablePrettySerializationOutput (Ljava/lang/Boolean;)V public fun setEnableQueueTracing (Ljava/lang/Boolean;)V diff --git a/sentry/src/main/java/io/sentry/ExternalOptions.java b/sentry/src/main/java/io/sentry/ExternalOptions.java index 272cf1c13a9..1bfec0f05b4 100644 --- a/sentry/src/main/java/io/sentry/ExternalOptions.java +++ b/sentry/src/main/java/io/sentry/ExternalOptions.java @@ -46,7 +46,6 @@ public final class ExternalOptions { private @Nullable Boolean enabled; private @Nullable Boolean enablePrettySerializationOutput; private @Nullable Boolean enableSpotlight; - private @Nullable Boolean enableLogs; private @Nullable Boolean enableMetrics; private @Nullable String spotlightConnectionUrl; @@ -178,8 +177,6 @@ public final class ExternalOptions { options.setCaptureOpenTelemetryEvents( propertiesProvider.getBooleanProperty("capture-open-telemetry-events")); - options.setEnableLogs(propertiesProvider.getBooleanProperty("logs.enabled")); - options.setEnableMetrics(propertiesProvider.getBooleanProperty("metrics.enabled")); for (final String ignoredExceptionType : @@ -717,14 +714,6 @@ public void setCaptureOpenTelemetryEvents(final @Nullable Boolean captureOpenTel return captureOpenTelemetryEvents; } - public void setEnableLogs(final @Nullable Boolean enableLogs) { - this.enableLogs = enableLogs; - } - - public @Nullable Boolean isEnableLogs() { - return enableLogs; - } - public void setEnableMetrics(final @Nullable Boolean enableMetrics) { this.enableMetrics = enableMetrics; } diff --git a/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt b/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt index cdd184b181f..1d99f810747 100644 --- a/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt @@ -530,11 +530,6 @@ class ExternalOptionsTest { } } - @Test - fun `creates options with enableLogs set to true`() { - withPropertiesFile("logs.enabled=true") { options -> assertTrue(options.isEnableLogs == true) } - } - @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 cecb003f97d..ac16dc7e941 100644 --- a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt @@ -579,7 +579,6 @@ class SentryOptionsTest { externalOptions.isEnableSpotlight = true externalOptions.spotlightConnectionUrl = "http://local.sentry.io:1234" externalOptions.isGlobalHubMode = true - externalOptions.isEnableLogs = true externalOptions.isEnableMetrics = false externalOptions.profileSessionSampleRate = 0.8 externalOptions.profilingTracesDirPath = "/profiling-traces" From 3f987ba59bf08d7470c4ecc49765626c57e377a5 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 30 Sep 2026 10:39:02 +0200 Subject: [PATCH 34/44] 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 35/44] 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 36/44] 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 { From 81e86e19fcb26fef3c71283e1b29d3cd9e8a4ec2 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 30 Sep 2026 10:45:39 +0200 Subject: [PATCH 37/44] ref(core): Remove legacy Metrics warning inputs Remove obsolete external and Android manifest parsing retained only for migration warnings that will not ship in v9. Co-Authored-By: Claude --- .../android/core/ManifestMetadataReader.java | 2 -- .../core/ManifestMetadataReaderTest.kt | 17 ----------------- sentry/api/sentry.api | 2 -- .../main/java/io/sentry/ExternalOptions.java | 11 ----------- .../java/io/sentry/ExternalOptionsTest.kt | 19 ------------------- sentry/src/test/java/io/sentry/ScopesTest.kt | 12 ------------ .../test/java/io/sentry/SentryOptionsTest.kt | 1 - 7 files changed, 64 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 438ad40501b..217f4937bf1 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 @@ -196,8 +196,6 @@ final class ManifestMetadataReader { static final String ENABLE_LOGCAT_LOGS = "io.sentry.logcat.logs.enabled"; - static final String ENABLE_METRICS = "io.sentry.metrics.enabled"; - static final String ENABLE_AUTO_TRACE_ID_GENERATION = "io.sentry.traces.enable-auto-id-generation"; 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 6d02c3c3132..4e64dc616b5 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 @@ -13,8 +13,6 @@ import io.sentry.ProfileLifecycle import io.sentry.SentryLevel import io.sentry.SentryReplayOptions import io.sentry.TransactionOptions -import io.sentry.test.createSentryClientMock -import io.sentry.test.createTestScopes import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test @@ -25,7 +23,6 @@ import kotlin.test.assertNull import kotlin.test.assertTrue import org.junit.runner.RunWith 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 @@ -2227,20 +2224,6 @@ class ManifestMetadataReaderTest { assertThat(fixture.options.isEnableLogcatLogs).isFalse() } - @Test - fun `legacy metrics metadata does not disable capture`() { - val bundle = bundleOf(ManifestMetadataReader.ENABLE_METRICS to false) - val context = fixture.getContext(metaData = bundle) - val client = createSentryClientMock() - - ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) - fixture.options.dsn = "https://key@sentry.io/proj" - val scopes = createTestScopes(fixture.options).also { it.bindClient(client) } - scopes.metrics().count("metric name") - - verify(client).captureMetric(any(), anyOrNull(), anyOrNull()) - } - @Test fun `applyMetadata reads feedback name required and keep default value if not found`() { // Arrange diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 0a3e9d9350f..f5ac34d52d3 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -590,7 +590,6 @@ public final class io/sentry/ExternalOptions { public fun isEnableBackpressureHandling ()Ljava/lang/Boolean; public fun isEnableCacheTracing ()Ljava/lang/Boolean; public fun isEnableDatabaseTransactionTracing ()Ljava/lang/Boolean; - public fun isEnableMetrics ()Ljava/lang/Boolean; public fun isEnablePrettySerializationOutput ()Ljava/lang/Boolean; public fun isEnableQueueTracing ()Ljava/lang/Boolean; public fun isEnableSpotlight ()Ljava/lang/Boolean; @@ -610,7 +609,6 @@ public final class io/sentry/ExternalOptions { public fun setEnableCacheTracing (Ljava/lang/Boolean;)V public fun setEnableDatabaseTransactionTracing (Ljava/lang/Boolean;)V public fun setEnableDeduplication (Ljava/lang/Boolean;)V - public fun setEnableMetrics (Ljava/lang/Boolean;)V public fun setEnablePrettySerializationOutput (Ljava/lang/Boolean;)V public fun setEnableQueueTracing (Ljava/lang/Boolean;)V public fun setEnableSpotlight (Ljava/lang/Boolean;)V diff --git a/sentry/src/main/java/io/sentry/ExternalOptions.java b/sentry/src/main/java/io/sentry/ExternalOptions.java index 1bfec0f05b4..83497c2fb06 100644 --- a/sentry/src/main/java/io/sentry/ExternalOptions.java +++ b/sentry/src/main/java/io/sentry/ExternalOptions.java @@ -46,7 +46,6 @@ public final class ExternalOptions { private @Nullable Boolean enabled; private @Nullable Boolean enablePrettySerializationOutput; private @Nullable Boolean enableSpotlight; - private @Nullable Boolean enableMetrics; private @Nullable String spotlightConnectionUrl; private @Nullable List ignoredCheckIns; @@ -177,8 +176,6 @@ public final class ExternalOptions { options.setCaptureOpenTelemetryEvents( propertiesProvider.getBooleanProperty("capture-open-telemetry-events")); - options.setEnableMetrics(propertiesProvider.getBooleanProperty("metrics.enabled")); - for (final String ignoredExceptionType : propertiesProvider.getList("ignored-exceptions-for-type")) { try { @@ -714,14 +711,6 @@ public void setCaptureOpenTelemetryEvents(final @Nullable Boolean captureOpenTel return captureOpenTelemetryEvents; } - public void setEnableMetrics(final @Nullable Boolean enableMetrics) { - this.enableMetrics = enableMetrics; - } - - public @Nullable Boolean isEnableMetrics() { - return enableMetrics; - } - public @Nullable Double getProfileSessionSampleRate() { return profileSessionSampleRate; } diff --git a/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt b/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt index 1d99f810747..349058e4b3b 100644 --- a/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/ExternalOptionsTest.kt @@ -530,25 +530,6 @@ class ExternalOptionsTest { } } - @Test - fun `creates options with enableMetrics set to true`() { - withPropertiesFile("metrics.enabled=true") { options -> - assertTrue(options.isEnableMetrics == true) - } - } - - @Test - fun `creates options with enableMetrics set to false`() { - withPropertiesFile("metrics.enabled=false") { options -> - assertTrue(options.isEnableMetrics == false) - } - } - - @Test - fun `creates options with enableMetrics set to null when not set`() { - withPropertiesFile { assertNull(it.isEnableMetrics) } - } - @Test fun `creates options with profileSessionSampleRate set to 0_8`() { withPropertiesFile("profile-session-sample-rate=0.8") { options -> diff --git a/sentry/src/test/java/io/sentry/ScopesTest.kt b/sentry/src/test/java/io/sentry/ScopesTest.kt index 5833054e5f2..324a097a7fd 100644 --- a/sentry/src/test/java/io/sentry/ScopesTest.kt +++ b/sentry/src/test/java/io/sentry/ScopesTest.kt @@ -3300,18 +3300,6 @@ class ScopesTest { verify(mockClient, never()).captureMetric(any(), anyOrNull(), anyOrNull()) } - @Test - fun `legacy external metrics configuration does not disable capture`() { - val (sut, mockClient) = - getEnabledScopes { options -> - options.merge(ExternalOptions().also { it.isEnableMetrics = false }) - } - - sut.metrics().count("metric name") - - verify(mockClient).captureMetric(any(), anyOrNull(), anyOrNull()) - } - @Test fun `creating count metric works`() { val (sut, mockClient) = getEnabledScopes() diff --git a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt index 07fc28476a4..3d38f48d08d 100644 --- a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt @@ -579,7 +579,6 @@ class SentryOptionsTest { externalOptions.isEnableSpotlight = true externalOptions.spotlightConnectionUrl = "http://local.sentry.io:1234" externalOptions.isGlobalHubMode = true - externalOptions.isEnableMetrics = false externalOptions.profileSessionSampleRate = 0.8 externalOptions.profilingTracesDirPath = "/profiling-traces" externalOptions.profileLifecycle = ProfileLifecycle.TRACE From e6be758ff74bca7763e0ca86efdbe83e1e16c4c6 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 30 Sep 2026 10:48:09 +0200 Subject: [PATCH 38/44] docs: Remove legacy enable-flag warning guidance Document the removed aggregate options without retaining warning-only migration behavior. Co-Authored-By: Claude --- .cursor/rules/options.mdc | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.cursor/rules/options.mdc b/.cursor/rules/options.mdc index 24e1dadc586..ec88295fa4b 100644 --- a/.cursor/rules/options.mdc +++ b/.cursor/rules/options.mdc @@ -61,8 +61,7 @@ Allows setting options via `sentry.properties` file or system properties. Fields getLogs().setSampleRate(options.getLogsSampleRate()); } ``` -- Do not add or restore `logs.enabled` or `metrics.enabled`. Legacy values are retained only long - enough to emit presence-aware migration warnings and must not be applied. +- Do not add or restore `logs.enabled` or `metrics.enabled`; these aggregate options are obsolete. **Tests:** - `sentry/src/test/java/io/sentry/ExternalOptionsTest.kt` — test true/false/null for booleans, valid values and null for doubles @@ -78,7 +77,7 @@ Allows setting options via `AndroidManifest.xml` `` tags. - Apply automatic-source options directly, for example `options.setEnableLogcatLogs(...)` for `io.sentry.logcat.logs.enabled` - Do not add or restore `io.sentry.logs.enabled` or `io.sentry.metrics.enabled`; those aggregate - keys are obsolete and are read only to emit migration warnings. + keys are obsolete. **Tests:** `sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt` - Test default value preserved when not in manifest @@ -93,8 +92,8 @@ namespace instead. For example, `SentryProperties.Logging.enableLogs` binds to `sentry.logging.enable-logs` and controls Logs forwarding from the auto-configured Logback appender. `sentry.logging.enabled` separately controls whether that appender is installed. -Do not add or restore `sentry.logs.enabled` or `sentry.metrics.enabled`. Spring detects those legacy -properties through `Environment` only to emit migration warnings; it does not bind or apply them. +Do not add or restore `sentry.logs.enabled` or `sentry.metrics.enabled`; those aggregate properties +are obsolete. **Tests:** `sentry-spring-boot*/src/test/kotlin/.../SentryAutoConfigurationTest.kt` - Add the new property to the existing binding test From 2293fcd256d086e87e7433a1dc01540e8ad3beb0 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 30 Sep 2026 14:58:54 +0200 Subject: [PATCH 39/44] 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 { From 8a070f8ba9a10b529106550da4264f8f95e61c6b Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 30 Sep 2026 16:18:08 +0200 Subject: [PATCH 40/44] fix(jul): Handle null messages with resource bundles Skip resource-bundle lookup and message formatting when a JUL record has no message. Preserve event and breadcrumb capture instead of aborting with a null-pointer exception. Co-Authored-By: Claude --- .../java/io/sentry/jul/SentryHandler.java | 14 +++++--- .../kotlin/io/sentry/jul/SentryHandlerTest.kt | 36 +++++++++++++++++++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java b/sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java index bb1cce04445..685402e27bd 100644 --- a/sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java +++ b/sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java @@ -28,6 +28,7 @@ import java.util.Date; import java.util.List; import java.util.Map; +import java.util.ResourceBundle; import java.util.logging.ErrorManager; import java.util.logging.Filter; import java.util.logging.Handler; @@ -295,13 +296,16 @@ SentryEvent createEvent(final @NotNull LogRecord record) { final Message sentryMessage = new Message(); sentryMessage.setParams(toParams(record.getParameters())); - String message = record.getMessage(); - if (record.getResourceBundle() != null - && record.getResourceBundle().containsKey(record.getMessage())) { - message = record.getResourceBundle().getString(record.getMessage()); + final @Nullable String messageTemplate = record.getMessage(); + final @Nullable ResourceBundle resourceBundle = record.getResourceBundle(); + @Nullable String message = messageTemplate; + if (messageTemplate != null + && resourceBundle != null + && resourceBundle.containsKey(messageTemplate)) { + message = resourceBundle.getString(messageTemplate); } sentryMessage.setMessage(message); - if (record.getParameters() != null) { + if (message != null && record.getParameters() != null) { try { sentryMessage.setFormatted(formatMessage(message, record.getParameters())); } catch (RuntimeException e) { diff --git a/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt b/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt index 29dbaeb3688..028c6759727 100644 --- a/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt +++ b/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt @@ -16,7 +16,9 @@ import io.sentry.transport.ITransport import java.time.Instant import java.time.LocalDateTime import java.time.ZoneId +import java.util.ListResourceBundle import java.util.logging.Level +import java.util.logging.LogRecord import java.util.logging.Logger import kotlin.test.AfterTest import kotlin.test.BeforeTest @@ -510,6 +512,40 @@ class SentryHandlerTest { verify(fixture.transport, never()).send(checkLogs {}) } + @Test + fun `captures null message as event and breadcrumb when resource bundle is set`() { + fixture = + Fixture( + minimumBreadcrumbLevel = Level.INFO, + minimumEventLevel = Level.SEVERE, + enableLogs = true, + ) + val resourceBundle = + object : ListResourceBundle() { + override fun getContents(): Array> = + arrayOf(arrayOf("message", "localized message")) + } + + fixture.handler.publish( + LogRecord(Level.INFO, null).apply { this.resourceBundle = resourceBundle } + ) + fixture.handler.publish( + LogRecord(Level.SEVERE, null).apply { this.resourceBundle = resourceBundle } + ) + Sentry.flush(10) + + verify(fixture.transport) + .send( + checkEvent { event -> + assertNull(event.message?.message) + assertEquals(1, event.breadcrumbs?.size) + assertNull(event.breadcrumbs?.single()?.message) + }, + anyOrNull(), + ) + verify(fixture.transport, never()).send(checkLogs {}) + } + @Test fun `converts finest log level to Sentry log level`() { fixture = Fixture(minimumLevel = Level.FINEST) From f4afe79deb1418ecc82f7b166ea75d1ade9233da Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 1 Oct 2026 05:52:34 +0200 Subject: [PATCH 41/44] docs(options): Clarify conditional signal options State that options such as sampling should only be added when the signal supports them. Avoid implying that Logs and Metrics currently expose sampling controls. Co-Authored-By: Claude --- .cursor/rules/options.mdc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.cursor/rules/options.mdc b/.cursor/rules/options.mdc index ec88295fa4b..06d1c02c44e 100644 --- a/.cursor/rules/options.mdc +++ b/.cursor/rules/options.mdc @@ -19,8 +19,9 @@ Each namespaced options class is a `public static final class` inside `SentryOpt Do not assume that a namespaced signal needs an aggregate `enabled` field. In particular, Logs and Metrics are available through deliberate API calls whenever the SDK is enabled. Their namespaced -options contain behavior such as sampling, `beforeSend`, limits, and processor factories. Automatic -capture integrations use source-local enable options that default to `false`. +options contain signal-specific behavior such as `beforeSend`, limits, and processor factories. Add +options such as sampling only when the signal supports them. Automatic capture integrations use +source-local enable options that default to `false`. To add a new namespaced options class: 1. Create the `public static final class` inside `SentryOptions` with fields, getters/setters, and any callback interfaces From c32509e794fda9835ec8977297bb6b8b6165c818 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 1 Oct 2026 09:32:51 +0200 Subject: [PATCH 42/44] fix(timber): Keep lazy logs constructor internal Hide the lazy evaluator constructor from the supported Kotlin API while retaining Java access for Android integration wiring. Use a lambda instead of a method reference for compatibility with older Android Gradle plugins. Co-Authored-By: Claude --- .../java/io/sentry/android/core/AndroidOptionsInitializer.java | 2 +- sentry-android-timber/api/sentry-android-timber.api | 1 - .../java/io/sentry/android/timber/SentryTimberIntegration.kt | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java index 5a4bb53ca67..c6c29a323fd 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java @@ -479,7 +479,7 @@ static void installDefaultIntegrations( } if (isTimberAvailable) { - options.addIntegration(new SentryTimberIntegration(options::isEnableTimberLogs)); + options.addIntegration(new SentryTimberIntegration(() -> options.isEnableTimberLogs())); } options.addIntegration(new AppComponentsBreadcrumbsIntegration(context)); options.addIntegration(new SystemEventsBreadcrumbsIntegration(context)); diff --git a/sentry-android-timber/api/sentry-android-timber.api b/sentry-android-timber/api/sentry-android-timber.api index 275460a0346..ad5c909836e 100644 --- a/sentry-android-timber/api/sentry-android-timber.api +++ b/sentry-android-timber/api/sentry-android-timber.api @@ -12,7 +12,6 @@ public final class io/sentry/android/timber/SentryTimberIntegration : io/sentry/ public fun (Lio/sentry/SentryLevel;Lio/sentry/SentryLevel;Lio/sentry/SentryLogLevel;)V public synthetic fun (Lio/sentry/SentryLevel;Lio/sentry/SentryLevel;Lio/sentry/SentryLogLevel;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Lio/sentry/SentryLevel;Lio/sentry/SentryLevel;Lio/sentry/SentryLogLevel;Z)V - public fun (Lio/sentry/util/LazyEvaluator$Evaluator;)V public fun (Z)V public fun close ()V public final fun getEnableLogs ()Z diff --git a/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberIntegration.kt b/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberIntegration.kt index ba8956696d0..dabf3531fd1 100644 --- a/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberIntegration.kt +++ b/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberIntegration.kt @@ -37,7 +37,7 @@ public class SentryTimberIntegration( enableLogsProvider = Evaluator { enableLogs } } - public constructor(enableLogsProvider: Evaluator) : this() { + internal constructor(enableLogsProvider: Evaluator) : this() { this.enableLogsProvider = enableLogsProvider } From 67e11ccd9f6b47c4e661e801cc33833595f4549a Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Fri, 9 Oct 2026 11:35:03 +0200 Subject: [PATCH 43/44] ref(logging): Remove unused no-op Logs processor Remove the internal no-op batch processor now that SentryClient always creates the configured Logs processor and no SDK path selects the stub. Co-Authored-By: Claude --- sentry/api/sentry.api | 7 ---- .../logger/NoOpLoggerBatchProcessor.java | 32 ------------------- 2 files changed, 39 deletions(-) delete mode 100644 sentry/src/main/java/io/sentry/logger/NoOpLoggerBatchProcessor.java diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 36fd271257f..09b2c74e5a5 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -5564,13 +5564,6 @@ public final class io/sentry/logger/NoOpLoggerApi : io/sentry/logger/ILoggerApi public fun warn (Ljava/lang/String;[Ljava/lang/Object;)V } -public final class io/sentry/logger/NoOpLoggerBatchProcessor : io/sentry/logger/ILoggerBatchProcessor { - public fun add (Lio/sentry/SentryLogEvent;)V - public fun close (Z)V - public fun flush (J)V - public static fun getInstance ()Lio/sentry/logger/NoOpLoggerBatchProcessor; -} - public final class io/sentry/logger/SentryLogParameters { public fun ()V public static fun create (Lio/sentry/SentryAttributes;)Lio/sentry/logger/SentryLogParameters; diff --git a/sentry/src/main/java/io/sentry/logger/NoOpLoggerBatchProcessor.java b/sentry/src/main/java/io/sentry/logger/NoOpLoggerBatchProcessor.java deleted file mode 100644 index 68dc4ecf937..00000000000 --- a/sentry/src/main/java/io/sentry/logger/NoOpLoggerBatchProcessor.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.sentry.logger; - -import io.sentry.SentryLogEvent; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -@ApiStatus.Internal -public final class NoOpLoggerBatchProcessor implements ILoggerBatchProcessor { - - private static final NoOpLoggerBatchProcessor instance = new NoOpLoggerBatchProcessor(); - - private NoOpLoggerBatchProcessor() {} - - public static NoOpLoggerBatchProcessor getInstance() { - return instance; - } - - @Override - public void add(@NotNull SentryLogEvent event) { - // do nothing - } - - @Override - public void close(final boolean isRestarting) { - // do nothing - } - - @Override - public void flush(long timeoutMillis) { - // do nothing - } -} From a1e848a484dad6821358ac48da2a7e01235c74f6 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Fri, 9 Oct 2026 11:35:57 +0200 Subject: [PATCH 44/44] ref(metrics): Remove unused no-op batch processor Remove the internal no-op batch processor now that SentryClient always creates the configured Metrics processor and no SDK path selects the stub. Co-Authored-By: Claude --- sentry/api/sentry.api | 7 ---- .../metrics/NoOpMetricsBatchProcessor.java | 32 ------------------- 2 files changed, 39 deletions(-) delete mode 100644 sentry/src/main/java/io/sentry/metrics/NoOpMetricsBatchProcessor.java diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 6a6cb18d68c..9b988911417 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -5678,13 +5678,6 @@ public final class io/sentry/metrics/NoOpMetricsApi : io/sentry/metrics/IMetrics public static fun getInstance ()Lio/sentry/metrics/NoOpMetricsApi; } -public final class io/sentry/metrics/NoOpMetricsBatchProcessor : io/sentry/metrics/IMetricsBatchProcessor { - public fun add (Lio/sentry/SentryMetricsEvent;)V - public fun close (Z)V - public fun flush (J)V - public static fun getInstance ()Lio/sentry/metrics/NoOpMetricsBatchProcessor; -} - public final class io/sentry/metrics/SentryMetricsParameters { public fun ()V public static fun create (Lio/sentry/SentryAttributes;)Lio/sentry/metrics/SentryMetricsParameters; diff --git a/sentry/src/main/java/io/sentry/metrics/NoOpMetricsBatchProcessor.java b/sentry/src/main/java/io/sentry/metrics/NoOpMetricsBatchProcessor.java deleted file mode 100644 index 021bed5ee32..00000000000 --- a/sentry/src/main/java/io/sentry/metrics/NoOpMetricsBatchProcessor.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.sentry.metrics; - -import io.sentry.SentryMetricsEvent; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -@ApiStatus.Internal -public final class NoOpMetricsBatchProcessor implements IMetricsBatchProcessor { - - private static final NoOpMetricsBatchProcessor instance = new NoOpMetricsBatchProcessor(); - - private NoOpMetricsBatchProcessor() {} - - public static NoOpMetricsBatchProcessor getInstance() { - return instance; - } - - @Override - public void add(@NotNull SentryMetricsEvent event) { - // do nothing - } - - @Override - public void close(final boolean isRestarting) { - // do nothing - } - - @Override - public void flush(long timeoutMillis) { - // do nothing - } -}