Skip to content

Commit 283f024

Browse files
authored
Add and populate appQualitySessionId in Crashlytics reports (#5045)
* Add and populate appQualitySessionId in Crashlytics reports * Bump Crashlytics ktx target sdk to 33, same as Java sdk * Log warning when missing AQS session id
1 parent baa3d13 commit 283f024

File tree

11 files changed

+269
-52
lines changed

11 files changed

+269
-52
lines changed

firebase-crashlytics/ktx/ktx.gradle

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ firebaseLibrary {
2525
}
2626

2727
android {
28-
compileSdkVersion project.targetSdkVersion
28+
compileSdkVersion 33
2929
defaultConfig {
30-
minSdkVersion 16
30+
minSdk 16
3131
multiDexEnabled true
32-
targetSdkVersion project.targetSdkVersion
32+
targetSdk 33
3333
versionName version
3434
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
3535
}
@@ -69,4 +69,3 @@ dependencies {
6969
// ==========================================================================
7070
ext.packageName = "com.google.firebase.crashlytics.ktx"
7171
apply from: '../../gradle/googleServices.gradle'
72-

firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/common/CrashlyticsCoreInitializationTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ public CrashlyticsCore build() {
136136
new DisabledBreadcrumbSource(),
137137
new UnavailableAnalyticsEventLogger(),
138138
fileStore,
139-
crashHandlerExecutor);
139+
crashHandlerExecutor,
140+
mock(CrashlyticsAppQualitySessionsSubscriber.class));
140141
}
141142
}
142143

firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/common/CrashlyticsCoreTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,8 @@ CrashlyticsCore build(Context context) {
426426
breadcrumbSource,
427427
new UnavailableAnalyticsEventLogger(),
428428
new FileStore(context),
429-
new SameThreadExecutorService());
429+
new SameThreadExecutorService(),
430+
mock(CrashlyticsAppQualitySessionsSubscriber.class));
430431
return crashlyticsCore;
431432
}
432433
}

firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/persistence/CrashlyticsReportPersistenceTest.java

Lines changed: 119 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static org.mockito.Mockito.when;
2020

2121
import com.google.firebase.crashlytics.internal.CrashlyticsTestCase;
22+
import com.google.firebase.crashlytics.internal.common.CrashlyticsAppQualitySessionsSubscriber;
2223
import com.google.firebase.crashlytics.internal.common.CrashlyticsReportWithSessionId;
2324
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport;
2425
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport.Session;
@@ -42,8 +43,8 @@
4243
import org.mockito.internal.util.collections.Sets;
4344

4445
public class CrashlyticsReportPersistenceTest extends CrashlyticsTestCase {
45-
4646
private static final int VERY_LARGE_UPPER_LIMIT = 9999;
47+
private static final String APP_QUALITY_SESSION_ID = "9";
4748

4849
private CrashlyticsReportPersistence reportPersistence;
4950
private FileStore fileStore;
@@ -62,12 +63,22 @@ private static SettingsProvider createSettingsProviderMock(
6263
return settingsProvider;
6364
}
6465

66+
private static CrashlyticsAppQualitySessionsSubscriber createSessionsSubscriberMock(
67+
String appQualitySessionId) {
68+
CrashlyticsAppQualitySessionsSubscriber sessionsSubscriber =
69+
mock(CrashlyticsAppQualitySessionsSubscriber.class);
70+
when(sessionsSubscriber.getAppQualitySessionId()).thenReturn(appQualitySessionId);
71+
return sessionsSubscriber;
72+
}
73+
6574
@Override
6675
public void setUp() throws Exception {
6776
fileStore = new FileStore(getContext());
6877
reportPersistence =
6978
new CrashlyticsReportPersistence(
70-
fileStore, createSettingsProviderMock(VERY_LARGE_UPPER_LIMIT, VERY_LARGE_UPPER_LIMIT));
79+
fileStore,
80+
createSettingsProviderMock(VERY_LARGE_UPPER_LIMIT, VERY_LARGE_UPPER_LIMIT),
81+
createSessionsSubscriberMock(APP_QUALITY_SESSION_ID));
7182
}
7283

