Skip to content

Commit dc806fd

Browse files
fix(android): Finish app start after activity spans
Keep standalone app-start transactions open until activity lifecycle spans are attached so early app-start completion does not drop activity spans. Co-Authored-By: Cursor <cursoragent@cursor.com>
1 parent 0497277 commit dc806fd

2 files changed

Lines changed: 34 additions & 3 deletions

File tree

sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -334,9 +334,7 @@ && isWithinAppStartContinuationWindow(ttidStartTime)) {
334334
setSpanOrigin(spanOptions);
335335

336336
if (isAppStart) {
337-
if (createStandaloneAppStart) {
338-
finishAppStartSpan();
339-
} else if (!options.isEnableStandaloneAppStartTracing()) {
337+
if (!createStandaloneAppStart && !options.isEnableStandaloneAppStartTracing()) {
340338
appStartSpan =
341339
transaction.startChild(
342340
getAppStartOp(coldStart),
@@ -618,6 +616,7 @@ public void onActivityPostStarted(final @NotNull Activity activity) {
618616
// Needed to handle hybrid SDKs
619617
helper.saveSpanToAppStartMetrics();
620618
}
619+
finishAppStartSpan();
621620
}
622621

623622
@Override

sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,6 +1160,38 @@ class ActivityLifecycleIntegrationTest {
11601160
)
11611161
}
11621162

1163+
@Test
1164+
fun `launcher activity attaches lifecycle spans before finishing stopped standalone App Start`() {
1165+
val sut =
1166+
fixture.getSut {
1167+
it.tracesSampleRate = 1.0
1168+
it.isEnableStandaloneAppStartTracing = true
1169+
}
1170+
sut.register(fixture.scopes, fixture.options)
1171+
val appStartEndDate = SentryNanotimeDate(Date(499), 0)
1172+
setAppStartTime(SentryNanotimeDate(Date(1), 0), appStartEndDate)
1173+
1174+
val activity = mock<Activity>()
1175+
sut.onActivityPreCreated(activity, fixture.bundle)
1176+
sut.onActivityCreated(activity, fixture.bundle)
1177+
1178+
val appStartIndex =
1179+
transactionIndexForOperation(ActivityLifecycleIntegration.STANDALONE_APP_START_OP)
1180+
val appStartTransaction = fixture.createdTransactions[appStartIndex]
1181+
assertFalse(appStartTransaction.isFinished)
1182+
1183+
sut.onActivityPostCreated(activity, fixture.bundle)
1184+
sut.onActivityPreStarted(activity)
1185+
sut.onActivityStarted(activity)
1186+
sut.onActivityPostStarted(activity)
1187+
1188+
val activityLoadSpans = appStartTransaction.children.filter { it.operation == "activity.load" }
1189+
assertEquals(2, activityLoadSpans.size)
1190+
assertTrue(activityLoadSpans.all { it.isFinished })
1191+
assertTrue(appStartTransaction.isFinished)
1192+
assertEquals(appStartEndDate.nanoTimestamp(), appStartTransaction.finishDate!!.nanoTimestamp())
1193+
}
1194+
11631195
@Test
11641196
fun `activity following a headless start reuses trace id and does not emit second standalone`() {
11651197
val storedTraceId = SentryId()

0 commit comments

Comments
 (0)