Skip to content

Commit e769c91

Browse files
authored
Scoping out report callback API (#7503)
1 parent 7a4b341 commit e769c91

23 files changed

+956
-683
lines changed

Crashlytics/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Unreleased
2+
- [added] Added a new API checkAndUpdateUnsentReportsWithCompletion for updating the crash report from the previous run of the app if, for example, the developer wants to implement a feedback dialog to ask end-users for more information. Unsent Crashlytics Reports have familiar methods like setting custom keys and logs.
3+
14
# v7.6.0
25
- [fixed] Fixed an issue where some developers experienced a race condition involving binary image operations (#7459).
36

Crashlytics/Crashlytics/Components/FIRCLSUserLogging.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ void FIRCLSUserLoggingWriteAndCheckABFiles(FIRCLSUserLoggingABStorage* storage,
101101
NSArray* FIRCLSUserLoggingStoredKeyValues(const char* path);
102102

103103
OBJC_EXTERN void FIRCLSLog(NSString* format, ...) NS_FORMAT_FUNCTION(1, 2);
104+
OBJC_EXTERN void FIRCLSLogToStorage(FIRCLSUserLoggingABStorage* storage,
105+
const char** activePath,
106+
NSString* format,
107+
...) NS_FORMAT_FUNCTION(3, 4);
108+
104109
#endif
105110

106111
__END_DECLS

Crashlytics/Crashlytics/Components/FIRCLSUserLogging.m

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ static void FIRCLSUserLoggingWriteKeysAndValues(NSDictionary *keysAndValues,
4242
static void FIRCLSUserLoggingCheckAndSwapABFiles(FIRCLSUserLoggingABStorage *storage,
4343
const char **activePath,
4444
off_t fileSize);
45-
void FIRCLSLogInternal(NSString *message);
45+
void FIRCLSLogInternal(FIRCLSUserLoggingABStorage *storage,
46+
const char **activePath,
47+
NSString *message);
4648

4749
#pragma mark - Setup
4850
void FIRCLSUserLoggingInit(FIRCLSUserLoggingReadOnlyContext *roContext,
@@ -397,7 +399,26 @@ void FIRCLSLog(NSString *format, ...) {
397399
NSString *msg = [[NSString alloc] initWithFormat:format arguments:args];
398400
va_end(args);
399401

400-
FIRCLSLogInternal(msg);
402+
FIRCLSUserLoggingABStorage *currentStorage = &_firclsContext.readonly->logging.logStorage;
403+
const char **activePath = &_firclsContext.writable->logging.activeUserLogPath;
404+
FIRCLSLogInternal(currentStorage, activePath, msg);
405+
}
406+
407+
void FIRCLSLogToStorage(FIRCLSUserLoggingABStorage *storage,
408+
const char **activePath,
409+
NSString *format,
410+
...) {
411+
// If the format is nil do nothing just like NSLog.
412+
if (!format) {
413+
return;
414+
}
415+
416+
va_list args;
417+
va_start(args, format);
418+
NSString *msg = [[NSString alloc] initWithFormat:format arguments:args];
419+
va_end(args);
420+
421+
FIRCLSLogInternal(storage, activePath, msg);
401422
}
402423

403424
#pragma mark - Properties
@@ -525,7 +546,9 @@ void FIRCLSLogInternalWrite(FIRCLSFile *file, NSString *message, uint64_t time)
525546
FIRCLSFileWriteSectionEnd(file);
526547
}
527548

528-
void FIRCLSLogInternal(NSString *message) {
549+
void FIRCLSLogInternal(FIRCLSUserLoggingABStorage *storage,
550+
const char **activePath,
551+
NSString *message) {
529552
if (!message) {
530553
return;
531554
}
@@ -539,7 +562,7 @@ void FIRCLSLogInternal(NSString *message) {
539562
struct timeval te;
540563

541564
NSUInteger messageLength = [message length];
542-
int maxLogSize = _firclsContext.readonly->logging.logStorage.maxSize;
565+
int maxLogSize = storage->maxSize;
543566

544567
if (messageLength > maxLogSize) {
545568
FIRCLSWarningLog(
@@ -556,9 +579,7 @@ void FIRCLSLogInternal(NSString *message) {
556579

557580
const uint64_t time = te.tv_sec * 1000LL + te.tv_usec / 1000;
558581

559-
FIRCLSUserLoggingWriteAndCheckABFiles(&_firclsContext.readonly->logging.logStorage,
560-
&_firclsContext.writable->logging.activeUserLogPath,
561-
^(FIRCLSFile *file) {
562-
FIRCLSLogInternalWrite(file, message, time);
563-
});
582+
FIRCLSUserLoggingWriteAndCheckABFiles(storage, activePath, ^(FIRCLSFile *file) {
583+
FIRCLSLogInternalWrite(file, message, time);
584+
});
564585
}

Crashlytics/Crashlytics/Controllers/FIRCLSExistingReportManager.h

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,59 @@ NS_ASSUME_NONNULL_BEGIN
1919
@class FIRCLSManagerData;
2020
@class FIRCLSReportUploader;
2121
@class FIRCLSDataCollectionToken;
22+
@class FIRCrashlyticsReport;
2223

2324
@interface FIRCLSExistingReportManager : NSObject
2425

26+
/**
27+
* Returns the number of unsent reports on the device, ignoring empty reports in
28+
* the active folder, and ignoring any reports in "processing" or "prepared".
29+
*
30+
* In the past, this would count reports in the processed or prepared
31+
* folders. This has been changed because reports in those paths have already
32+
* been cleared for upload, so there isn't any point in asking for permission
33+
* or possibly spamming end-users if a report gets stuck.
34+
*
35+
* The tricky part is, customers will NOT be alerted in checkForUnsentReports
36+
* for reports in these paths, but when they choose sendUnsentReports / enable data
37+
* collection, reports in those directories will be re-managed. This should be ok and
38+
* just an edge case because reports should only be in processing or prepared for a split second as
39+
* they do on-device symbolication and get converted into a GDTEvent. After a report is handed off
40+
* to GoogleDataTransport, it is uploaded regardless of Crashlytics data collection.
41+
*/
42+
@property(nonatomic, readonly) NSUInteger unsentReportsCount;
43+
44+
/**
45+
* This value needs to stay in sync with numUnsentReports, so if there is > 0 numUnsentReports,
46+
* newestUnsentReport needs to return a value. Otherwise it needs to return null.
47+
*
48+
* FIRCLSContext needs to be initialized before the FIRCrashlyticsReport is instantiated.
49+
*/
50+
@property(nonatomic, readonly) FIRCrashlyticsReport *_Nullable newestUnsentReport;
51+
2552
- (instancetype)initWithManagerData:(FIRCLSManagerData *)managerData
2653
reportUploader:(FIRCLSReportUploader *)reportUploader;
2754

2855
- (instancetype)init NS_UNAVAILABLE;
2956
+ (instancetype)new NS_UNAVAILABLE;
3057

31-
- (int)unsentReportsCountWithPreexisting:(NSArray<NSString *> *)paths;
58+
/**
59+
* This is important to call once, early in startup, before the
60+
* new report for this run of the app has been created. Any
61+
* reports in ExistingReportManager will be uploaded or deleted
62+
* and we don't want to do that for the current run of the app.
63+
*/
64+
- (void)collectExistingReports;
3265

33-
- (void)deleteUnsentReportsWithPreexisting:(NSArray *)preexistingReportPaths;
66+
/**
67+
* This is the side-effect of calling deleteUnsentReports, or collect_reports setting
68+
* being false.
69+
*/
70+
- (void)deleteUnsentReports;
3471

35-
- (void)processExistingReportPaths:(NSArray *)reportPaths
36-
dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken
72+
- (void)sendUnsentReportsWithToken:(FIRCLSDataCollectionToken *)dataCollectionToken
3773
asUrgent:(BOOL)urgent;
3874

39-
- (void)handleContentsInOtherReportingDirectoriesWithToken:(FIRCLSDataCollectionToken *)token;
40-
4175
@end
4276

4377
NS_ASSUME_NONNULL_END

Crashlytics/Crashlytics/Controllers/FIRCLSExistingReportManager.m

Lines changed: 88 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,23 @@
1717
#import "Crashlytics/Crashlytics/Controllers/FIRCLSManagerData.h"
1818
#import "Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader.h"
1919
#import "Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionToken.h"
20-
#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
2120
#import "Crashlytics/Crashlytics/Models/FIRCLSFileManager.h"
2221
#import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
22+
#import "Crashlytics/Crashlytics/Private/FIRCrashlyticsReport_Private.h"
23+
#import "Crashlytics/Crashlytics/Public/FirebaseCrashlytics/FIRCrashlyticsReport.h"
2324

2425
@interface FIRCLSExistingReportManager ()
2526

2627
@property(nonatomic, strong) FIRCLSFileManager *fileManager;
2728
@property(nonatomic, strong) NSOperationQueue *operationQueue;
2829
@property(nonatomic, strong) FIRCLSReportUploader *reportUploader;
2930

31+
// This list of active reports excludes the brand new active report that will be created this run of
32+
// the app.
33+
@property(nonatomic, strong) NSArray *existingUnemptyActiveReportPaths;
34+
@property(nonatomic, strong) NSArray *processingReportPaths;
35+
@property(nonatomic, strong) NSArray *preparedReportPaths;
36+
3037
@end
3138

3239
@implementation FIRCLSExistingReportManager
@@ -45,51 +52,106 @@ - (instancetype)initWithManagerData:(FIRCLSManagerData *)managerData
4552
return self;
4653
}
4754

48-
/**
49-
* Returns the number of unsent reports on the device, including the ones passed in.
50-
*/
51-
- (int)unsentReportsCountWithPreexisting:(NSArray<NSString *> *)paths {
52-
int count = [self countSubmittableAndDeleteUnsubmittableReportPaths:paths];
55+
NSInteger compareOlder(FIRCLSInternalReport *reportA,
56+
FIRCLSInternalReport *reportB,
57+
void *context) {
58+
return [reportA.dateCreated compare:reportB.dateCreated];
59+
}
60+
61+
- (void)collectExistingReports {
62+
self.existingUnemptyActiveReportPaths =
63+
[self getUnemptyExistingActiveReportsAndDeleteEmpty:self.fileManager.activePathContents];
64+
self.processingReportPaths = self.fileManager.processingPathContents;
65+
self.preparedReportPaths = self.fileManager.preparedPathContents;
66+
}
67+
68+
- (FIRCrashlyticsReport *)newestUnsentReport {
69+
if (self.unsentReportsCount <= 0) {
70+
return nil;
71+
}
72+
73+
NSMutableArray<NSString *> *allReportPaths =
74+
[NSMutableArray arrayWithArray:self.existingUnemptyActiveReportPaths];
5375

54-
count += self.fileManager.processingPathContents.count;
55-
count += self.fileManager.preparedPathContents.count;
56-
return count;
76+
NSMutableArray<FIRCLSInternalReport *> *validReports = [NSMutableArray array];
77+
for (NSString *path in allReportPaths) {
78+
FIRCLSInternalReport *_Nullable report = [FIRCLSInternalReport reportWithPath:path];
79+
if (!report) {
80+
continue;
81+
}
82+
[validReports addObject:report];
83+
}
84+
85+
[validReports sortUsingFunction:compareOlder context:nil];
86+
87+
FIRCLSInternalReport *_Nullable internalReport = [validReports lastObject];
88+
return [[FIRCrashlyticsReport alloc] initWithInternalReport:internalReport];
89+
}
90+
91+
- (NSUInteger)unsentReportsCount {
92+
// There are nuances about why we only count active reports.
93+
// See the header comment for more information.
94+
return self.existingUnemptyActiveReportPaths.count;
5795
}
5896

59-
- (int)countSubmittableAndDeleteUnsubmittableReportPaths:(NSArray *)reportPaths {
60-
int count = 0;
97+
- (NSArray *)getUnemptyExistingActiveReportsAndDeleteEmpty:(NSArray *)reportPaths {
98+
NSMutableArray *unemptyReports = [NSMutableArray array];
6199
for (NSString *path in reportPaths) {
62100
FIRCLSInternalReport *report = [FIRCLSInternalReport reportWithPath:path];
63-
if ([report needsToBeSubmitted]) {
64-
count++;
101+
if ([report hasAnyEvents]) {
102+
[unemptyReports addObject:path];
65103
} else {
66104
[self.operationQueue addOperationWithBlock:^{
67-
[self->_fileManager removeItemAtPath:path];
105+
[self.fileManager removeItemAtPath:path];
68106
}];
69107
}
70108
}
71-
return count;
109+
return unemptyReports;
72110
}
73111

74-
- (void)processExistingReportPaths:(NSArray *)reportPaths
75-
dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken
112+
- (void)sendUnsentReportsWithToken:(FIRCLSDataCollectionToken *)dataCollectionToken
76113
asUrgent:(BOOL)urgent {
77-
for (NSString *path in reportPaths) {
114+
for (NSString *path in self.existingUnemptyActiveReportPaths) {
78115
[self processExistingActiveReportPath:path
79116
dataCollectionToken:dataCollectionToken
80117
asUrgent:urgent];
81118
}
119+
120+
// deal with stuff in processing more carefully - do not process again
121+
[self.operationQueue addOperationWithBlock:^{
122+
for (NSString *path in self.processingReportPaths) {
123+
FIRCLSInternalReport *report = [FIRCLSInternalReport reportWithPath:path];
124+
[self.reportUploader prepareAndSubmitReport:report
125+
dataCollectionToken:dataCollectionToken
126+
asUrgent:NO
127+
withProcessing:NO];
128+
}
129+
}];
130+
131+
// Because this could happen quite a bit after the inital set of files was
132+
// captured, some could be completed (deleted). So, just double-check to make sure
133+
// the file still exists.
134+
[self.operationQueue addOperationWithBlock:^{
135+
for (NSString *path in self.preparedReportPaths) {
136+
if (![[self.fileManager underlyingFileManager] fileExistsAtPath:path]) {
137+
continue;
138+
}
139+
[self.reportUploader uploadPackagedReportAtPath:path
140+
dataCollectionToken:dataCollectionToken
141+
asUrgent:NO];
142+
}
143+
}];
82144
}
83145

84146
- (void)processExistingActiveReportPath:(NSString *)path
85147
dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken
86148
asUrgent:(BOOL)urgent {
87149
FIRCLSInternalReport *report = [FIRCLSInternalReport reportWithPath:path];
88150

89-
// TODO: needsToBeSubmitted should really be called on the background queue.
90-
if (![report needsToBeSubmitted]) {
151+
// TODO: hasAnyEvents should really be called on the background queue.
152+
if (![report hasAnyEvents]) {
91153
[self.operationQueue addOperationWithBlock:^{
92-
[self->_fileManager removeItemAtPath:path];
154+
[self.fileManager removeItemAtPath:path];
93155
}];
94156

95157
return;
@@ -104,11 +166,6 @@ - (void)processExistingActiveReportPath:(NSString *)path
104166
return;
105167
}
106168

107-
[self submitReport:report dataCollectionToken:dataCollectionToken];
108-
}
109-
110-
- (void)submitReport:(FIRCLSInternalReport *)report
111-
dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken {
112169
[self.operationQueue addOperationWithBlock:^{
113170
[self.reportUploader prepareAndSubmitReport:report
114171
dataCollectionToken:dataCollectionToken
@@ -117,61 +174,17 @@ - (void)submitReport:(FIRCLSInternalReport *)report
117174
}];
118175
}
119176

120-
// This is the side-effect of calling deleteUnsentReports, or collect_reports setting
121-
// being false
122-
- (void)deleteUnsentReportsWithPreexisting:(NSArray *)preexistingReportPaths {
123-
[self removeExistingReportPaths:preexistingReportPaths];
124-
[self removeExistingReportPaths:self.fileManager.processingPathContents];
125-
[self removeExistingReportPaths:self.fileManager.preparedPathContents];
126-
}
177+
- (void)deleteUnsentReports {
178+
NSArray<NSString *> *reportPaths = @[];
179+
reportPaths = [reportPaths arrayByAddingObjectsFromArray:self.existingUnemptyActiveReportPaths];
180+
reportPaths = [reportPaths arrayByAddingObjectsFromArray:self.processingReportPaths];
181+
reportPaths = [reportPaths arrayByAddingObjectsFromArray:self.preparedReportPaths];
127182

128-
- (void)removeExistingReportPaths:(NSArray *)reportPaths {
129183
[self.operationQueue addOperationWithBlock:^{
130184
for (NSString *path in reportPaths) {
131185
[self.fileManager removeItemAtPath:path];
132186
}
133187
}];
134188
}
135189

136-
- (void)handleContentsInOtherReportingDirectoriesWithToken:(FIRCLSDataCollectionToken *)token {
137-
[self handleExistingFilesInProcessingWithToken:token];
138-
[self handleExistingFilesInPreparedWithToken:token];
139-
}
140-
141-
- (void)handleExistingFilesInProcessingWithToken:(FIRCLSDataCollectionToken *)token {
142-
NSArray *processingPaths = _fileManager.processingPathContents;
143-
144-
// deal with stuff in processing more carefully - do not process again
145-
[self.operationQueue addOperationWithBlock:^{
146-
for (NSString *path in processingPaths) {
147-
FIRCLSInternalReport *report = [FIRCLSInternalReport reportWithPath:path];
148-
[self.reportUploader prepareAndSubmitReport:report
149-
dataCollectionToken:token
150-
asUrgent:NO
151-
withProcessing:NO];
152-
}
153-
}];
154-
}
155-
156-
- (void)handleExistingFilesInPreparedWithToken:(FIRCLSDataCollectionToken *)token {
157-
NSArray *preparedPaths = self.fileManager.preparedPathContents;
158-
[self.operationQueue addOperationWithBlock:^{
159-
[self uploadPreexistingFiles:preparedPaths withToken:token];
160-
}];
161-
}
162-
163-
- (void)uploadPreexistingFiles:(NSArray *)files withToken:(FIRCLSDataCollectionToken *)token {
164-
// Because this could happen quite a bit after the inital set of files was
165-
// captured, some could be completed (deleted). So, just double-check to make sure
166-
// the file still exists.
167-
168-
for (NSString *path in files) {
169-
if (![[_fileManager underlyingFileManager] fileExistsAtPath:path]) {
170-
continue;
171-
}
172-
173-
[self.reportUploader uploadPackagedReportAtPath:path dataCollectionToken:token asUrgent:NO];
174-
}
175-
}
176-
177190
@end

Crashlytics/Crashlytics/Controllers/FIRCLSReportManager.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ NS_ASSUME_NONNULL_BEGIN
3636

3737
- (FBLPromise<NSNumber *> *)startWithProfilingMark:(FIRCLSProfileMark)mark;
3838

39-
- (FBLPromise<NSNumber *> *)checkForUnsentReports;
39+
- (FBLPromise<FIRCrashlyticsReport *> *)checkForUnsentReports;
4040
- (FBLPromise *)sendUnsentReports;
4141
- (FBLPromise *)deleteUnsentReports;
4242

0 commit comments

Comments
 (0)