7384
public void testListSortedOpenSessionIds() {
@@ -147,6 +158,7 @@ public void testLoadFinalizedReports_reportThenEvent_returnsReportWithEvent() {
147158
assertEquals(
148159
testReport
149160
.withSessionEndFields(endedAt, false, null)
161+
.withAppQualitySessionId(APP_QUALITY_SESSION_ID)
150162
.withEvents(ImmutableList.from(testEvent)),
151163
finalizedReport);
152164
}
@@ -172,6 +184,7 @@ public void testLoadFinalizedReports_reportThenMultipleEvents_returnsReportWithM
172184
assertEquals(
173185
testReport
174186
.withSessionEndFields(endedAt, false, null)
187+
.withAppQualitySessionId(APP_QUALITY_SESSION_ID)
175188
.withEvents(ImmutableList.from(testEvent, testEvent2)),
176189
finalizedReport);
177190
}
@@ -201,12 +214,14 @@ public void testLoadFinalizedReports_reportThenMultipleEvents_returnsReportWithM
201214
assertEquals(
202215
testReport1
203216
.withSessionEndFields(endedAt, false, null)
217+
.withAppQualitySessionId(APP_QUALITY_SESSION_ID)
204218
.withEvents(ImmutableList.from(testEvent1)),
205219
finalizedReport1);
206220
final CrashlyticsReport finalizedReport2 = finalizedReports.get(0).getReport();
207221
assertEquals(
208222
testReport2
209223
.withSessionEndFields(endedAt, false, null)
224+
.withAppQualitySessionId(APP_QUALITY_SESSION_ID)
210225
.withEvents(ImmutableList.from(testEvent2)),
211226
finalizedReport2);
212227
}
@@ -274,7 +289,9 @@ public void testFinalizeReports_skipsCappingCurrentSession() throws IOException
274289
public void testFinalizeReports_capsReports() {
275290
reportPersistence =
276291
new CrashlyticsReportPersistence(
277-
fileStore, createSettingsProviderMock(4, VERY_LARGE_UPPER_LIMIT));
292+
fileStore,
293+
createSettingsProviderMock(4, VERY_LARGE_UPPER_LIMIT),
294+
createSessionsSubscriberMock(APP_QUALITY_SESSION_ID));
278295
for (int i = 0; i < 10; i++) {
279296
persistReportWithEvent(reportPersistence, "testSession" + i, true);
280297
}
@@ -298,7 +315,9 @@ public void testFinalizeReports_whenSettingsChanges_capsReports() throws IOExcep
298315
new Settings(0, sessionData2, new FeatureFlagData(true, true, false), 3, 0, 1.0, 1.0, 1);
299316

300317
when(settingsProvider.getSettingsSync()).thenReturn(settings1);
301-
reportPersistence = new CrashlyticsReportPersistence(fileStore, settingsProvider);
318+
reportPersistence =
319+
new CrashlyticsReportPersistence(
320+
fileStore, settingsProvider, createSessionsSubscriberMock(APP_QUALITY_SESSION_ID));
302321

