Skip to content

Commit

Permalink
Fix: Make lifecycle breadcrumbs logged in fragments customizable (#1734
Browse files Browse the repository at this point in the history
…) (#2299)

Co-authored-by: Roman Zavarnitsyn <rom4ek93@gmail.com>
  • Loading branch information
ILikeYourHat and romtsn committed Oct 31, 2022
1 parent b516cf3 commit b7fb83c
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 27 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -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
Expand Down
21 changes: 20 additions & 1 deletion sentry-android-fragment/api/sentry-android-fragment.api
Expand Up @@ -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 <init> (Landroid/app/Application;)V
public fun <init> (Landroid/app/Application;Ljava/util/Set;Z)V
public fun <init> (Landroid/app/Application;ZZ)V
public fun close ()V
public fun onActivityCreated (Landroid/app/Activity;Landroid/os/Bundle;)V
Expand All @@ -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 <init> (Lio/sentry/IHub;Ljava/util/Set;Z)V
public synthetic fun <init> (Lio/sentry/IHub;Ljava/util/Set;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Lio/sentry/IHub;ZZ)V
public synthetic fun <init> (Lio/sentry/IHub;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (ZZ)V
public synthetic fun <init> (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
Expand Down
Expand Up @@ -13,14 +13,30 @@ import java.io.Closeable

class FragmentLifecycleIntegration(
private val application: Application,
private val enableFragmentLifecycleBreadcrumbs: Boolean,
private val filterFragmentLifecycleBreadcrumbs: Set<FragmentLifecycleState>,
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
Expand All @@ -46,7 +62,7 @@ class FragmentLifecycleIntegration(
?.registerFragmentLifecycleCallbacks(
SentryFragmentLifecycleCallbacks(
hub = hub,
enableFragmentLifecycleBreadcrumbs = enableFragmentLifecycleBreadcrumbs,
filterFragmentLifecycleBreadcrumbs = filterFragmentLifecycleBreadcrumbs,
enableAutoFragmentLifecycleTracing = enableAutoFragmentLifecycleTracing
),
true
Expand Down
@@ -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")
}
Expand Up @@ -19,45 +19,62 @@ import java.util.WeakHashMap
@Suppress("TooManyFunctions")
class SentryFragmentLifecycleCallbacks(
private val hub: IHub = HubAdapter.getInstance(),
val enableFragmentLifecycleBreadcrumbs: Boolean,
val filterFragmentLifecycleBreadcrumbs: Set<FragmentLifecycleState>,
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
)

private val isPerformanceEnabled get() = hub.options.isTracingEnabled && enableAutoFragmentLifecycleTracing

private val fragmentsWithOngoingTransactions = WeakHashMap<Fragment, ISpan>()

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(
fragmentManager: FragmentManager,
fragment: Fragment,
outState: Bundle
) {
addBreadcrumb(fragment, "save instance state")
addBreadcrumb(fragment, FragmentLifecycleState.SAVE_INSTANCE_STATE)
}

override fun onFragmentCreated(
fragmentManager: FragmentManager,
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
Expand All @@ -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
Expand Down
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
)
Expand Down
Expand Up @@ -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
Expand All @@ -23,6 +24,7 @@ import io.sentry.SpanStatus
import kotlin.test.Test
import kotlin.test.assertEquals

@Suppress("SameParameterValue")
class SentryFragmentLifecycleCallbacksTest {

private class Fixture {
Expand All @@ -35,7 +37,7 @@ class SentryFragmentLifecycleCallbacksTest {
val span = mock<ISpan>()

fun getSut(
enableFragmentLifecycleBreadcrumbs: Boolean = true,
loggedFragmentLifecycleStates: Set<FragmentLifecycleState> = FragmentLifecycleState.values().toSet(),
enableAutoFragmentLifecycleTracing: Boolean = false,
tracesSampleRate: Double? = 1.0,
isAdded: Boolean = true
Expand All @@ -53,7 +55,7 @@ class SentryFragmentLifecycleCallbacksTest {
whenever(fragment.isAdded).thenReturn(isAdded)
return SentryFragmentLifecycleCallbacks(
hub = hub,
enableFragmentLifecycleBreadcrumbs = enableFragmentLifecycleBreadcrumbs,
filterFragmentLifecycleBreadcrumbs = loggedFragmentLifecycleStates,
enableAutoFragmentLifecycleTracing = enableAutoFragmentLifecycleTracing
)
}
Expand All @@ -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<Breadcrumb>())
verifyBreadcrumbAddedCount(1)
verifyBreadcrumbAdded("created")
}

@Test
Expand Down Expand Up @@ -273,4 +277,8 @@ class SentryFragmentLifecycleCallbacksTest {
anyOrNull()
)
}

private fun verifyBreadcrumbAddedCount(count: Int) {
verify(fixture.hub, times(count)).addBreadcrumb(any<Breadcrumb>(), anyOrNull())
}
}

0 comments on commit b7fb83c

Please sign in to comment.