diff --git a/sentry-android-core/build.gradle.kts b/sentry-android-core/build.gradle.kts index 0e3708a89b..957244b852 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 d31d925810..cd47f96d87 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 2bd26051c0..cacdaf0834 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 ad5c909836..275460a034 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 aa71fa7471..ba8956696d 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 5aae1be93f..6597d528f8 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()