Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
12 changes: 12 additions & 0 deletions sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -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 <init> (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 <init> ()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 <init> ()V
public fun collect (Lio/sentry/PerformanceCollectionData;)V
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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<AndroidLoggerBatchProcessor>(processor)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
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.SentryOptions
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,
config: ((SentryOptions) -> Unit)? = null,
): AndroidLoggerBatchProcessor {
if (useImmediateExecutor) {
options.executorService = ImmediateExecutorService()
}
config?.invoke(options)
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 sut =
fixture.getSut { options ->
val rejectingExecutor = mock<io.sentry.ISentryExecutorService>()
whenever(rejectingExecutor.submit(any())).thenThrow(RuntimeException("Rejected"))
options.executorService = rejectingExecutor
}

// 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())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
14 changes: 13 additions & 1 deletion sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -3669,9 +3669,11 @@ public final class io/sentry/SentryOptions$DistributionOptions {
public final class io/sentry/SentryOptions$Logs {
public fun <init> ()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 {
Expand Down Expand Up @@ -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 <init> ()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
Expand All @@ -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 <init> (Lio/sentry/Scopes;)V
public fun debug (Ljava/lang/String;[Ljava/lang/Object;)V
Expand All @@ -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 <init> (Lio/sentry/SentryOptions;Lio/sentry/ISentryClient;)V
public fun add (Lio/sentry/SentryLogEvent;)V
public fun close (Z)V
Expand Down
4 changes: 2 additions & 2 deletions sentry/src/main/java/io/sentry/SentryClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
Expand Down
16 changes: 16 additions & 0 deletions sentry/src/main/java/io/sentry/SentryOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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 {

Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Comment on lines 26 to 32

This comment was marked as outdated.


private final @NotNull SentryOptions options;
protected final @NotNull SentryOptions options;
private final @NotNull ISentryClient client;
private final @NotNull Queue<SentryLogEvent> queue;
private final @NotNull ISentryExecutorService executorService;
Expand Down
Loading
Loading