Skip to content

Commit 294dba2

Browse files
authored
Merge 0bd817c into ee35ac3
2 parents ee35ac3 + 0bd817c commit 294dba2

20 files changed

+244
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ SentryAndroid.init(
4949
```
5050

5151
</details>
52+
- Android: Flush logs when app enters background ([#4873](https://github.com/getsentry/sentry-java/pull/4873))
5253

5354

5455
### Improvements
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package io.sentry.android.core;
2+
3+
import io.sentry.Scopes;
4+
import io.sentry.SentryLevel;
5+
import io.sentry.logger.LoggerApi;
6+
import io.sentry.logger.LoggerBatchProcessor;
7+
import java.io.Closeable;
8+
import java.io.IOException;
9+
import org.jetbrains.annotations.ApiStatus;
10+
import org.jetbrains.annotations.NotNull;
11+
12+
@ApiStatus.Internal
13+
public class AndroidLoggerApi extends LoggerApi implements Closeable, AppState.AppStateListener {
14+
15+
public AndroidLoggerApi(final @NotNull Scopes scopes) {
16+
super(scopes);
17+
AppState.getInstance().addAppStateListener(this);
18+
}
19+
20+
@Override
21+
@ApiStatus.Internal
22+
public void close() throws IOException {
23+
AppState.getInstance().removeAppStateListener(this);
24+
}
25+
26+
@Override
27+
public void onForeground() {}
28+
29+
@Override
30+
public void onBackground() {
31+
try {
32+
scopes
33+
.getOptions()
34+
.getExecutorService()
35+
.submit(
36+
new Runnable() {
37+
@Override
38+
public void run() {
39+
scopes.getClient().flushLogs(LoggerBatchProcessor.FLUSH_AFTER_MS);
40+
}
41+
});
42+
} catch (Throwable t) {
43+
scopes
44+
.getOptions()
45+
.getLogger()
46+
.log(SentryLevel.ERROR, t, "Failed to submit log flush runnable");
47+
}
48+
}
49+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.sentry.android.core;
2+
3+
import io.sentry.Scopes;
4+
import io.sentry.logger.ILoggerApiFactory;
5+
import io.sentry.logger.LoggerApi;
6+
import org.jetbrains.annotations.ApiStatus;
7+
import org.jetbrains.annotations.NotNull;
8+
9+
@ApiStatus.Internal
10+
public final class AndroidLoggerApiFactory implements ILoggerApiFactory {
11+
12+
@Override
13+
@NotNull
14+
public LoggerApi create(@NotNull Scopes scopes) {
15+
return new AndroidLoggerApi(scopes);
16+
}
17+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ static void loadDefaultAndMetadataOptions(
139139

140140
readDefaultOptionValues(options, finalContext, buildInfoProvider);
141141
AppState.getInstance().registerLifecycleObserver(options);
142+
143+
options.setLoggerApiFactory(new AndroidLoggerApiFactory());
142144
}
143145

144146
@TestOnly

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions
4545
this.options.isEnableAppLifecycleBreadcrumbs());
4646

4747
if (this.options.isEnableAutoSessionTracking()
48-
|| this.options.isEnableAppLifecycleBreadcrumbs()) {
48+
|| this.options.isEnableAppLifecycleBreadcrumbs()
49+
|| this.options.getLogs().isEnabled()) {
4950
try (final ISentryLifecycleToken ignored = lock.acquire()) {
5051
if (watcher != null) {
5152
return;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package io.sentry.android.core
2+
3+
import androidx.test.ext.junit.runners.AndroidJUnit4
4+
import io.sentry.Scopes
5+
import kotlin.test.assertIs
6+
import org.junit.Test
7+
import org.junit.runner.RunWith
8+
import org.mockito.kotlin.mock
9+
10+
@RunWith(AndroidJUnit4::class)
11+
class AndroidLoggerApiFactoryTest {
12+
13+
@Test
14+
fun `factory creates AndroidLogger`() {
15+
val factory = AndroidLoggerApiFactory()
16+
val logger = factory.create(mock<Scopes>())
17+
assertIs<AndroidLoggerApi>(logger)
18+
}
19+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package io.sentry.android.core
2+
3+
import androidx.test.ext.junit.runners.AndroidJUnit4
4+
import io.sentry.Scopes
5+
import io.sentry.SentryClient
6+
import io.sentry.SentryOptions
7+
import io.sentry.test.ImmediateExecutorService
8+
import kotlin.test.assertTrue
9+
import org.junit.Before
10+
import org.junit.Test
11+
import org.junit.runner.RunWith
12+
import org.mockito.kotlin.any
13+
import org.mockito.kotlin.mock
14+
import org.mockito.kotlin.verify
15+
import org.mockito.kotlin.whenever
16+
17+
@RunWith(AndroidJUnit4::class)
18+
class AndroidLoggerApiTest {
19+
20+
@Before
21+
fun setup() {
22+
AppState.getInstance().resetInstance()
23+
}
24+
25+
@Test
26+
fun `AndroidLogger registers and unregisters app state listener`() {
27+
val scopes = mock<Scopes>()
28+
val logger = AndroidLoggerApi(scopes)
29+
assertTrue(AppState.getInstance().lifecycleObserver.listeners.isNotEmpty())
30+
31+
logger.close()
32+
assertTrue(AppState.getInstance().lifecycleObserver.listeners.isEmpty())
33+
}
34+
35+
@Test
36+
fun `AndroidLogger triggers flushing if app goes in background`() {
37+
val scopes = mock<Scopes>()
38+
39+
val client = mock<SentryClient>()
40+
whenever(scopes.client).thenReturn(client)
41+
42+
val options = SentryOptions()
43+
options.executorService = ImmediateExecutorService()
44+
whenever(scopes.options).thenReturn(options)
45+
46+
val logger = AndroidLoggerApi(scopes)
47+
logger.onBackground()
48+
49+
verify(client).flushLogs(any())
50+
}
51+
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -926,4 +926,10 @@ class AndroidOptionsInitializerTest {
926926
fixture.initSut()
927927
assertIs<AndroidRuntimeManager>(fixture.sentryOptions.runtimeManager)
928928
}
929+
930+
@Test
931+
fun `AndroidLoggerApiFactory is set in the options`() {
932+
fixture.initSut()
933+
assertIs<AndroidLoggerApiFactory>(fixture.sentryOptions.loggerApiFactory)
934+
}
929935
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import io.sentry.DateUtils
55
import io.sentry.IContinuousProfiler
66
import io.sentry.IScope
77
import io.sentry.IScopes
8+
import io.sentry.ISentryClient
89
import io.sentry.ReplayController
910
import io.sentry.ScopeCallback
1011
import io.sentry.SentryLevel
1112
import io.sentry.SentryOptions
1213
import io.sentry.Session
1314
import io.sentry.Session.State
15+
import io.sentry.test.ImmediateExecutorService
1416
import io.sentry.transport.ICurrentDateProvider
1517
import kotlin.test.BeforeTest
1618
import kotlin.test.Test
@@ -36,6 +38,8 @@ class LifecycleWatcherTest {
3638
val replayController = mock<ReplayController>()
3739
val continuousProfiler = mock<IContinuousProfiler>()
3840

41+
val client = mock<ISentryClient>()
42+
3943
fun getSUT(
4044
sessionIntervalMillis: Long = 0L,
4145
enableAutoSessionTracking: Boolean = true,
@@ -49,9 +53,13 @@ class LifecycleWatcherTest {
4953
whenever(scopes.configureScope(argumentCaptor.capture())).thenAnswer {
5054
argumentCaptor.value.run(scope)
5155
}
56+
whenever(scope.client).thenReturn(client)
57+
5258
options.setReplayController(replayController)
5359
options.setContinuousProfiler(continuousProfiler)
60+
options.executorService = ImmediateExecutorService()
5461
whenever(scopes.options).thenReturn(options)
62+
whenever(scopes.globalScope).thenReturn(scope)
5563

5664
return LifecycleWatcher(
5765
scopes,

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ class SessionTrackingIntegrationTest {
142142
TODO("Not yet implemented")
143143
}
144144

145+
override fun flushLogs(timeoutMillis: Long) {
146+
TODO("Not yet implemented")
147+
}
148+
145149
override fun captureFeedback(feedback: Feedback, hint: Hint?, scope: IScope): SentryId {
146150
TODO("Not yet implemented")
147151
}

0 commit comments

Comments
 (0)