303322
DecimalFormat format = new DecimalFormat("00");
304323
for (int i = 0; i < 16; i++) {
@@ -324,7 +343,9 @@ public void testFinalizeReports_whenSettingsChanges_capsReports() throws IOExcep
324343
public void testFinalizeReports_removesLowPriorityReportsFirst() throws IOException {
325344
reportPersistence =
326345
new CrashlyticsReportPersistence(
327-
fileStore, createSettingsProviderMock(4, VERY_LARGE_UPPER_LIMIT));
346+
fileStore,
347+
createSettingsProviderMock(4, VERY_LARGE_UPPER_LIMIT),
348+
createSessionsSubscriberMock(APP_QUALITY_SESSION_ID));
328349

329350
for (int i = 0; i < 10; i++) {
330351
boolean priority = i >= 3 && i <= 8;
@@ -347,7 +368,9 @@ public void testFinalizeReports_prioritizesNativeAndNonnativeFatals() throws IOE
347368
CrashlyticsReport.FilesPayload filesPayload = makeFilePayload();
348369
reportPersistence =
349370
new CrashlyticsReportPersistence(
350-
fileStore, createSettingsProviderMock(4, VERY_LARGE_UPPER_LIMIT));
371+
fileStore,
372+
createSettingsProviderMock(4, VERY_LARGE_UPPER_LIMIT),
373+
createSessionsSubscriberMock(APP_QUALITY_SESSION_ID));
351374

352375
persistReportWithEvent(reportPersistence, "testSession1", true);
353376
reportPersistence.finalizeSessionWithNativeEvent("testSession1", filesPayload, null);
@@ -370,7 +393,9 @@ public void testFinalizeReports_prioritizesNativeAndNonnativeFatals() throws IOE
370393
public void testFinalizeReports_removesOldestReportsFirst() throws IOException {
371394
reportPersistence =
372395
new CrashlyticsReportPersistence(
373-
fileStore, createSettingsProviderMock(4, VERY_LARGE_UPPER_LIMIT));
396+
fileStore,
397+
createSettingsProviderMock(4, VERY_LARGE_UPPER_LIMIT),
398+
createSessionsSubscriberMock(APP_QUALITY_SESSION_ID));
374399
for (int i = 0; i < 8; i++) {
375400
String sessionId = "testSession" + i;
376401
persistReportWithEvent(reportPersistence, sessionId, true);
@@ -519,7 +544,9 @@ public void testDeleteAllReports_removesAllReports() {
519544
public void testPersistEvent_keepsAppropriateNumberOfMostRecentEvents() throws IOException {
520545
reportPersistence =
521546
new CrashlyticsReportPersistence(
522-
fileStore, createSettingsProviderMock(VERY_LARGE_UPPER_LIMIT, 4));
547+
fileStore,
548+
createSettingsProviderMock(VERY_LARGE_UPPER_LIMIT, 4),
549+
createSessionsSubscriberMock(APP_QUALITY_SESSION_ID));
523550
final String sessionId = "testSession";
524551
final CrashlyticsReport testReport = makeTestReport(sessionId);
525552
final CrashlyticsReport.Session.Event testEvent1 = makeTestEvent("type1", "reason1");
@@ -547,6 +574,7 @@ public void testPersistEvent_keepsAppropriateNumberOfMostRecentEvents() throws I
547574
assertEquals(
548575
testReport
549576
.withSessionEndFields(endedAt, false, null)
577+
.withAppQualitySessionId(APP_QUALITY_SESSION_ID)
550578
.withEvents(ImmutableList.from(testEvent2, testEvent3, testEvent4, testEvent5)),
551579
finalizedReport);
552580
}
@@ -563,7 +591,9 @@ public void testPersistEvent_whenSettingsChanges_keepsAppropriateNumberOfMostRec
563591
new Settings(0, sessionData2, new FeatureFlagData(true, true, false), 3, 0, 1.0, 1.0, 1);
564592

565593
when(settingsProvider.getSettingsSync()).thenReturn(settings1);
566-
reportPersistence = new CrashlyticsReportPersistence(fileStore, settingsProvider);
594+
reportPersistence =
595+
new CrashlyticsReportPersistence(
596+
fileStore, settingsProvider, createSessionsSubscriberMock(APP_QUALITY_SESSION_ID));
567597

568598
final String sessionId = "testSession";
569599
final CrashlyticsReport testReport = makeTestReport(sessionId);
@@ -592,6 +622,7 @@ public void testPersistEvent_whenSettingsChanges_keepsAppropriateNumberOfMostRec
592622
assertEquals(
593623
testReport
594624
.withSessionEndFields(endedAt, false, null)
625+
.withAppQualitySessionId(APP_QUALITY_SESSION_ID)
595626
.withEvents(ImmutableList.from(testEvent2, testEvent3, testEvent4, testEvent5)),
596627
finalizedReport);
597628

@@ -629,6 +660,7 @@ public void testPersistEvent_whenSettingsChanges_keepsAppropriateNumberOfMostRec
629660
assertEquals(
630661
testReport2
631662
.withSessionEndFields(endedAt, false, null)
663+
.withAppQualitySessionId(APP_QUALITY_SESSION_ID)
632664
.withEvents(
633665
ImmutableList.from(
634666
testEvent3,
@@ -645,7 +677,9 @@ public void testPersistEvent_whenSettingsChanges_keepsAppropriateNumberOfMostRec
645677
public void testPersistReportWithAnrEvent() throws IOException {
646678
reportPersistence =
647679
new CrashlyticsReportPersistence(
648-
fileStore, createSettingsProviderMock(VERY_LARGE_UPPER_LIMIT, 4));
680+
fileStore,
681+
createSettingsProviderMock(VERY_LARGE_UPPER_LIMIT, 4),
682+
createSessionsSubscriberMock(APP_QUALITY_SESSION_ID));
649683
final String sessionId = "testSession";
650684
final CrashlyticsReport testReport = makeTestReport(sessionId);
651685
final Event testEvent = makeTestAnrEvent();
@@ -663,6 +697,81 @@ public void testPersistReportWithAnrEvent() throws IOException {
663697
assertEquals(1, finalizedReport.getSession().getEvents().size());
664698
}
665699

700+
public void testFinalizeReports_missingAppQualitySessionId() {
701+
reportPersistence =
702+
new CrashlyticsReportPersistence(
703+
fileStore,
704+
createSettingsProviderMock(4, VERY_LARGE_UPPER_LIMIT),
705+
// Simulate Sessions subscriber failure by setting appQualitySessionId to null.
706+
createSessionsSubscriberMock(/* appQualitySessionId= */ null));
707+
708+
String sessionId = "testSession";
709+
CrashlyticsReport testReport = makeTestReport(sessionId);
710+
CrashlyticsReport.Session.Event testEvent = makeTestEvent();
711+
712+
reportPersistence.persistReport(testReport);
713+
reportPersistence.persistEvent(testEvent, sessionId);
714+
715+
long endedAt = System.currentTimeMillis();
716+
717+
reportPersistence.finalizeReports("skippedSession", endedAt);
718+
719+
List<CrashlyticsReportWithSessionId> finalizedReports =
720+
reportPersistence.loadFinalizedReports();
721+
assertEquals(1, finalizedReports.size());
722+
CrashlyticsReport finalizedReport = finalizedReports.get(0).getReport();
723+
assertNotNull(finalizedReport.getSession());
724+
assertEquals(
725+
testReport
726+
.withSessionEndFields(endedAt, false, null)
727+
.withEvents(ImmutableList.from(testEvent)),
728+
finalizedReport);
729+
730+
// getAppQualitySessionId should return null since sessions subscriber never got an id.
731+
assertNull(finalizedReport.getSession().getAppQualitySessionId());
732+
}
733+
734+
public void testPersistEvent_updatesLatestAppQualitySession() {
735+
CrashlyticsAppQualitySessionsSubscriber mockSessionsSubscriber =
736+
createSessionsSubscriberMock(APP_QUALITY_SESSION_ID);
737+
CrashlyticsReportPersistence reportPersistence =
738+
new CrashlyticsReportPersistence(
739+
fileStore,
740+
createSettingsProviderMock(VERY_LARGE_UPPER_LIMIT, VERY_LARGE_UPPER_LIMIT),
741+
mockSessionsSubscriber);
742+
743+
String sessionId = "testSession";
744+
CrashlyticsReport testReport = makeTestReport(sessionId);
745+
CrashlyticsReport.Session.Event testEvent1 = makeTestEvent("type1", "reason1");
746+
CrashlyticsReport.Session.Event testEvent2 = makeTestEvent("type2", "reason2");
747+
CrashlyticsReport.Session.Event testEvent3 = makeTestEvent("type3", "reason3");
748+
749+
reportPersistence.persistReport(testReport);
750+
reportPersistence.persistEvent(testEvent1, sessionId);
751+
reportPersistence.persistEvent(testEvent2, sessionId);
752+
753+
// Simulate a new app quality sessions session before the last event.
754+
String latestAppQualitySessionId = "300";
755+
when(mockSessionsSubscriber.getAppQualitySessionId()).thenReturn(latestAppQualitySessionId);
756+
reportPersistence.persistEvent(testEvent3, sessionId);
757+
758+
long endedAt = System.currentTimeMillis();
759+
760+
reportPersistence.finalizeReports("skippedSession", endedAt);
761+
762+
List<CrashlyticsReportWithSessionId> finalizedReports =
763+
reportPersistence.loadFinalizedReports();
764+
assertEquals(1, finalizedReports.size());
765+
CrashlyticsReport finalizedReport = finalizedReports.get(0).getReport();
766+
assertNotNull(finalizedReport.getSession());
767+
assertEquals(
768+
testReport
769+
.withSessionEndFields(endedAt, false, null)
770+
.withAppQualitySessionId(latestAppQualitySessionId)
771+
.withEvents(ImmutableList.from(testEvent1, testEvent2, testEvent3)),
772+
finalizedReport);
773+
}
774+
666775
private static void persistReportWithEvent(
667776
CrashlyticsReportPersistence reportPersistence, String sessionId, boolean isHighPriority) {
668777
CrashlyticsReport testReport = makeTestReport(sessionId);

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/FirebaseCrashlytics.java

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import com.google.firebase.crashlytics.internal.common.AppData;
3232
import com.google.firebase.crashlytics.internal.common.BuildIdInfo;
3333
import com.google.firebase.crashlytics.internal.common.CommonUtils;
34+
import com.google.firebase.crashlytics.internal.common.CrashlyticsAppQualitySessionsSubscriber;
3435
import com.google.firebase.crashlytics.internal.common.CrashlyticsCore;
3536
import com.google.firebase.crashlytics.internal.common.DataCollectionArbiter;
3637
import com.google.firebase.crashlytics.internal.common.ExecutorUtils;
@@ -41,7 +42,6 @@
4142
import com.google.firebase.inject.Deferred;
4243
import com.google.firebase.installations.FirebaseInstallationsApi;
4344
import com.google.firebase.sessions.FirebaseSessions;
44-
import com.google.firebase.sessions.api.SessionSubscriber;
4545
import java.util.List;
4646
import java.util.concurrent.Callable;
4747
import java.util.concurrent.ExecutorService;
@@ -91,6 +91,10 @@ public class FirebaseCrashlytics {
9191
final ExecutorService crashHandlerExecutor =
9292
ExecutorUtils.buildSingleThreadExecutorService("Crashlytics Exception Handler");
9393

94+
CrashlyticsAppQualitySessionsSubscriber sessionsSubscriber =
95+
new CrashlyticsAppQualitySessionsSubscriber(arbiter);
96+
firebaseSessions.register(sessionsSubscriber);
97+
9498
final CrashlyticsCore core =
9599
new CrashlyticsCore(
96100
app,
@@ -100,7 +104,8 @@ public class FirebaseCrashlytics {
100104
analyticsDeferredProxy.getDeferredBreadcrumbSource(),
101105
analyticsDeferredProxy.getAnalyticsEventLogger(),
102106
fileStore,
103-
crashHandlerExecutor);
107+
crashHandlerExecutor,
108+
sessionsSubscriber);
104109

105110
final String googleAppId = app.getOptions().getApplicationId();
106111
final String mappingFileId = CommonUtils.getMappingFileId(context);
@@ -179,28 +184,6 @@ public Void call() throws Exception {
179184
}
180185
});
181186

182-
// TODO(mrober): Replace with a real session implementation.
183-
firebaseSessions.register(
184-
new SessionSubscriber() {
185-
@Override
186-
public void onSessionChanged(@NonNull SessionDetails sessionDetails) {
187-
Logger.getLogger().d("onSessionChanged: " + sessionDetails);
188-
// TODO(mrober): Set new field in report and remove this.
189-
core.setInternalKey("sessionId", sessionDetails.getSessionId());
190-
}
191-
192-
@Override
193-
public boolean isDataCollectionEnabled() {
194-
return arbiter.isAutomaticDataCollectionEnabled();
195-
}
196-
197-
@NonNull
198-
@Override
199-
public SessionSubscriber.Name getSessionSubscriberName() {
200-
return SessionSubscriber.Name.CRASHLYTICS;
201-
}
202-
});
203-
204187
return new FirebaseCrashlytics(core);
205188
}
206189

0 commit comments

Comments
 (0)