From 240923613a508d75729695dd82eaf6ffe012ff5b Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Thu, 4 Dec 2025 07:32:06 +0100 Subject: [PATCH 1/4] feat(android): Add log flushing on app backgrounding --- .../api/sentry-android-core.api | 12 +++ .../core/AndroidLoggerBatchProcessor.java | 47 ++++++++++ .../AndroidLoggerBatchProcessorFactory.java | 15 +++ .../core/AndroidOptionsInitializer.java | 1 + .../AndroidLoggerBatchProcessorFactoryTest.kt | 23 +++++ .../core/AndroidLoggerBatchProcessorTest.kt | 93 +++++++++++++++++++ .../core/AndroidOptionsInitializerTest.kt | 9 ++ sentry/api/sentry.api | 14 ++- .../src/main/java/io/sentry/SentryClient.java | 4 +- .../main/java/io/sentry/SentryOptions.java | 16 ++++ .../DefaultLoggerBatchProcessorFactory.java | 13 +++ .../logger/ILoggerBatchProcessorFactory.java | 12 +++ .../sentry/logger/LoggerBatchProcessor.java | 6 +- 13 files changed, 260 insertions(+), 5 deletions(-) create mode 100644 sentry-android-core/src/main/java/io/sentry/android/core/AndroidLoggerBatchProcessor.java create mode 100644 sentry-android-core/src/main/java/io/sentry/android/core/AndroidLoggerBatchProcessorFactory.java create mode 100644 sentry-android-core/src/test/java/io/sentry/android/core/AndroidLoggerBatchProcessorFactoryTest.kt create mode 100644 sentry-android-core/src/test/java/io/sentry/android/core/AndroidLoggerBatchProcessorTest.kt create mode 100644 sentry/src/main/java/io/sentry/logger/DefaultLoggerBatchProcessorFactory.java create mode 100644 sentry/src/main/java/io/sentry/logger/ILoggerBatchProcessorFactory.java diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api index 1c89d8524c0..08895e6713f 100644 --- a/sentry-android-core/api/sentry-android-core.api +++ b/sentry-android-core/api/sentry-android-core.api @@ -82,6 +82,18 @@ public final class io/sentry/android/core/AndroidLogger : io/sentry/ILogger { public fun log (Lio/sentry/SentryLevel;Ljava/lang/Throwable;Ljava/lang/String;[Ljava/lang/Object;)V } +public final class io/sentry/android/core/AndroidLoggerBatchProcessor : io/sentry/logger/LoggerBatchProcessor, io/sentry/android/core/AppState$AppStateListener { + public fun (Lio/sentry/SentryOptions;Lio/sentry/ISentryClient;)V + public fun close (Z)V + public fun onBackground ()V + public fun onForeground ()V +} + +public final class io/sentry/android/core/AndroidLoggerBatchProcessorFactory : io/sentry/logger/ILoggerBatchProcessorFactory { + public fun ()V + public fun create (Lio/sentry/SentryOptions;Lio/sentry/SentryClient;)Lio/sentry/logger/ILoggerBatchProcessor; +} + public class io/sentry/android/core/AndroidMemoryCollector : io/sentry/IPerformanceSnapshotCollector { public fun ()V public fun collect (Lio/sentry/PerformanceCollectionData;)V diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidLoggerBatchProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidLoggerBatchProcessor.java new file mode 100644 index 00000000000..13b12dc702a --- /dev/null +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidLoggerBatchProcessor.java @@ -0,0 +1,47 @@ +package io.sentry.android.core; + +import io.sentry.ISentryClient; +import io.sentry.SentryLevel; +import io.sentry.SentryOptions; +import io.sentry.logger.LoggerBatchProcessor; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +@ApiStatus.Internal +public final class AndroidLoggerBatchProcessor extends LoggerBatchProcessor + implements AppState.AppStateListener { + + public AndroidLoggerBatchProcessor( + @NotNull SentryOptions options, @NotNull ISentryClient client) { + super(options, client); + AppState.getInstance().addAppStateListener(this); + } + + @Override + public void onForeground() { + // no-op + } + + @Override + public void onBackground() { + try { + options + .getExecutorService() + .submit( + new Runnable() { + @Override + public void run() { + flush(LoggerBatchProcessor.FLUSH_AFTER_MS); + } + }); + } catch (Throwable t) { + options.getLogger().log(SentryLevel.ERROR, t, "Failed to submit log flush in onBackground()"); + } + } + + @Override + public void close(boolean isRestarting) { + AppState.getInstance().removeAppStateListener(this); + super.close(isRestarting); + } +} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidLoggerBatchProcessorFactory.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidLoggerBatchProcessorFactory.java new file mode 100644 index 00000000000..694f94c7f7b --- /dev/null +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidLoggerBatchProcessorFactory.java @@ -0,0 +1,15 @@ +package io.sentry.android.core; + +import io.sentry.SentryClient; +import io.sentry.SentryOptions; +import io.sentry.logger.ILoggerBatchProcessor; +import io.sentry.logger.ILoggerBatchProcessorFactory; +import org.jetbrains.annotations.NotNull; + +public final class AndroidLoggerBatchProcessorFactory implements ILoggerBatchProcessorFactory { + @Override + public @NotNull ILoggerBatchProcessor create( + @NotNull SentryOptions options, @NotNull SentryClient client) { + return new AndroidLoggerBatchProcessor(options, client); + } +} 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 4e679a22e96..c284d2256e2 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 @@ -123,6 +123,7 @@ static void loadDefaultAndMetadataOptions( options.setOpenTelemetryMode(SentryOpenTelemetryMode.OFF); options.setDateProvider(new SentryAndroidDateProvider()); options.setRuntimeManager(new AndroidRuntimeManager()); + options.getLogs().setLoggerBatchProcessorFactory(new AndroidLoggerBatchProcessorFactory()); // set a lower flush timeout on Android to avoid ANRs options.setFlushTimeoutMillis(DEFAULT_FLUSH_TIMEOUT_MS); diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidLoggerBatchProcessorFactoryTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidLoggerBatchProcessorFactoryTest.kt new file mode 100644 index 00000000000..33d66c2b9ad --- /dev/null +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidLoggerBatchProcessorFactoryTest.kt @@ -0,0 +1,23 @@ +package io.sentry.android.core + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.sentry.SentryClient +import kotlin.test.Test +import kotlin.test.assertIs +import org.junit.runner.RunWith +import org.mockito.kotlin.mock + +@RunWith(AndroidJUnit4::class) +class AndroidLoggerBatchProcessorFactoryTest { + + @Test + fun `create returns AndroidLoggerBatchProcessor instance`() { + val factory = AndroidLoggerBatchProcessorFactory() + val options = SentryAndroidOptions() + val client: SentryClient = mock() + + val processor = factory.create(options, client) + + assertIs(processor) + } +} 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 new file mode 100644 index 00000000000..54a36c0b1b5 --- /dev/null +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidLoggerBatchProcessorTest.kt @@ -0,0 +1,93 @@ +package io.sentry.android.core + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.sentry.ISentryClient +import io.sentry.SentryLogEvent +import io.sentry.SentryLogLevel +import io.sentry.protocol.SentryId +import io.sentry.test.ImmediateExecutorService +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertNotNull +import kotlin.test.assertTrue +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@RunWith(AndroidJUnit4::class) +class AndroidLoggerBatchProcessorTest { + + private class Fixture { + val options = SentryAndroidOptions() + val client: ISentryClient = mock() + + fun getSut(useImmediateExecutor: Boolean = false): AndroidLoggerBatchProcessor { + if (useImmediateExecutor) { + options.executorService = ImmediateExecutorService() + } + return AndroidLoggerBatchProcessor(options, client) + } + } + + private val fixture = Fixture() + + @BeforeTest + fun `set up`() { + AppState.getInstance().resetInstance() + } + + @AfterTest + fun `tear down`() { + AppState.getInstance().resetInstance() + } + + @Test + fun `constructor registers as AppState listener`() { + fixture.getSut() + assertNotNull(AppState.getInstance().lifecycleObserver) + } + + @Test + fun `onBackground schedules flush`() { + val sut = fixture.getSut(useImmediateExecutor = true) + val logEvent = SentryLogEvent(SentryId(), 1.0, "test", SentryLogLevel.INFO) + sut.add(logEvent) + + sut.onBackground() + + verify(fixture.client).captureBatchedLogEvents(any()) + } + + @Test + fun `onBackground handles executor exception gracefully`() { + val options = SentryAndroidOptions() + // Use a rejecting executor + val rejectingExecutor = mock() + whenever(rejectingExecutor.submit(any())).thenThrow(RuntimeException("Rejected")) + options.executorService = rejectingExecutor + + val sut = AndroidLoggerBatchProcessor(options, fixture.client) + + // Should not throw + sut.onBackground() + } + + @Test + fun `close removes AppState listener`() { + val sut = fixture.getSut() + sut.close(false) + + assertTrue(AppState.getInstance().lifecycleObserver.listeners.isEmpty()) + } + + @Test + fun `close with isRestarting true still removes listener`() { + val sut = fixture.getSut() + sut.close(true) + + assertTrue(AppState.getInstance().lifecycleObserver.listeners.isEmpty()) + } +} 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 290ae6dea9d..47825cd1e7b 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 @@ -774,6 +774,15 @@ class AndroidOptionsInitializerTest { assertTrue { fixture.sentryOptions.socketTagger is AndroidSocketTagger } } + @Test + fun `AndroidLoggerBatchProcessorFactory is set to options`() { + fixture.initSut() + + assertTrue { + fixture.sentryOptions.logs.loggerBatchProcessorFactory is AndroidLoggerBatchProcessorFactory + } + } + @Test fun `does not install ComposeGestureTargetLocator, if sentry-compose is not available`() { fixture.initSutWithClassLoader() diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index bee8b9e343e..5cb6632011a 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -3669,9 +3669,11 @@ public final class io/sentry/SentryOptions$DistributionOptions { 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 } public abstract interface class io/sentry/SentryOptions$Logs$BeforeSendLogCallback { @@ -5021,6 +5023,11 @@ public abstract interface class io/sentry/internal/viewhierarchy/ViewHierarchyEx public abstract fun export (Lio/sentry/protocol/ViewHierarchyNode;Ljava/lang/Object;)Z } +public final class io/sentry/logger/DefaultLoggerBatchProcessorFactory : io/sentry/logger/ILoggerBatchProcessorFactory { + public fun ()V + public fun create (Lio/sentry/SentryOptions;Lio/sentry/SentryClient;)Lio/sentry/logger/ILoggerBatchProcessor; +} + public abstract interface class io/sentry/logger/ILoggerApi { public abstract fun debug (Ljava/lang/String;[Ljava/lang/Object;)V public abstract fun error (Ljava/lang/String;[Ljava/lang/Object;)V @@ -5039,6 +5046,10 @@ public abstract interface class io/sentry/logger/ILoggerBatchProcessor { public abstract fun flush (J)V } +public abstract interface class io/sentry/logger/ILoggerBatchProcessorFactory { + public abstract fun create (Lio/sentry/SentryOptions;Lio/sentry/SentryClient;)Lio/sentry/logger/ILoggerBatchProcessor; +} + public final class io/sentry/logger/LoggerApi : io/sentry/logger/ILoggerApi { public fun (Lio/sentry/Scopes;)V public fun debug (Ljava/lang/String;[Ljava/lang/Object;)V @@ -5052,10 +5063,11 @@ public final class io/sentry/logger/LoggerApi : io/sentry/logger/ILoggerApi { public fun warn (Ljava/lang/String;[Ljava/lang/Object;)V } -public final class io/sentry/logger/LoggerBatchProcessor : io/sentry/logger/ILoggerBatchProcessor { +public class io/sentry/logger/LoggerBatchProcessor : io/sentry/logger/ILoggerBatchProcessor { public static final field FLUSH_AFTER_MS I public static final field MAX_BATCH_SIZE I 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 add (Lio/sentry/SentryLogEvent;)V public fun close (Z)V diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 73ff534dad8..0a767ed1b8e 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.LoggerBatchProcessor; import io.sentry.logger.NoOpLoggerBatchProcessor; import io.sentry.protocol.Contexts; import io.sentry.protocol.DebugMeta; @@ -62,7 +61,8 @@ public SentryClient(final @NotNull SentryOptions options) { final RequestDetailsResolver requestDetailsResolver = new RequestDetailsResolver(options); transport = transportFactory.create(options, requestDetailsResolver.resolve()); if (options.getLogs().isEnabled()) { - loggerBatchProcessor = new LoggerBatchProcessor(options, this); + loggerBatchProcessor = + options.getLogs().getLoggerBatchProcessorFactory().create(options, this); } else { loggerBatchProcessor = NoOpLoggerBatchProcessor.getInstance(); } diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 368d9121959..7cfdf24a722 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -15,6 +15,8 @@ import io.sentry.internal.modules.IModulesLoader; import io.sentry.internal.modules.NoOpModulesLoader; import io.sentry.internal.viewhierarchy.ViewHierarchyExporter; +import io.sentry.logger.DefaultLoggerBatchProcessorFactory; +import io.sentry.logger.ILoggerBatchProcessorFactory; import io.sentry.protocol.SdkVersion; import io.sentry.protocol.SentryTransaction; import io.sentry.transport.ITransport; @@ -3672,6 +3674,9 @@ public static final class Logs { */ private @Nullable BeforeSendLogCallback beforeSend; + private @NotNull ILoggerBatchProcessorFactory loggerBatchProcessorFactory = + new DefaultLoggerBatchProcessorFactory(); + /** * Whether Sentry Logs feature is enabled and Sentry.logger() usages are sent to Sentry. * @@ -3708,6 +3713,17 @@ public void setBeforeSend(@Nullable BeforeSendLogCallback beforeSendLog) { this.beforeSend = beforeSendLog; } + @ApiStatus.Internal + public @NotNull ILoggerBatchProcessorFactory getLoggerBatchProcessorFactory() { + return loggerBatchProcessorFactory; + } + + @ApiStatus.Internal + public void setLoggerBatchProcessorFactory( + final @NotNull ILoggerBatchProcessorFactory loggerBatchProcessorFactory) { + this.loggerBatchProcessorFactory = loggerBatchProcessorFactory; + } + /** The BeforeSendLog callback */ public interface BeforeSendLogCallback { diff --git a/sentry/src/main/java/io/sentry/logger/DefaultLoggerBatchProcessorFactory.java b/sentry/src/main/java/io/sentry/logger/DefaultLoggerBatchProcessorFactory.java new file mode 100644 index 00000000000..c722da9be9d --- /dev/null +++ b/sentry/src/main/java/io/sentry/logger/DefaultLoggerBatchProcessorFactory.java @@ -0,0 +1,13 @@ +package io.sentry.logger; + +import io.sentry.SentryClient; +import io.sentry.SentryOptions; +import org.jetbrains.annotations.NotNull; + +public final class DefaultLoggerBatchProcessorFactory implements ILoggerBatchProcessorFactory { + @Override + public @NotNull ILoggerBatchProcessor create( + @NotNull SentryOptions options, @NotNull SentryClient client) { + return new LoggerBatchProcessor(options, client); + } +} diff --git a/sentry/src/main/java/io/sentry/logger/ILoggerBatchProcessorFactory.java b/sentry/src/main/java/io/sentry/logger/ILoggerBatchProcessorFactory.java new file mode 100644 index 00000000000..029c3e15ef9 --- /dev/null +++ b/sentry/src/main/java/io/sentry/logger/ILoggerBatchProcessorFactory.java @@ -0,0 +1,12 @@ +package io.sentry.logger; + +import io.sentry.SentryClient; +import io.sentry.SentryOptions; +import org.jetbrains.annotations.NotNull; + +public interface ILoggerBatchProcessorFactory { + + @NotNull + ILoggerBatchProcessor create( + final @NotNull SentryOptions options, final @NotNull SentryClient client); +} diff --git a/sentry/src/main/java/io/sentry/logger/LoggerBatchProcessor.java b/sentry/src/main/java/io/sentry/logger/LoggerBatchProcessor.java index 48a73400f51..cdea169b925 100644 --- a/sentry/src/main/java/io/sentry/logger/LoggerBatchProcessor.java +++ b/sentry/src/main/java/io/sentry/logger/LoggerBatchProcessor.java @@ -1,5 +1,6 @@ package io.sentry.logger; +import com.jakewharton.nopen.annotation.Open; import io.sentry.DataCategory; import io.sentry.ISentryClient; import io.sentry.ISentryExecutorService; @@ -23,13 +24,14 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public final class LoggerBatchProcessor implements ILoggerBatchProcessor { +@Open +public class LoggerBatchProcessor implements ILoggerBatchProcessor { public static final int FLUSH_AFTER_MS = 5000; public static final int MAX_BATCH_SIZE = 100; public static final int MAX_QUEUE_SIZE = 1000; - private final @NotNull SentryOptions options; + protected final @NotNull SentryOptions options; private final @NotNull ISentryClient client; private final @NotNull Queue queue; private final @NotNull ISentryExecutorService executorService; From 91a6504fcbb8f602e8ceda2a45ce6a8c1bbac3ce Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Thu, 4 Dec 2025 07:34:23 +0100 Subject: [PATCH 2/4] Update Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cce62395402..1e2fb45b128 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- Android: Flush logs when app enters background ([#4951](https://github.com/getsentry/sentry-java/pull/4951)) - Add option to capture additional OkHttp network request/response details in session replays ([#4919](https://github.com/getsentry/sentry-java/pull/4919)) - Depends on `SentryOkHttpInterceptor` to intercept the request and extract request/response bodies - To enable, add url regexes via the `io.sentry.session-replay.network-detail-allow-urls` metadata tag in AndroidManifest ([code sample](https://github.com/getsentry/sentry-java/blob/b03edbb1b0d8b871c62a09bc02cbd8a4e1f6fea1/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml#L196-L205)) From d10308e1669ac663cbcb28c4d01f20d12e7d6962 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Thu, 4 Dec 2025 09:51:33 +0100 Subject: [PATCH 3/4] Improve tests --- .../core/AndroidLoggerBatchProcessorTest.kt | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 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 54a36c0b1b5..ab83671fa0e 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 @@ -4,6 +4,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 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 kotlin.test.AfterTest @@ -24,10 +25,14 @@ class AndroidLoggerBatchProcessorTest { val options = SentryAndroidOptions() val client: ISentryClient = mock() - fun getSut(useImmediateExecutor: Boolean = false): AndroidLoggerBatchProcessor { + fun getSut( + useImmediateExecutor: Boolean = false, + config: ((SentryOptions) -> Unit)? = null, + ): AndroidLoggerBatchProcessor { if (useImmediateExecutor) { options.executorService = ImmediateExecutorService() } + config?.invoke(options) return AndroidLoggerBatchProcessor(options, client) } } @@ -63,13 +68,12 @@ class AndroidLoggerBatchProcessorTest { @Test fun `onBackground handles executor exception gracefully`() { - val options = SentryAndroidOptions() - // Use a rejecting executor - val rejectingExecutor = mock() - whenever(rejectingExecutor.submit(any())).thenThrow(RuntimeException("Rejected")) - options.executorService = rejectingExecutor - - val sut = AndroidLoggerBatchProcessor(options, fixture.client) + val sut = + fixture.getSut { options -> + val rejectingExecutor = mock() + whenever(rejectingExecutor.submit(any())).thenThrow(RuntimeException("Rejected")) + options.executorService = rejectingExecutor + } // Should not throw sut.onBackground() From a991803d72fcb2ad4dd07e7110d07af86011cf18 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Thu, 4 Dec 2025 12:01:50 +0100 Subject: [PATCH 4/4] Improve test coverage --- sentry/src/test/java/io/sentry/SentryOptionsTest.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt index bf54db9228d..e882f6fdc6f 100644 --- a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt @@ -1,6 +1,7 @@ package io.sentry import io.sentry.SentryOptions.RequestSize +import io.sentry.logger.ILoggerBatchProcessorFactory import io.sentry.util.StringUtils import java.io.File import java.net.Proxy @@ -12,6 +13,7 @@ import kotlin.test.assertIs import kotlin.test.assertNotEquals import kotlin.test.assertNotNull import kotlin.test.assertNull +import kotlin.test.assertSame import kotlin.test.assertTrue import org.mockito.kotlin.eq import org.mockito.kotlin.mock @@ -908,4 +910,12 @@ class SentryOptionsTest { options.maxFeatureFlags = 50 assertEquals(50, options.maxFeatureFlags) } + + @Test + fun `loggerBatchFactory can be changed`() { + val mock = mock() + val options = SentryOptions() + options.logs.loggerBatchProcessorFactory = mock + assertSame(mock, options.logs.loggerBatchProcessorFactory) + } }