Skip to content

Commit

Permalink
Fix: Make lifecycle breadcrumbs logged in fragments customizable (get…
Browse files Browse the repository at this point in the history
  • Loading branch information
ILikeYourHat committed Oct 14, 2022
1 parent adee765 commit ca02d92
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 60 deletions.
38 changes: 15 additions & 23 deletions sentry-android-fragment/api/sentry-android-fragment.api
Original file line number Diff line number Diff line change
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,28 +21,19 @@ 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/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;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 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
public fun onFragmentDetached (Landroidx/fragment/app/FragmentManager;Landroidx/fragment/app/Fragment;)V
public fun onFragmentPaused (Landroidx/fragment/app/FragmentManager;Landroidx/fragment/app/Fragment;)V
public fun onFragmentResumed (Landroidx/fragment/app/FragmentManager;Landroidx/fragment/app/Fragment;)V
public fun onFragmentSaveInstanceState (Landroidx/fragment/app/FragmentManager;Landroidx/fragment/app/Fragment;Landroid/os/Bundle;)V
public fun onFragmentStarted (Landroidx/fragment/app/FragmentManager;Landroidx/fragment/app/Fragment;)V
public fun onFragmentStopped (Landroidx/fragment/app/FragmentManager;Landroidx/fragment/app/Fragment;)V
public fun onFragmentViewCreated (Landroidx/fragment/app/FragmentManager;Landroidx/fragment/app/Fragment;Landroid/view/View;Landroid/os/Bundle;)V
public fun onFragmentViewDestroyed (Landroidx/fragment/app/FragmentManager;Landroidx/fragment/app/Fragment;)V
}

public final class io/sentry/android/fragment/SentryFragmentLifecycleCallbacks$Companion {
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;
}

Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,35 @@ import io.sentry.IHub
import io.sentry.Integration
import io.sentry.SentryLevel.DEBUG
import io.sentry.SentryOptions
import io.sentry.android.fragment.internal.SentryFragmentLifecycleCallbacks
import java.io.Closeable

class FragmentLifecycleIntegration(
private val application: Application,
private val enableFragmentLifecycleBreadcrumbs: Boolean,
private val loggedFragmentLifecycleStates: Set<FragmentLifecycleState>,
private val enableAutoFragmentLifecycleTracing: Boolean
) :
ActivityLifecycleCallbacks,
Integration,
Closeable {

constructor(application: Application) : this(application, true, false)
constructor(application: Application) : this(
application = application,
loggedFragmentLifecycleStates = FragmentLifecycleState.values().toSet(),
enableAutoFragmentLifecycleTracing = false
)

constructor(
application: Application,
enableFragmentLifecycleBreadcrumbs: Boolean,
enableAutoFragmentLifecycleTracing: Boolean
) : this(
application = application,
loggedFragmentLifecycleStates = FragmentLifecycleState.values().toSet()
.takeIf { enableFragmentLifecycleBreadcrumbs }
.orEmpty(),
enableAutoFragmentLifecycleTracing = enableAutoFragmentLifecycleTracing
)

private lateinit var hub: IHub
private lateinit var options: SentryOptions
Expand All @@ -46,7 +63,7 @@ class FragmentLifecycleIntegration(
?.registerFragmentLifecycleCallbacks(
SentryFragmentLifecycleCallbacks(
hub = hub,
enableFragmentLifecycleBreadcrumbs = enableFragmentLifecycleBreadcrumbs,
loggedFragmentLifecycleStates = loggedFragmentLifecycleStates,
enableAutoFragmentLifecycleTracing = enableAutoFragmentLifecycleTracing
),
true
Expand Down
Original file line number Diff line number Diff line change
@@ -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")
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.sentry.android.fragment
package io.sentry.android.fragment.internal

import android.content.Context
import android.os.Bundle
Expand All @@ -14,24 +14,16 @@ import io.sentry.ISpan
import io.sentry.SentryLevel.INFO
import io.sentry.SpanStatus
import io.sentry.TypeCheckHint.ANDROID_FRAGMENT
import io.sentry.android.fragment.FragmentLifecycleState
import java.util.WeakHashMap

@Suppress("TooManyFunctions")
class SentryFragmentLifecycleCallbacks(
internal class SentryFragmentLifecycleCallbacks(
private val hub: IHub = HubAdapter.getInstance(),
val enableFragmentLifecycleBreadcrumbs: Boolean,
val loggedFragmentLifecycleStates: Set<FragmentLifecycleState>,
val enableAutoFragmentLifecycleTracing: Boolean
) : FragmentLifecycleCallbacks() {

constructor(
enableFragmentLifecycleBreadcrumbs: Boolean = true,
enableAutoFragmentLifecycleTracing: Boolean = false
) : this(
hub = HubAdapter.getInstance(),
enableFragmentLifecycleBreadcrumbs = enableFragmentLifecycleBreadcrumbs,
enableAutoFragmentLifecycleTracing = enableAutoFragmentLifecycleTracing
)

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

private val fragmentsWithOngoingTransactions = WeakHashMap<Fragment, ISpan>()
Expand All @@ -41,23 +33,23 @@ class SentryFragmentLifecycleCallbacks(
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 +64,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 (!loggedFragmentLifecycleStates.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
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import io.sentry.IHub
import io.sentry.SentryOptions
import io.sentry.android.fragment.internal.SentryFragmentLifecycleCallbacks
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class FragmentLifecycleIntegrationTest {
Expand Down Expand Up @@ -91,7 +92,7 @@ class FragmentLifecycleIntegrationTest {
check { fragmentCallbacks ->
val callback = (fragmentCallbacks as SentryFragmentLifecycleCallbacks)
assertTrue(callback.enableAutoFragmentLifecycleTracing)
assertFalse(callback.enableFragmentLifecycleBreadcrumbs)
assertEquals(emptySet(), callback.loggedFragmentLifecycleStates)
},
eq(true)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.sentry.android.fragment
package io.sentry.android.fragment.internal

import android.content.Context
import android.os.Bundle
Expand All @@ -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 @@ -20,9 +21,11 @@ import io.sentry.ScopeCallback
import io.sentry.SentryLevel.INFO
import io.sentry.SentryOptions
import io.sentry.SpanStatus
import io.sentry.android.fragment.FragmentLifecycleState
import kotlin.test.Test
import kotlin.test.assertEquals

@Suppress("SameParameterValue")
class SentryFragmentLifecycleCallbacksTest {

private class Fixture {
Expand All @@ -35,7 +38,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 +56,7 @@ class SentryFragmentLifecycleCallbacksTest {
whenever(fragment.isAdded).thenReturn(isAdded)
return SentryFragmentLifecycleCallbacks(
hub = hub,
enableFragmentLifecycleBreadcrumbs = enableFragmentLifecycleBreadcrumbs,
loggedFragmentLifecycleStates = loggedFragmentLifecycleStates,
enableAutoFragmentLifecycleTracing = enableAutoFragmentLifecycleTracing
)
}
Expand All @@ -71,12 +74,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 +278,8 @@ class SentryFragmentLifecycleCallbacksTest {
anyOrNull()
)
}

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

0 comments on commit ca02d92

Please sign in to comment.