diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f9fb0d541..57011ec10b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Unreleased ### Features + +- Customizable fragment lifecycle breadcrumbs ([#2299](https://github.com/getsentry/sentry-java/pull/2299)) - Provide hook for Jetpack Compose navigation instrumentation ([#2320](https://github.com/getsentry/sentry-java/pull/2320)) ## 6.6.0 diff --git a/sentry-android-fragment/api/sentry-android-fragment.api b/sentry-android-fragment/api/sentry-android-fragment.api index 5e7673c1b3..4b3487c36e 100644 --- a/sentry-android-fragment/api/sentry-android-fragment.api +++ b/sentry-android-fragment/api/sentry-android-fragment.api @@ -8,6 +8,7 @@ public final class io/sentry/android/fragment/BuildConfig { public final class io/sentry/android/fragment/FragmentLifecycleIntegration : android/app/Application$ActivityLifecycleCallbacks, io/sentry/Integration, java/io/Closeable { public fun (Landroid/app/Application;)V + public fun (Landroid/app/Application;Ljava/util/Set;Z)V public fun (Landroid/app/Application;ZZ)V public fun close ()V public fun onActivityCreated (Landroid/app/Activity;Landroid/os/Bundle;)V @@ -20,15 +21,33 @@ public final class io/sentry/android/fragment/FragmentLifecycleIntegration : and public fun register (Lio/sentry/IHub;Lio/sentry/SentryOptions;)V } +public final class io/sentry/android/fragment/FragmentLifecycleState : java/lang/Enum { + public static final field ATTACHED Lio/sentry/android/fragment/FragmentLifecycleState; + public static final field CREATED Lio/sentry/android/fragment/FragmentLifecycleState; + public static final field DESTROYED Lio/sentry/android/fragment/FragmentLifecycleState; + public static final field DETACHED Lio/sentry/android/fragment/FragmentLifecycleState; + public static final field PAUSED Lio/sentry/android/fragment/FragmentLifecycleState; + public static final field RESUMED Lio/sentry/android/fragment/FragmentLifecycleState; + public static final field SAVE_INSTANCE_STATE Lio/sentry/android/fragment/FragmentLifecycleState; + public static final field STARTED Lio/sentry/android/fragment/FragmentLifecycleState; + public static final field STOPPED Lio/sentry/android/fragment/FragmentLifecycleState; + public static final field VIEW_CREATED Lio/sentry/android/fragment/FragmentLifecycleState; + public static final field VIEW_DESTROYED Lio/sentry/android/fragment/FragmentLifecycleState; + public static fun valueOf (Ljava/lang/String;)Lio/sentry/android/fragment/FragmentLifecycleState; + public static fun values ()[Lio/sentry/android/fragment/FragmentLifecycleState; +} + public final class io/sentry/android/fragment/SentryFragmentLifecycleCallbacks : androidx/fragment/app/FragmentManager$FragmentLifecycleCallbacks { public static final field Companion Lio/sentry/android/fragment/SentryFragmentLifecycleCallbacks$Companion; public static final field FRAGMENT_LOAD_OP Ljava/lang/String; + public fun (Lio/sentry/IHub;Ljava/util/Set;Z)V + public synthetic fun (Lio/sentry/IHub;Ljava/util/Set;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Lio/sentry/IHub;ZZ)V - public synthetic fun (Lio/sentry/IHub;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (ZZ)V public synthetic fun (ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getEnableAutoFragmentLifecycleTracing ()Z public final fun getEnableFragmentLifecycleBreadcrumbs ()Z + public final fun getFilterFragmentLifecycleBreadcrumbs ()Ljava/util/Set; public fun onFragmentAttached (Landroidx/fragment/app/FragmentManager;Landroidx/fragment/app/Fragment;Landroid/content/Context;)V public fun onFragmentCreated (Landroidx/fragment/app/FragmentManager;Landroidx/fragment/app/Fragment;Landroid/os/Bundle;)V public fun onFragmentDestroyed (Landroidx/fragment/app/FragmentManager;Landroidx/fragment/app/Fragment;)V diff --git a/sentry-android-fragment/src/main/java/io/sentry/android/fragment/FragmentLifecycleIntegration.kt b/sentry-android-fragment/src/main/java/io/sentry/android/fragment/FragmentLifecycleIntegration.kt index dfa2b145cb..521292f8a8 100644 --- a/sentry-android-fragment/src/main/java/io/sentry/android/fragment/FragmentLifecycleIntegration.kt +++ b/sentry-android-fragment/src/main/java/io/sentry/android/fragment/FragmentLifecycleIntegration.kt @@ -13,14 +13,30 @@ import java.io.Closeable class FragmentLifecycleIntegration( private val application: Application, - private val enableFragmentLifecycleBreadcrumbs: Boolean, + private val filterFragmentLifecycleBreadcrumbs: Set, private val enableAutoFragmentLifecycleTracing: Boolean ) : ActivityLifecycleCallbacks, Integration, Closeable { - constructor(application: Application) : this(application, true, false) + constructor(application: Application) : this( + application = application, + filterFragmentLifecycleBreadcrumbs = FragmentLifecycleState.values().toSet(), + enableAutoFragmentLifecycleTracing = false + ) + + constructor( + application: Application, + enableFragmentLifecycleBreadcrumbs: Boolean, + enableAutoFragmentLifecycleTracing: Boolean + ) : this( + application = application, + filterFragmentLifecycleBreadcrumbs = FragmentLifecycleState.values().toSet() + .takeIf { enableFragmentLifecycleBreadcrumbs } + .orEmpty(), + enableAutoFragmentLifecycleTracing = enableAutoFragmentLifecycleTracing + ) private lateinit var hub: IHub private lateinit var options: SentryOptions @@ -46,7 +62,7 @@ class FragmentLifecycleIntegration( ?.registerFragmentLifecycleCallbacks( SentryFragmentLifecycleCallbacks( hub = hub, - enableFragmentLifecycleBreadcrumbs = enableFragmentLifecycleBreadcrumbs, + filterFragmentLifecycleBreadcrumbs = filterFragmentLifecycleBreadcrumbs, enableAutoFragmentLifecycleTracing = enableAutoFragmentLifecycleTracing ), true diff --git a/sentry-android-fragment/src/main/java/io/sentry/android/fragment/FragmentLifecycleState.kt b/sentry-android-fragment/src/main/java/io/sentry/android/fragment/FragmentLifecycleState.kt new file mode 100644 index 0000000000..cdc5ea999d --- /dev/null +++ b/sentry-android-fragment/src/main/java/io/sentry/android/fragment/FragmentLifecycleState.kt @@ -0,0 +1,15 @@ +package io.sentry.android.fragment + +enum class FragmentLifecycleState(internal val breadcrumbName: String) { + ATTACHED("attached"), + SAVE_INSTANCE_STATE("save instance state"), + CREATED("created"), + VIEW_CREATED("view created"), + STARTED("started"), + RESUMED("resumed"), + PAUSED("paused"), + STOPPED("stopped"), + VIEW_DESTROYED("view destroyed"), + DESTROYED("destroyed"), + DETACHED("detached") +} diff --git a/sentry-android-fragment/src/main/java/io/sentry/android/fragment/SentryFragmentLifecycleCallbacks.kt b/sentry-android-fragment/src/main/java/io/sentry/android/fragment/SentryFragmentLifecycleCallbacks.kt index dcc1b116a8..7cef61c085 100644 --- a/sentry-android-fragment/src/main/java/io/sentry/android/fragment/SentryFragmentLifecycleCallbacks.kt +++ b/sentry-android-fragment/src/main/java/io/sentry/android/fragment/SentryFragmentLifecycleCallbacks.kt @@ -19,16 +19,30 @@ import java.util.WeakHashMap @Suppress("TooManyFunctions") class SentryFragmentLifecycleCallbacks( private val hub: IHub = HubAdapter.getInstance(), - val enableFragmentLifecycleBreadcrumbs: Boolean, + val filterFragmentLifecycleBreadcrumbs: Set, val enableAutoFragmentLifecycleTracing: Boolean ) : FragmentLifecycleCallbacks() { + constructor( + hub: IHub, + enableFragmentLifecycleBreadcrumbs: Boolean, + enableAutoFragmentLifecycleTracing: Boolean + ) : this( + hub = hub, + filterFragmentLifecycleBreadcrumbs = FragmentLifecycleState.values().toSet() + .takeIf { enableFragmentLifecycleBreadcrumbs } + .orEmpty(), + enableAutoFragmentLifecycleTracing = enableAutoFragmentLifecycleTracing + ) + constructor( enableFragmentLifecycleBreadcrumbs: Boolean = true, enableAutoFragmentLifecycleTracing: Boolean = false ) : this( hub = HubAdapter.getInstance(), - enableFragmentLifecycleBreadcrumbs = enableFragmentLifecycleBreadcrumbs, + filterFragmentLifecycleBreadcrumbs = FragmentLifecycleState.values().toSet() + .takeIf { enableFragmentLifecycleBreadcrumbs } + .orEmpty(), enableAutoFragmentLifecycleTracing = enableAutoFragmentLifecycleTracing ) @@ -36,12 +50,15 @@ class SentryFragmentLifecycleCallbacks( private val fragmentsWithOngoingTransactions = WeakHashMap() + val enableFragmentLifecycleBreadcrumbs: Boolean + get() = filterFragmentLifecycleBreadcrumbs.isNotEmpty() + override fun onFragmentAttached( fragmentManager: FragmentManager, fragment: Fragment, context: Context ) { - addBreadcrumb(fragment, "attached") + addBreadcrumb(fragment, FragmentLifecycleState.ATTACHED) } override fun onFragmentSaveInstanceState( @@ -49,7 +66,7 @@ class SentryFragmentLifecycleCallbacks( fragment: Fragment, outState: Bundle ) { - addBreadcrumb(fragment, "save instance state") + addBreadcrumb(fragment, FragmentLifecycleState.SAVE_INSTANCE_STATE) } override fun onFragmentCreated( @@ -57,7 +74,7 @@ class SentryFragmentLifecycleCallbacks( fragment: Fragment, savedInstanceState: Bundle? ) { - addBreadcrumb(fragment, "created") + addBreadcrumb(fragment, FragmentLifecycleState.CREATED) // we only start the tracing for the fragment if the fragment has been added to its activity // and not only to the backstack @@ -72,48 +89,48 @@ class SentryFragmentLifecycleCallbacks( view: View, savedInstanceState: Bundle? ) { - addBreadcrumb(fragment, "view created") + addBreadcrumb(fragment, FragmentLifecycleState.VIEW_CREATED) } override fun onFragmentStarted(fragmentManager: FragmentManager, fragment: Fragment) { - addBreadcrumb(fragment, "started") + addBreadcrumb(fragment, FragmentLifecycleState.STARTED) } override fun onFragmentResumed(fragmentManager: FragmentManager, fragment: Fragment) { - addBreadcrumb(fragment, "resumed") + addBreadcrumb(fragment, FragmentLifecycleState.RESUMED) stopTracing(fragment) } override fun onFragmentPaused(fragmentManager: FragmentManager, fragment: Fragment) { - addBreadcrumb(fragment, "paused") + addBreadcrumb(fragment, FragmentLifecycleState.PAUSED) } override fun onFragmentStopped(fragmentManager: FragmentManager, fragment: Fragment) { - addBreadcrumb(fragment, "stopped") + addBreadcrumb(fragment, FragmentLifecycleState.STOPPED) } override fun onFragmentViewDestroyed(fragmentManager: FragmentManager, fragment: Fragment) { - addBreadcrumb(fragment, "view destroyed") + addBreadcrumb(fragment, FragmentLifecycleState.VIEW_DESTROYED) } override fun onFragmentDestroyed(fragmentManager: FragmentManager, fragment: Fragment) { - addBreadcrumb(fragment, "destroyed") + addBreadcrumb(fragment, FragmentLifecycleState.DESTROYED) stopTracing(fragment) } override fun onFragmentDetached(fragmentManager: FragmentManager, fragment: Fragment) { - addBreadcrumb(fragment, "detached") + addBreadcrumb(fragment, FragmentLifecycleState.DETACHED) } - private fun addBreadcrumb(fragment: Fragment, state: String) { - if (!enableFragmentLifecycleBreadcrumbs) { + private fun addBreadcrumb(fragment: Fragment, state: FragmentLifecycleState) { + if (!filterFragmentLifecycleBreadcrumbs.contains(state)) { return } val breadcrumb = Breadcrumb().apply { type = "navigation" - setData("state", state) + setData("state", state.breadcrumbName) setData("screen", getFragmentName(fragment)) category = "ui.fragment.lifecycle" level = INFO diff --git a/sentry-android-fragment/src/test/java/io/sentry/android/fragment/FragmentLifecycleIntegrationTest.kt b/sentry-android-fragment/src/test/java/io/sentry/android/fragment/FragmentLifecycleIntegrationTest.kt index 889fc424a3..5b8f3a9a26 100644 --- a/sentry-android-fragment/src/test/java/io/sentry/android/fragment/FragmentLifecycleIntegrationTest.kt +++ b/sentry-android-fragment/src/test/java/io/sentry/android/fragment/FragmentLifecycleIntegrationTest.kt @@ -13,7 +13,7 @@ import com.nhaarman.mockitokotlin2.whenever import io.sentry.IHub import io.sentry.SentryOptions import kotlin.test.Test -import kotlin.test.assertFalse +import kotlin.test.assertEquals import kotlin.test.assertTrue class FragmentLifecycleIntegrationTest { @@ -91,7 +91,7 @@ class FragmentLifecycleIntegrationTest { check { fragmentCallbacks -> val callback = (fragmentCallbacks as SentryFragmentLifecycleCallbacks) assertTrue(callback.enableAutoFragmentLifecycleTracing) - assertFalse(callback.enableFragmentLifecycleBreadcrumbs) + assertEquals(emptySet(), callback.filterFragmentLifecycleBreadcrumbs) }, eq(true) ) diff --git a/sentry-android-fragment/src/test/java/io/sentry/android/fragment/SentryFragmentLifecycleCallbacksTest.kt b/sentry-android-fragment/src/test/java/io/sentry/android/fragment/SentryFragmentLifecycleCallbacksTest.kt index 6ece174100..86fc94334d 100644 --- a/sentry-android-fragment/src/test/java/io/sentry/android/fragment/SentryFragmentLifecycleCallbacksTest.kt +++ b/sentry-android-fragment/src/test/java/io/sentry/android/fragment/SentryFragmentLifecycleCallbacksTest.kt @@ -9,6 +9,7 @@ import com.nhaarman.mockitokotlin2.anyOrNull import com.nhaarman.mockitokotlin2.check import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.never +import com.nhaarman.mockitokotlin2.times import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.whenever import io.sentry.Breadcrumb @@ -23,6 +24,7 @@ import io.sentry.SpanStatus import kotlin.test.Test import kotlin.test.assertEquals +@Suppress("SameParameterValue") class SentryFragmentLifecycleCallbacksTest { private class Fixture { @@ -35,7 +37,7 @@ class SentryFragmentLifecycleCallbacksTest { val span = mock() fun getSut( - enableFragmentLifecycleBreadcrumbs: Boolean = true, + loggedFragmentLifecycleStates: Set = FragmentLifecycleState.values().toSet(), enableAutoFragmentLifecycleTracing: Boolean = false, tracesSampleRate: Double? = 1.0, isAdded: Boolean = true @@ -53,7 +55,7 @@ class SentryFragmentLifecycleCallbacksTest { whenever(fragment.isAdded).thenReturn(isAdded) return SentryFragmentLifecycleCallbacks( hub = hub, - enableFragmentLifecycleBreadcrumbs = enableFragmentLifecycleBreadcrumbs, + filterFragmentLifecycleBreadcrumbs = loggedFragmentLifecycleStates, enableAutoFragmentLifecycleTracing = enableAutoFragmentLifecycleTracing ) } @@ -71,12 +73,14 @@ class SentryFragmentLifecycleCallbacksTest { } @Test - fun `When fragment is attached with disabled breadcrumbs, it should not add breadcrumb`() { - val sut = fixture.getSut(enableFragmentLifecycleBreadcrumbs = false) + fun `When fragment is attached with subset of logged breadcrumbs, it should add only those breadcrumbs`() { + val sut = fixture.getSut(loggedFragmentLifecycleStates = setOf(FragmentLifecycleState.CREATED)) + sut.onFragmentCreated(fixture.fragmentManager, fixture.fragment, savedInstanceState = null) sut.onFragmentAttached(fixture.fragmentManager, fixture.fragment, fixture.context) - verify(fixture.hub, never()).addBreadcrumb(any()) + verifyBreadcrumbAddedCount(1) + verifyBreadcrumbAdded("created") } @Test @@ -273,4 +277,8 @@ class SentryFragmentLifecycleCallbacksTest { anyOrNull() ) } + + private fun verifyBreadcrumbAddedCount(count: Int) { + verify(fixture.hub, times(count)).addBreadcrumb(any(), anyOrNull()) + } }