Skip to content

Commit 76ef13c

Browse files
authored
Merge branch 'main' into feat/tombstone_integration
2 parents 605a840 + dba088c commit 76ef13c

16 files changed

+277
-7
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
# Changelog
22

3-
## Unreleased
3+
## 8.28.0
44

55
### Features
66

7+
- Android: Flush logs when app enters background ([#4951](https://github.com/getsentry/sentry-java/pull/4951))
78
- Add option to capture additional OkHttp network request/response details in session replays ([#4919](https://github.com/getsentry/sentry-java/pull/4919))
89
- Depends on `SentryOkHttpInterceptor` to intercept the request and extract request/response bodies
910
- 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))

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
1111
android.useAndroidX=true
1212

1313
# Release information
14-
versionName=8.27.1
14+
versionName=8.28.0
1515

1616
# Override the SDK name on native crashes on Android
1717
sentryAndroidSdkName=sentry.native.android

sentry-android-core/api/sentry-android-core.api

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,18 @@ public final class io/sentry/android/core/AndroidLogger : io/sentry/ILogger {
8282
public fun log (Lio/sentry/SentryLevel;Ljava/lang/Throwable;Ljava/lang/String;[Ljava/lang/Object;)V
8383
}
8484

85+
public final class io/sentry/android/core/AndroidLoggerBatchProcessor : io/sentry/logger/LoggerBatchProcessor, io/sentry/android/core/AppState$AppStateListener {
86+
public fun <init> (Lio/sentry/SentryOptions;Lio/sentry/ISentryClient;)V
87+
public fun close (Z)V
88+
public fun onBackground ()V
89+
public fun onForeground ()V
90+
}
91+
92+
public final class io/sentry/android/core/AndroidLoggerBatchProcessorFactory : io/sentry/logger/ILoggerBatchProcessorFactory {
93+
public fun <init> ()V
94+
public fun create (Lio/sentry/SentryOptions;Lio/sentry/SentryClient;)Lio/sentry/logger/ILoggerBatchProcessor;
95+
}
96+
8597
public class io/sentry/android/core/AndroidMemoryCollector : io/sentry/IPerformanceSnapshotCollector {
8698
public fun <init> ()V
8799
public fun collect (Lio/sentry/PerformanceCollectionData;)V
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package io.sentry.android.core;
2+
3+
import io.sentry.ISentryClient;
4+
import io.sentry.SentryLevel;
5+
import io.sentry.SentryOptions;
6+
import io.sentry.logger.LoggerBatchProcessor;
7+
import org.jetbrains.annotations.ApiStatus;
8+
import org.jetbrains.annotations.NotNull;
9+
10+
@ApiStatus.Internal
11+
public final class AndroidLoggerBatchProcessor extends LoggerBatchProcessor
12+
implements AppState.AppStateListener {
13+
14+
public AndroidLoggerBatchProcessor(
15+
@NotNull SentryOptions options, @NotNull ISentryClient client) {
16+
super(options, client);
17+
AppState.getInstance().addAppStateListener(this);
18+
}
19+
20+
@Override
21+
public void onForeground() {
22+
// no-op
23+
}
24+
25+
@Override
26+
public void onBackground() {
27+
try {
28+
options
29+
.getExecutorService()
30+
.submit(
31+
new Runnable() {
32+
@Override
33+
public void run() {
34+
flush(LoggerBatchProcessor.FLUSH_AFTER_MS);
35+
}
36+
});
37+
} catch (Throwable t) {
38+
options.getLogger().log(SentryLevel.ERROR, t, "Failed to submit log flush in onBackground()");
39+
}
40+
}
41+
42+
@Override
43+
public void close(boolean isRestarting) {
44+
AppState.getInstance().removeAppStateListener(this);
45+
super.close(isRestarting);
46+
}
47+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.sentry.android.core;
2+
3+
import io.sentry.SentryClient;
4+
import io.sentry.SentryOptions;
5+
import io.sentry.logger.ILoggerBatchProcessor;
6+
import io.sentry.logger.ILoggerBatchProcessorFactory;
7+
import org.jetbrains.annotations.NotNull;
8+
9+
public final class AndroidLoggerBatchProcessorFactory implements ILoggerBatchProcessorFactory {
10+
@Override
11+
public @NotNull ILoggerBatchProcessor create(
12+
@NotNull SentryOptions options, @NotNull SentryClient client) {
13+
return new AndroidLoggerBatchProcessor(options, client);
14+
}
15+
}

sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ static void loadDefaultAndMetadataOptions(
124124
options.setOpenTelemetryMode(SentryOpenTelemetryMode.OFF);
125125
options.setDateProvider(new SentryAndroidDateProvider());
126126
options.setRuntimeManager(new AndroidRuntimeManager());
127+
options.getLogs().setLoggerBatchProcessorFactory(new AndroidLoggerBatchProcessorFactory());
127128

128129
// set a lower flush timeout on Android to avoid ANRs
129130
options.setFlushTimeoutMillis(DEFAULT_FLUSH_TIMEOUT_MS);
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package io.sentry.android.core
2+
3+
import androidx.test.ext.junit.runners.AndroidJUnit4
4+
import io.sentry.SentryClient
5+
import kotlin.test.Test
6+
import kotlin.test.assertIs
7+
import org.junit.runner.RunWith
8+
import org.mockito.kotlin.mock
9+
10+
@RunWith(AndroidJUnit4::class)
11+
class AndroidLoggerBatchProcessorFactoryTest {
12+
13+
@Test
14+
fun `create returns AndroidLoggerBatchProcessor instance`() {
15+
val factory = AndroidLoggerBatchProcessorFactory()
16+
val options = SentryAndroidOptions()
17+
val client: SentryClient = mock()
18+
19+
val processor = factory.create(options, client)
20+
21+
assertIs<AndroidLoggerBatchProcessor>(processor)
22+
}
23+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package io.sentry.android.core
2+
3+
import androidx.test.ext.junit.runners.AndroidJUnit4
4+
import io.sentry.ISentryClient
5+
import io.sentry.SentryLogEvent
6+
import io.sentry.SentryLogLevel
7+
import io.sentry.SentryOptions
8+
import io.sentry.protocol.SentryId
9+
import io.sentry.test.ImmediateExecutorService
10+
import kotlin.test.AfterTest
11+
import kotlin.test.BeforeTest
12+
import kotlin.test.Test
13+
import kotlin.test.assertNotNull
14+
import kotlin.test.assertTrue
15+
import org.junit.runner.RunWith
16+
import org.mockito.kotlin.any
17+
import org.mockito.kotlin.mock
18+
import org.mockito.kotlin.verify
19+
import org.mockito.kotlin.whenever
20+
21+
@RunWith(AndroidJUnit4::class)
22+
class AndroidLoggerBatchProcessorTest {
23+
24+
private class Fixture {
25+
val options = SentryAndroidOptions()
26+
val client: ISentryClient = mock()
27+
28+
fun getSut(
29+
useImmediateExecutor: Boolean = false,
30+
config: ((SentryOptions) -> Unit)? = null,
31+
): AndroidLoggerBatchProcessor {
32+
if (useImmediateExecutor) {
33+
options.executorService = ImmediateExecutorService()
34+
}
35+
config?.invoke(options)
36+
return AndroidLoggerBatchProcessor(options, client)
37+
}
38+
}
39+
40+
private val fixture = Fixture()
41+
42+
@BeforeTest
43+
fun `set up`() {
44+
AppState.getInstance().resetInstance()
45+
}
46+
47+
@AfterTest
48+
fun `tear down`() {
49+
AppState.getInstance().resetInstance()
50+
}
51+
52+
@Test
53+
fun `constructor registers as AppState listener`() {
54+
fixture.getSut()
55+
assertNotNull(AppState.getInstance().lifecycleObserver)
56+
}
57+
58+
@Test
59+
fun `onBackground schedules flush`() {
60+
val sut = fixture.getSut(useImmediateExecutor = true)
61+
val logEvent = SentryLogEvent(SentryId(), 1.0, "test", SentryLogLevel.INFO)
62+
sut.add(logEvent)
63+
64+
sut.onBackground()
65+
66+
verify(fixture.client).captureBatchedLogEvents(any())
67+
}
68+
69+
@Test
70+
fun `onBackground handles executor exception gracefully`() {
71+
val sut =
72+
fixture.getSut { options ->
73+
val rejectingExecutor = mock<io.sentry.ISentryExecutorService>()
74+
whenever(rejectingExecutor.submit(any())).thenThrow(RuntimeException("Rejected"))
75+
options.executorService = rejectingExecutor
76+
}
77+
78+
// Should not throw
79+
sut.onBackground()
80+
}
81+
82+
@Test
83+
fun `close removes AppState listener`() {
84+
val sut = fixture.getSut()
85+
sut.close(false)
86+
87+
assertTrue(AppState.getInstance().lifecycleObserver.listeners.isEmpty())
88+
}
89+
90+
@Test
91+
fun `close with isRestarting true still removes listener`() {
92+
val sut = fixture.getSut()
93+
sut.close(true)
94+
95+
assertTrue(AppState.getInstance().lifecycleObserver.listeners.isEmpty())
96+
}
97+
}

sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,15 @@ class AndroidOptionsInitializerTest {
775775
assertTrue { fixture.sentryOptions.socketTagger is AndroidSocketTagger }
776776
}
777777

778+
@Test
779+
fun `AndroidLoggerBatchProcessorFactory is set to options`() {
780+
fixture.initSut()
781+
782+
assertTrue {
783+
fixture.sentryOptions.logs.loggerBatchProcessorFactory is AndroidLoggerBatchProcessorFactory
784+
}
785+
}
786+
778787
@Test
779788
fun `does not install ComposeGestureTargetLocator, if sentry-compose is not available`() {
780789
fixture.initSutWithClassLoader()

sentry/api/sentry.api

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3669,9 +3669,11 @@ public final class io/sentry/SentryOptions$DistributionOptions {
36693669
public final class io/sentry/SentryOptions$Logs {
36703670
public fun <init> ()V
36713671
public fun getBeforeSend ()Lio/sentry/SentryOptions$Logs$BeforeSendLogCallback;
3672+
public fun getLoggerBatchProcessorFactory ()Lio/sentry/logger/ILoggerBatchProcessorFactory;
36723673
public fun isEnabled ()Z
36733674
public fun setBeforeSend (Lio/sentry/SentryOptions$Logs$BeforeSendLogCallback;)V
36743675
public fun setEnabled (Z)V
3676+
public fun setLoggerBatchProcessorFactory (Lio/sentry/logger/ILoggerBatchProcessorFactory;)V
36753677
}
36763678

36773679
public abstract interface class io/sentry/SentryOptions$Logs$BeforeSendLogCallback {
@@ -5025,6 +5027,11 @@ public abstract interface class io/sentry/internal/viewhierarchy/ViewHierarchyEx
50255027
public abstract fun export (Lio/sentry/protocol/ViewHierarchyNode;Ljava/lang/Object;)Z
50265028
}
50275029

5030+
public final class io/sentry/logger/DefaultLoggerBatchProcessorFactory : io/sentry/logger/ILoggerBatchProcessorFactory {
5031+
public fun <init> ()V
5032+
public fun create (Lio/sentry/SentryOptions;Lio/sentry/SentryClient;)Lio/sentry/logger/ILoggerBatchProcessor;
5033+
}
5034+
50285035
public abstract interface class io/sentry/logger/ILoggerApi {
50295036
public abstract fun debug (Ljava/lang/String;[Ljava/lang/Object;)V
50305037
public abstract fun error (Ljava/lang/String;[Ljava/lang/Object;)V
@@ -5043,6 +5050,10 @@ public abstract interface class io/sentry/logger/ILoggerBatchProcessor {
50435050
public abstract fun flush (J)V
50445051
}
50455052

5053+
public abstract interface class io/sentry/logger/ILoggerBatchProcessorFactory {
5054+
public abstract fun create (Lio/sentry/SentryOptions;Lio/sentry/SentryClient;)Lio/sentry/logger/ILoggerBatchProcessor;
5055+
}
5056+
50465057
public final class io/sentry/logger/LoggerApi : io/sentry/logger/ILoggerApi {
50475058
public fun <init> (Lio/sentry/Scopes;)V
50485059
public fun debug (Ljava/lang/String;[Ljava/lang/Object;)V
@@ -5056,10 +5067,11 @@ public final class io/sentry/logger/LoggerApi : io/sentry/logger/ILoggerApi {
50565067
public fun warn (Ljava/lang/String;[Ljava/lang/Object;)V
50575068
}
50585069

5059-
public final class io/sentry/logger/LoggerBatchProcessor : io/sentry/logger/ILoggerBatchProcessor {
5070+
public class io/sentry/logger/LoggerBatchProcessor : io/sentry/logger/ILoggerBatchProcessor {
50605071
public static final field FLUSH_AFTER_MS I
50615072
public static final field MAX_BATCH_SIZE I
50625073
public static final field MAX_QUEUE_SIZE I
5074+
protected final field options Lio/sentry/SentryOptions;
50635075
public fun <init> (Lio/sentry/SentryOptions;Lio/sentry/ISentryClient;)V
50645076
public fun add (Lio/sentry/SentryLogEvent;)V
50655077
public fun close (Z)V

0 commit comments

Comments
 (0)