FIRCLSReportUploader.m 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. // Copyright 2019 Google
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "Interop/Analytics/Public/FIRAnalyticsInterop.h"
  15. #import "Crashlytics/Crashlytics/Components/FIRCLSApplication.h"
  16. #import "Crashlytics/Crashlytics/Controllers/FIRCLSAnalyticsManager.h"
  17. #import "Crashlytics/Crashlytics/Controllers/FIRCLSManagerData.h"
  18. #import "Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader_Private.h"
  19. #import "Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionToken.h"
  20. #import "Crashlytics/Crashlytics/Helpers/FIRCLSDefines.h"
  21. #import "Crashlytics/Crashlytics/Models/FIRCLSFileManager.h"
  22. #import "Crashlytics/Crashlytics/Models/FIRCLSInstallIdentifierModel.h"
  23. #import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
  24. #import "Crashlytics/Crashlytics/Models/FIRCLSSymbolResolver.h"
  25. #import "Crashlytics/Crashlytics/Models/Record/FIRCLSReportAdapter.h"
  26. #import "Crashlytics/Crashlytics/Operations/Reports/FIRCLSProcessReportOperation.h"
  27. #include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
  28. #import "Crashlytics/Shared/FIRCLSConstants.h"
  29. #import "Crashlytics/Shared/FIRCLSNetworking/FIRCLSMultipartMimeStreamEncoder.h"
  30. #import "Crashlytics/Shared/FIRCLSNetworking/FIRCLSURLBuilder.h"
  31. #import <GoogleDataTransport/GoogleDataTransport.h>
  32. @interface FIRCLSReportUploader () {
  33. id<FIRAnalyticsInterop> _analytics;
  34. }
  35. @property(nonatomic, strong) GDTCORTransport *googleTransport;
  36. @property(nonatomic, strong) FIRCLSInstallIdentifierModel *installIDModel;
  37. @property(nonatomic, readonly) NSString *googleAppID;
  38. @end
  39. @implementation FIRCLSReportUploader
  40. - (instancetype)initWithManagerData:(FIRCLSManagerData *)managerData {
  41. self = [super init];
  42. if (!self) {
  43. return nil;
  44. }
  45. _operationQueue = managerData.operationQueue;
  46. _googleAppID = managerData.googleAppID;
  47. _googleTransport = managerData.googleTransport;
  48. _installIDModel = managerData.installIDModel;
  49. _fileManager = managerData.fileManager;
  50. _analytics = managerData.analytics;
  51. return self;
  52. }
  53. #pragma mark - Packaging and Submission
  54. /*
  55. * For a crash report, this is the initial code path for uploading. A report
  56. * will not repeat this code path after it's happened because this code path
  57. * will move the report from the "active" folder into "processing" and then
  58. * "prepared". Once in prepared, the report can be re-uploaded any number of times
  59. * with uploadPackagedReportAtPath in the case of an upload failure.
  60. */
  61. - (void)prepareAndSubmitReport:(FIRCLSInternalReport *)report
  62. dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken
  63. asUrgent:(BOOL)urgent
  64. withProcessing:(BOOL)shouldProcess {
  65. if (![dataCollectionToken isValid]) {
  66. FIRCLSErrorLog(@"Data collection disabled and report will not be submitted");
  67. return;
  68. }
  69. // This activity is still relevant using GoogleDataTransport because the on-device
  70. // symbolication operation may be computationally intensive.
  71. FIRCLSApplicationActivity(
  72. FIRCLSApplicationActivityDefault, @"Crashlytics Crash Report Processing", ^{
  73. // Run this only once because it can be run multiple times in succession,
  74. // and if it's slow it could delay crash upload too much without providing
  75. // user benefit.
  76. static dispatch_once_t regenerateOnceToken;
  77. dispatch_once(&regenerateOnceToken, ^{
  78. // Check to see if the FID has rotated before we construct the payload
  79. // so that the payload has an updated value.
  80. [self.installIDModel regenerateInstallIDIfNeeded];
  81. });
  82. // Run on-device symbolication before packaging if we should process
  83. if (shouldProcess) {
  84. if (![self.fileManager moveItemAtPath:report.path
  85. toDirectory:self.fileManager.processingPath]) {
  86. FIRCLSErrorLog(@"Unable to move report for processing");
  87. return;
  88. }
  89. // adjust the report's path, and process it
  90. [report setPath:[self.fileManager.processingPath
  91. stringByAppendingPathComponent:report.directoryName]];
  92. FIRCLSSymbolResolver *resolver = [[FIRCLSSymbolResolver alloc] init];
  93. FIRCLSProcessReportOperation *processOperation =
  94. [[FIRCLSProcessReportOperation alloc] initWithReport:report resolver:resolver];
  95. [processOperation start];
  96. }
  97. // With the new report endpoint, the report is deleted once it is written to GDT
  98. // Check if the report has a crash file before the report is moved or deleted
  99. BOOL isCrash = report.isCrash;
  100. // For the new endpoint, just move the .clsrecords from "processing" -> "prepared".
  101. // In the old endpoint this was for packaging the report as a multipartmime file,
  102. // so this can probably be removed for GoogleDataTransport.
  103. if (![self.fileManager moveItemAtPath:report.path
  104. toDirectory:self.fileManager.preparedPath]) {
  105. FIRCLSErrorLog(@"Unable to move report to prepared");
  106. return;
  107. }
  108. NSString *packagedPath = [self.fileManager.preparedPath
  109. stringByAppendingPathComponent:report.path.lastPathComponent];
  110. FIRCLSInfoLog(@"[Firebase/Crashlytics] Packaged report with id '%@' for submission",
  111. report.identifier);
  112. [self uploadPackagedReportAtPath:packagedPath
  113. dataCollectionToken:dataCollectionToken
  114. asUrgent:urgent];
  115. // We don't check for success here for 2 reasons:
  116. // 1) If we can't upload a crash for whatever reason, but we can upload analytics
  117. // it's better for the customer to get accurate Crash Free Users.
  118. // 2) In the past we did try to check for success, but it was a useless check because
  119. // sendDataEvent is async (unless we're sending urgently).
  120. if (isCrash) {
  121. [FIRCLSAnalyticsManager logCrashWithTimeStamp:report.crashedOnDate.timeIntervalSince1970
  122. toAnalytics:self->_analytics];
  123. }
  124. });
  125. return;
  126. }
  127. /*
  128. * This code path can be repeated any number of times for a prepared crash report if
  129. * the report is failing to upload.
  130. *
  131. * Therefore, side effects (like logging to Analytics) should not go in this method or
  132. * else they will re-trigger when failures happen.
  133. *
  134. * When a crash report fails to upload, it will stay in the "prepared" folder. Upon next
  135. * run of the app, the ReportManager will attempt to re-upload prepared reports using this
  136. * method.
  137. */
  138. - (void)uploadPackagedReportAtPath:(NSString *)path
  139. dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken
  140. asUrgent:(BOOL)urgent {
  141. FIRCLSDebugLog(@"Submitting report %@", urgent ? @"urgently" : @"async");
  142. if (![dataCollectionToken isValid]) {
  143. FIRCLSErrorLog(@"A report upload was requested with an invalid data collection token.");
  144. return;
  145. }
  146. FIRCLSReportAdapter *adapter = [[FIRCLSReportAdapter alloc] initWithPath:path
  147. googleAppId:self.googleAppID
  148. installIDModel:self.installIDModel];
  149. GDTCOREvent *event = [self.googleTransport eventForTransport];
  150. event.dataObject = adapter;
  151. event.qosTier = GDTCOREventQoSFast; // Bypass batching, send immediately
  152. dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
  153. [self.googleTransport
  154. sendDataEvent:event
  155. onComplete:^(BOOL wasWritten, NSError *error) {
  156. if (!wasWritten) {
  157. FIRCLSErrorLog(
  158. @"Failed to send crash report due to failure writing GoogleDataTransport event");
  159. return;
  160. }
  161. if (error) {
  162. FIRCLSErrorLog(@"Failed to send crash report due to GoogleDataTransport error: %@",
  163. error.localizedDescription);
  164. return;
  165. }
  166. FIRCLSInfoLog(@"Completed report submission with id: %@", path.lastPathComponent);
  167. if (urgent) {
  168. dispatch_semaphore_signal(semaphore);
  169. }
  170. [self cleanUpSubmittedReportAtPath:path];
  171. }];
  172. if (urgent) {
  173. dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
  174. }
  175. }
  176. - (BOOL)cleanUpSubmittedReportAtPath:(NSString *)path {
  177. if (![[self fileManager] removeItemAtPath:path]) {
  178. FIRCLSErrorLog(@"Unable to remove packaged submission");
  179. return NO;
  180. }
  181. return YES;
  182. }
  183. @end