Skip to content

Commit b7fb83c

Browse files
ILikeYourHatromtsn
andauthored
Fix: Make lifecycle breadcrumbs logged in fragments customizable (#1734) (#2299)
Co-authored-by: Roman Zavarnitsyn <rom4ek93@gmail.com>
1 parent b516cf3 commit b7fb83c

File tree

7 files changed

+104
-27
lines changed

7 files changed

+104
-27
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
## Unreleased
44

55
### Features
6+
7+
- Customizable fragment lifecycle breadcrumbs ([#2299](https://github.com/getsentry/sentry-java/pull/2299))
68
- Provide hook for Jetpack Compose navigation instrumentation ([#2320](https://github.com/getsentry/sentry-java/pull/2320))
79

810
## 6.6.0

sentry-android-fragment/api/sentry-android-fragment.api

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ public final class io/sentry/android/fragment/BuildConfig {
88

99
public final class io/sentry/android/fragment/FragmentLifecycleIntegration : android/app/Application$ActivityLifecycleCallbacks, io/sentry/Integration, java/io/Closeable {
1010
public fun <init> (Landroid/app/Application;)V
11+
public fun <init> (Landroid/app/Application;Ljava/util/Set;Z)V
1112
public fun <init> (Landroid/app/Application;ZZ)V
1213
public fun close ()V
1314
public fun onActivityCreated (Landroid/app/Activity;Landroid/os/Bundle;)V
@@ -20,15 +21,33 @@ public final class io/sentry/android/fragment/FragmentLifecycleIntegration : and
2021
public fun register (Lio/sentry/IHub;Lio/sentry/SentryOptions;)V
2122
}
2223

24+
public final class io/sentry/android/fragment/FragmentLifecycleState : java/lang/Enum {
25+
public static final field ATTACHED Lio/sentry/android/fragment/FragmentLifecycleState;
26+
public static final field CREATED Lio/sentry/android/fragment/FragmentLifecycleState;
27+
public static final field DESTROYED Lio/sentry/android/fragment/FragmentLifecycleState;
28+
public static final field DETACHED Lio/sentry/android/fragment/FragmentLifecycleState;
29+
public static final field PAUSED Lio/sentry/android/fragment/FragmentLifecycleState;
30+
public static final field RESUMED Lio/sentry/android/fragment/FragmentLifecycleState;
31+
public static final field SAVE_INSTANCE_STATE Lio/sentry/android/fragment/FragmentLifecycleState;
32+
public static final field STARTED Lio/sentry/android/fragment/FragmentLifecycleState;
33+
public static final field STOPPED Lio/sentry/android/fragment/FragmentLifecycleState;
34+
public static final field VIEW_CREATED Lio/sentry/android/fragment/FragmentLifecycleState;
35+
public static final field VIEW_DESTROYED Lio/sentry/android/fragment/FragmentLifecycleState;
36+
public static fun valueOf (Ljava/lang/String;)Lio/sentry/android/fragment/FragmentLifecycleState;
37+
public static fun values ()[Lio/sentry/android/fragment/FragmentLifecycleState;
38+
}
39+
2340
public final class io/sentry/android/fragment/SentryFragmentLifecycleCallbacks : androidx/fragment/app/FragmentManager$FragmentLifecycleCallbacks {
2441
public static final field Companion Lio/sentry/android/fragment/SentryFragmentLifecycleCallbacks$Companion;
2542
public static final field FRAGMENT_LOAD_OP Ljava/lang/String;
43+
public fun <init> (Lio/sentry/IHub;Ljava/util/Set;Z)V
44+
public synthetic fun <init> (Lio/sentry/IHub;Ljava/util/Set;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
2645
public fun <init> (Lio/sentry/IHub;ZZ)V
27-
public synthetic fun <init> (Lio/sentry/IHub;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
2846
public fun <init> (ZZ)V
2947
public synthetic fun <init> (ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
3048
public final fun getEnableAutoFragmentLifecycleTracing ()Z
3149
public final fun getEnableFragmentLifecycleBreadcrumbs ()Z
50+
public final fun getFilterFragmentLifecycleBreadcrumbs ()Ljava/util/Set;
3251
public fun onFragmentAttached (Landroidx/fragment/app/FragmentManager;Landroidx/fragment/app/Fragment;Landroid/content/Context;)V
3352
public fun onFragmentCreated (Landroidx/fragment/app/FragmentManager;Landroidx/fragment/app/Fragment;Landroid/os/Bundle;)V
3453
public fun onFragmentDestroyed (Landroidx/fragment/app/FragmentManager;Landroidx/fragment/app/Fragment;)V

sentry-android-fragment/src/main/java/io/sentry/android/fragment/FragmentLifecycleIntegration.kt

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,30 @@ import java.io.Closeable
1313

1414
class FragmentLifecycleIntegration(
1515
private val application: Application,
16-
private val enableFragmentLifecycleBreadcrumbs: Boolean,
16+
private val filterFragmentLifecycleBreadcrumbs: Set<FragmentLifecycleState>,
1717
private val enableAutoFragmentLifecycleTracing: Boolean
1818
) :
1919
ActivityLifecycleCallbacks,
2020
Integration,
2121
Closeable {
2222

23-
constructor(application: Application) : this(application, true, false)
23+
constructor(application: Application) : this(
24+
application = application,
25+
filterFragmentLifecycleBreadcrumbs = FragmentLifecycleState.values().toSet(),
26+
enableAutoFragmentLifecycleTracing = false
27+
)
28+
29+
constructor(
30+
application: Application,
31+
enableFragmentLifecycleBreadcrumbs: Boolean,
32+
enableAutoFragmentLifecycleTracing: Boolean
33+
) : this(
34+
application = application,
35+
filterFragmentLifecycleBreadcrumbs = FragmentLifecycleState.values().toSet()
36+
.takeIf { enableFragmentLifecycleBreadcrumbs }
37+
.orEmpty(),
38+
enableAutoFragmentLifecycleTracing = enableAutoFragmentLifecycleTracing
39+
)
2440

2541
private lateinit var hub: IHub
2642
private lateinit var options: SentryOptions
@@ -46,7 +62,7 @@ class FragmentLifecycleIntegration(
4662
?.registerFragmentLifecycleCallbacks(
4763
SentryFragmentLifecycleCallbacks(
4864
hub = hub,
49-
enableFragmentLifecycleBreadcrumbs = enableFragmentLifecycleBreadcrumbs,
65+
filterFragmentLifecycleBreadcrumbs = filterFragmentLifecycleBreadcrumbs,
5066
enableAutoFragmentLifecycleTracing = enableAutoFragmentLifecycleTracing
5167
),
5268
true
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.sentry.android.fragment
2+
3+
enum class FragmentLifecycleState(internal val breadcrumbName: String) {
4+
ATTACHED("attached"),
5+
SAVE_INSTANCE_STATE("save instance state"),
6+
CREATED("created"),
7+
VIEW_CREATED("view created"),
8+
STARTED("started"),
9+
RESUMED("resumed"),
10+
PAUSED("paused"),
11+
STOPPED("stopped"),
12+
VIEW_DESTROYED("view destroyed"),
13+
DESTROYED("destroyed"),
14+
DETACHED("detached")
15+
}

sentry-android-fragment/src/main/java/io/sentry/android/fragment/SentryFragmentLifecycleCallbacks.kt

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,45 +19,62 @@ import java.util.WeakHashMap
1919
@Suppress("TooManyFunctions")
2020
class SentryFragmentLifecycleCallbacks(
2121
private val hub: IHub = HubAdapter.getInstance(),
22-
val enableFragmentLifecycleBreadcrumbs: Boolean,
22+
val filterFragmentLifecycleBreadcrumbs: Set<FragmentLifecycleState>,
2323
val enableAutoFragmentLifecycleTracing: Boolean
2424
) : FragmentLifecycleCallbacks() {
2525

26+
constructor(
27+
hub: IHub,
28+
enableFragmentLifecycleBreadcrumbs: Boolean,
29+
enableAutoFragmentLifecycleTracing: Boolean
30+
) : this(
31+
hub = hub,
32+
filterFragmentLifecycleBreadcrumbs = FragmentLifecycleState.values().toSet()
33+
.takeIf { enableFragmentLifecycleBreadcrumbs }
34+
.orEmpty(),
35+
enableAutoFragmentLifecycleTracing = enableAutoFragmentLifecycleTracing
36+
)
37+
2638
constructor(
2739
enableFragmentLifecycleBreadcrumbs: Boolean = true,
2840
enableAutoFragmentLifecycleTracing: Boolean = false
2941
) : this(
3042
hub = HubAdapter.getInstance(),
31-
enableFragmentLifecycleBreadcrumbs = enableFragmentLifecycleBreadcrumbs,
43+
filterFragmentLifecycleBreadcrumbs = FragmentLifecycleState.values().toSet()
44+
.takeIf { enableFragmentLifecycleBreadcrumbs }
45+
.orEmpty(),
3246
enableAutoFragmentLifecycleTracing = enableAutoFragmentLifecycleTracing
3347
)
3448

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

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

53+
val enableFragmentLifecycleBreadcrumbs: Boolean
54+
get() = filterFragmentLifecycleBreadcrumbs.isNotEmpty()
55+
3956
override fun onFragmentAttached(
4057
fragmentManager: FragmentManager,
4158
fragment: Fragment,
4259
context: Context
4360
) {
44-
addBreadcrumb(fragment, "attached")
61+
addBreadcrumb(fragment, FragmentLifecycleState.ATTACHED)
4562
}
4663

4764
override fun onFragmentSaveInstanceState(
4865
fragmentManager: FragmentManager,
4966
fragment: Fragment,
5067
outState: Bundle
5168
) {
52-
addBreadcrumb(fragment, "save instance state")
69+
addBreadcrumb(fragment, FragmentLifecycleState.SAVE_INSTANCE_STATE)
5370
}
5471

5572
override fun onFragmentCreated(
5673
fragmentManager: FragmentManager,
5774
fragment: Fragment,
5875
savedInstanceState: Bundle?
5976
) {
60-
addBreadcrumb(fragment, "created")
77+
addBreadcrumb(fragment, FragmentLifecycleState.CREATED)
6178

6279
// we only start the tracing for the fragment if the fragment has been added to its activity
6380
// and not only to the backstack
@@ -72,48 +89,48 @@ class SentryFragmentLifecycleCallbacks(
7289
view: View,
7390
savedInstanceState: Bundle?
7491
) {
75-
addBreadcrumb(fragment, "view created")
92+
addBreadcrumb(fragment, FragmentLifecycleState.VIEW_CREATED)
7693
}
7794

7895
override fun onFragmentStarted(fragmentManager: FragmentManager, fragment: Fragment) {
79-
addBreadcrumb(fragment, "started")
96+
addBreadcrumb(fragment, FragmentLifecycleState.STARTED)
8097
}
8198

8299
override fun onFragmentResumed(fragmentManager: FragmentManager, fragment: Fragment) {
83-
addBreadcrumb(fragment, "resumed")
100+
addBreadcrumb(fragment, FragmentLifecycleState.RESUMED)
84101

85102
stopTracing(fragment)
86103
}
87104

88105
override fun onFragmentPaused(fragmentManager: FragmentManager, fragment: Fragment) {
89-
addBreadcrumb(fragment, "paused")
106+
addBreadcrumb(fragment, FragmentLifecycleState.PAUSED)
90107
}
91108

92109
override fun onFragmentStopped(fragmentManager: FragmentManager, fragment: Fragment) {
93-
addBreadcrumb(fragment, "stopped")
110+
addBreadcrumb(fragment, FragmentLifecycleState.STOPPED)
94111
}
95112

96113
override fun onFragmentViewDestroyed(fragmentManager: FragmentManager, fragment: Fragment) {
97-
addBreadcrumb(fragment, "view destroyed")
114+
addBreadcrumb(fragment, FragmentLifecycleState.VIEW_DESTROYED)
98115
}
99116

100117
override fun onFragmentDestroyed(fragmentManager: FragmentManager, fragment: Fragment) {
101-
addBreadcrumb(fragment, "destroyed")
118+
addBreadcrumb(fragment, FragmentLifecycleState.DESTROYED)
102119

103120
stopTracing(fragment)
104121
}
105122

106123
override fun onFragmentDetached(fragmentManager: FragmentManager, fragment: Fragment) {
107-
addBreadcrumb(fragment, "detached")
124+
addBreadcrumb(fragment, FragmentLifecycleState.DETACHED)
108125
}
109126

110-
private fun addBreadcrumb(fragment: Fragment, state: String) {
111-
if (!enableFragmentLifecycleBreadcrumbs) {
127+
private fun addBreadcrumb(fragment: Fragment, state: FragmentLifecycleState) {
128+
if (!filterFragmentLifecycleBreadcrumbs.contains(state)) {
112129
return
113130
}
114131
val breadcrumb = Breadcrumb().apply {
115132
type = "navigation"
116-
setData("state", state)
133+
setData("state", state.breadcrumbName)
117134
setData("screen", getFragmentName(fragment))
118135
category = "ui.fragment.lifecycle"
119136
level = INFO

sentry-android-fragment/src/test/java/io/sentry/android/fragment/FragmentLifecycleIntegrationTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import com.nhaarman.mockitokotlin2.whenever
1313
import io.sentry.IHub
1414
import io.sentry.SentryOptions
1515
import kotlin.test.Test
16-
import kotlin.test.assertFalse
16+
import kotlin.test.assertEquals
1717
import kotlin.test.assertTrue
1818

1919
class FragmentLifecycleIntegrationTest {
@@ -91,7 +91,7 @@ class FragmentLifecycleIntegrationTest {
9191
check { fragmentCallbacks ->
9292
val callback = (fragmentCallbacks as SentryFragmentLifecycleCallbacks)
9393
assertTrue(callback.enableAutoFragmentLifecycleTracing)
94-
assertFalse(callback.enableFragmentLifecycleBreadcrumbs)
94+
assertEquals(emptySet(), callback.filterFragmentLifecycleBreadcrumbs)
9595
},
9696
eq(true)
9797
)

sentry-android-fragment/src/test/java/io/sentry/android/fragment/SentryFragmentLifecycleCallbacksTest.kt

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.nhaarman.mockitokotlin2.anyOrNull
99
import com.nhaarman.mockitokotlin2.check
1010
import com.nhaarman.mockitokotlin2.mock
1111
import com.nhaarman.mockitokotlin2.never
12+
import com.nhaarman.mockitokotlin2.times
1213
import com.nhaarman.mockitokotlin2.verify
1314
import com.nhaarman.mockitokotlin2.whenever
1415
import io.sentry.Breadcrumb
@@ -23,6 +24,7 @@ import io.sentry.SpanStatus
2324
import kotlin.test.Test
2425
import kotlin.test.assertEquals
2526

27+
@Suppress("SameParameterValue")
2628
class SentryFragmentLifecycleCallbacksTest {
2729

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

3739
fun getSut(
38-
enableFragmentLifecycleBreadcrumbs: Boolean = true,
40+
loggedFragmentLifecycleStates: Set<FragmentLifecycleState> = FragmentLifecycleState.values().toSet(),
3941
enableAutoFragmentLifecycleTracing: Boolean = false,
4042
tracesSampleRate: Double? = 1.0,
4143
isAdded: Boolean = true
@@ -53,7 +55,7 @@ class SentryFragmentLifecycleCallbacksTest {
5355
whenever(fragment.isAdded).thenReturn(isAdded)
5456
return SentryFragmentLifecycleCallbacks(
5557
hub = hub,
56-
enableFragmentLifecycleBreadcrumbs = enableFragmentLifecycleBreadcrumbs,
58+
filterFragmentLifecycleBreadcrumbs = loggedFragmentLifecycleStates,
5759
enableAutoFragmentLifecycleTracing = enableAutoFragmentLifecycleTracing
5860
)
5961
}
@@ -71,12 +73,14 @@ class SentryFragmentLifecycleCallbacksTest {
7173
}
7274

7375
@Test
74-
fun `When fragment is attached with disabled breadcrumbs, it should not add breadcrumb`() {
75-
val sut = fixture.getSut(enableFragmentLifecycleBreadcrumbs = false)
76+
fun `When fragment is attached with subset of logged breadcrumbs, it should add only those breadcrumbs`() {
77+
val sut = fixture.getSut(loggedFragmentLifecycleStates = setOf(FragmentLifecycleState.CREATED))
7678

79+
sut.onFragmentCreated(fixture.fragmentManager, fixture.fragment, savedInstanceState = null)
7780
sut.onFragmentAttached(fixture.fragmentManager, fixture.fragment, fixture.context)
7881

79-
verify(fixture.hub, never()).addBreadcrumb(any<Breadcrumb>())
82+
verifyBreadcrumbAddedCount(1)
83+
verifyBreadcrumbAdded("created")
8084
}
8185

8286
@Test
@@ -273,4 +277,8 @@ class SentryFragmentLifecycleCallbacksTest {
273277
anyOrNull()
274278
)
275279
}
280+
281+
private fun verifyBreadcrumbAddedCount(count: Int) {
282+
verify(fixture.hub, times(count)).addBreadcrumb(any<Breadcrumb>(), anyOrNull())
283+
}
276284
}

0 commit comments

Comments
 (0)