QNUploadInfoReporter.m 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. //
  2. // QNUploadInfoReporter.m
  3. // QiniuSDK
  4. //
  5. // Created by WorkSpace_Sun on 2019/6/24.
  6. // Copyright © 2019 Qiniu. All rights reserved.
  7. //
  8. #import "QNUploadInfoReporter.h"
  9. #import "QNUploadInfoCollector.h"
  10. #import "QNResponseInfo.h"
  11. #import "QNFile.h"
  12. #import "QNUpToken.h"
  13. #import "QNUserAgent.h"
  14. #import "QNAsyncRun.h"
  15. #import "QNSystemTool.h"
  16. #import "QNVersion.h"
  17. #import <objc/runtime.h>
  18. @interface QNReportBaseItem ()
  19. // 打点类型 request、block、quality
  20. @property (nonatomic, copy) NSString *log_type;
  21. // 客户端时间戳
  22. @property (nonatomic, assign) int64_t up_time;
  23. @end
  24. @implementation QNReportBaseItem
  25. - (instancetype)init
  26. {
  27. self = [super init];
  28. if (self) {
  29. self.up_time = [[NSDate dateWithTimeIntervalSinceNow:0] timeIntervalSince1970];
  30. }
  31. return self;
  32. }
  33. - (NSString *)toJson {
  34. NSMutableDictionary *itemDic = [NSMutableDictionary dictionary];
  35. // self class property
  36. unsigned int selfPropertyCount = 0;
  37. objc_property_t *selfProperties = class_copyPropertyList([self class], &selfPropertyCount);
  38. for (unsigned int i = 0; i < selfPropertyCount; i ++) {
  39. objc_property_t property = selfProperties[i];
  40. const char *name = property_getName(property);
  41. unsigned int attrCount = 0;
  42. objc_property_attribute_t * attrs = property_copyAttributeList(property, &attrCount);
  43. for (unsigned int j = 0; j < attrCount; j ++) {
  44. objc_property_attribute_t attr = attrs[j];
  45. const char *attrName = attr.name;
  46. if (0 == strcmp(attrName, "T")) {
  47. const char *value = attr.value;
  48. if (0 == strcmp(value, "@\"NSString\"")) {
  49. NSString *key = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
  50. NSString *ivarValue = [self valueForKey:key];
  51. if (ivarValue) [itemDic setValue:ivarValue forKey:key];
  52. } else {
  53. // 默认其他属性的基本类型是int
  54. NSString *key = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
  55. NSNumber *ivarValue = [self valueForKey:key];
  56. if (ivarValue && ![ivarValue isEqualToNumber:@(QN_IntNotSet)]) [itemDic setValue:ivarValue forKey:key];
  57. }
  58. }
  59. }
  60. free(attrs);
  61. }
  62. free(selfProperties);
  63. // super class property
  64. unsigned int superPropertyCount = 0;
  65. objc_property_t *superProperties = class_copyPropertyList([self superclass], &superPropertyCount);
  66. for (unsigned int i = 0; i < superPropertyCount; i ++) {
  67. objc_property_t property = superProperties[i];
  68. const char *name = property_getName(property);
  69. unsigned int attrCount = 0;
  70. objc_property_attribute_t * attrs = property_copyAttributeList(property, &attrCount);
  71. for (unsigned int j = 0; j < attrCount; j ++) {
  72. objc_property_attribute_t attr = attrs[j];
  73. const char *attrName = attr.name;
  74. if (0 == strcmp(attrName, "T")) {
  75. const char *value = attr.value;
  76. if (0 == strcmp(value, "@\"NSString\"")) {
  77. NSString *key = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
  78. NSString *ivarValue = [self valueForKey:key];
  79. if (ivarValue) [itemDic setValue:ivarValue forKey:key];
  80. } else {
  81. // 默认其他属性的基本类型是int
  82. NSString *key = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
  83. NSNumber *ivarValue = [self valueForKey:key];
  84. if (ivarValue) [itemDic setValue:ivarValue forKey:key];
  85. }
  86. }
  87. }
  88. free(attrs);
  89. }
  90. free(superProperties);
  91. NSError *error;
  92. NSData *itemData = [NSJSONSerialization dataWithJSONObject:itemDic options:kNilOptions error:&error];
  93. if (error) return nil;
  94. NSString *itemJson = [[NSString alloc] initWithData:itemData encoding:NSUTF8StringEncoding];
  95. return itemJson;
  96. }
  97. @end
  98. @interface QNReportRequestItem ()
  99. // API 请求类型,可选值有 "form","mkblk","bput","mkfile","put","init_parts","upload_part","complete_part","uc_query","httpdns_query"
  100. @property (nonatomic, copy) NSString *up_type;
  101. // 记录⽬标 Bucket 名称
  102. @property (nonatomic, copy) NSString *target_bucket;
  103. // 记录⽬标 Key 名称
  104. @property (nonatomic, copy) NSString *target_key;
  105. // 本次分片上传的偏移量,单位为字节
  106. @property (nonatomic, assign) int64_t file_offset;
  107. // ⽬标上传的区域 ID,可选值为 "z0","z1","z2","as0","na0" 等
  108. @property (nonatomic, copy) NSString *target_region_id;
  109. // 当前上传的区域 ID,可选值为 "z0","z1","z2","as0","na0" 等
  110. @property (nonatomic, copy) NSString *current_region_id;
  111. // 该域名通过预取 DNS 得到的 IP 地址数量(目前是0)
  112. @property (nonatomic, assign) int64_t prefetched_ip_count;
  113. // 当前进程 ID
  114. @property (nonatomic, assign) int64_t pid;
  115. // 当前线程 ID
  116. @property (nonatomic, assign) int64_t tid;
  117. // 当前平台的操作系统名称
  118. @property (nonatomic, copy) NSString *os_name;
  119. // 当前平台的操作系统版本号
  120. @property (nonatomic, copy) NSString *os_version;
  121. // 当前 SDK 名称,默认Object-C
  122. @property (nonatomic, copy) NSString *sdk_name;
  123. // 当前 SDK 版本号
  124. @property (nonatomic, copy) NSString *sdk_version;
  125. // 记录响应状态码
  126. @property (nonatomic, assign) int64_t status_code;
  127. // 记录响应中存储的 ReqId
  128. @property (nonatomic, copy) NSString *req_id;
  129. // 记录主机域名(不含解析,不含端⼝)
  130. @property (nonatomic, copy) NSString *host;
  131. // 记录成功建⽴连接的服务器 IP 地址
  132. @property (nonatomic, copy) NSString *remote_ip;
  133. // 记录主机端口号
  134. @property (nonatomic, assign) int64_t port;
  135. // 记录从发送请求到收到响应之间的单调时间差,单位为毫秒
  136. @property (nonatomic, assign) int64_t total_elapsed_time;
  137. // 记录⼀次请求中 DNS 查询的耗时,单位为毫秒,如果当前请求不需要进⾏ DNS 查询,则填写 0
  138. @property (nonatomic, assign) int64_t dns_elapsed_time;
  139. // 记录一次请求中建立⽹络连接的耗时,单位为毫秒,如果当前请求不不需要进行⽹络连接,则填写 0
  140. @property (nonatomic, assign) int64_t connect_elapsed_time;
  141. // 记录一次请求中建立安全⽹络连接的耗时,单位为毫秒(该耗时被 connect_elapsed_time 包含,因此总是⼩小于或等于 connect_elapsed_time,如果当前请求不需要进行安全连接,则填写 0)
  142. @property (nonatomic, assign) int64_t tls_connect_elapsed_time;
  143. // 记录⼀次请求中发送请求的耗时,单位为毫秒
  144. @property (nonatomic, assign) int64_t request_elapsed_time;
  145. // 记录⼀次请求中从发送请求完毕到收到响应前的耗时,单位为毫秒
  146. @property (nonatomic, assign) int64_t wait_elapsed_time;
  147. // 记录⼀次请求中读取响应的耗时,单位为毫秒
  148. @property (nonatomic, assign) int64_t response_elapsed_time;
  149. // 本次成功发送请求的请求体大小,单位为字节
  150. @property (nonatomic, assign) int64_t bytes_sent;
  151. // 预期发送请求的请求体大小,单位为字节
  152. @property (nonatomic, assign) int64_t bytes_total;
  153. // 错误类型
  154. @property (nonatomic, copy) NSString *error_type;
  155. // 对于服务器器成功响应,且响应体中包含 error 字段的,则给出 error 字段的内容。否则对于其他错误,则可以⾃自定义错误描述 信息
  156. @property (nonatomic, copy) NSString *error_description;
  157. // 请求结束时的⽹网络类型,可选值有 "wifi", "2g", "3g", "4g" 等。如果当前⽹网络不不可⽤用,则给出 "none"
  158. @property (nonatomic, copy) NSString *network_type;
  159. // 请求结束时的信号强度
  160. @property (nonatomic, assign) int64_t signal_strength;
  161. @end
  162. @implementation QNReportRequestItem
  163. - (instancetype)init
  164. {
  165. self = [super init];
  166. if (self) {
  167. self.log_type = @"request";
  168. }
  169. return self;
  170. }
  171. + (instancetype)buildWithUpType:(NSString *)up_type
  172. TargetBucket:(NSString *)target_bucket
  173. targetKey:(NSString *)target_key
  174. fileOffset:(int64_t)file_offset
  175. targetRegionId:(NSString *)target_region_id
  176. currentRegionId:(NSString *)current_region_id
  177. prefetchedIpCount:(int64_t)prefetched_ip_count
  178. pid:(int64_t)pid
  179. tid:(int64_t)tid
  180. statusCode:(int64_t)status_code
  181. reqId:(NSString *)req_id
  182. host:(NSString *)host
  183. remoteIp:(NSString *)remote_ip
  184. port:(int64_t)port
  185. totalElapsedTime:(int64_t)total_elapsed_time
  186. dnsElapsedTime:(int64_t)dns_elapsed_time
  187. connectElapsedTime:(int64_t)connect_elapsed_time
  188. tlsConnectElapsedTime:(int64_t)tls_connect_elapsed_time
  189. requestElapsedTime:(int64_t)request_elapsed_time
  190. waitElapsedTime:(int64_t)wait_elapsed_time
  191. responseElapsedTime:(int64_t)response_elapsed_time
  192. bytesSent:(int64_t)bytes_sent
  193. bytesTotal:(int64_t)bytes_total
  194. errorType:(NSString *)error_type
  195. errorDescription:(NSString *)error_description
  196. networkType:(NSString *)network_type
  197. signalStrength:(int64_t)signal_strength {
  198. QNReportRequestItem *item = [[QNReportRequestItem alloc] init];
  199. item.up_type = up_type;
  200. item.target_bucket = target_bucket;
  201. item.target_key = target_key;
  202. item.file_offset = file_offset;
  203. item.target_region_id = target_region_id;
  204. item.current_region_id = current_region_id;
  205. item.prefetched_ip_count = prefetched_ip_count;
  206. item.pid = pid;
  207. item.tid = tid;
  208. item.status_code = status_code;
  209. item.req_id = req_id;
  210. item.host = host;
  211. item.remote_ip = remote_ip;
  212. item.port = port;
  213. item.total_elapsed_time = total_elapsed_time;
  214. item.dns_elapsed_time = dns_elapsed_time;
  215. item.connect_elapsed_time = connect_elapsed_time;
  216. item.tls_connect_elapsed_time = tls_connect_elapsed_time;
  217. item.request_elapsed_time = request_elapsed_time;
  218. item.wait_elapsed_time = wait_elapsed_time;
  219. item.response_elapsed_time = response_elapsed_time;
  220. item.bytes_sent = bytes_sent;
  221. item.bytes_total = bytes_total;
  222. item.error_type = error_type;
  223. item.error_description = error_description;
  224. item.network_type = network_type;
  225. item.signal_strength = signal_strength;
  226. #if __IPHONE_OS_VERSION_MIN_REQUIRED
  227. item.os_name = [[UIDevice currentDevice] model];
  228. item.os_version = [[UIDevice currentDevice] systemVersion];
  229. #else
  230. item.os_name = @"Mac OS X";
  231. item.os_version = [[NSProcessInfo processInfo] operatingSystemVersionString];
  232. #endif
  233. item.sdk_name = @"Object-C";
  234. item.sdk_version = kQiniuVersion;
  235. return item;
  236. }
  237. @end
  238. // block type item - 用于统计分片上传整体质量信息
  239. @interface QNReportBlockItem ()
  240. // ⽬标上传的区域 ID,可选值为 "z0","z1","z2","as0","na0"
  241. @property (nonatomic, copy) NSString *target_region_id;
  242. // 当前上传的区域 ID,可选值为 "z0","z1","z2","as0","na0"
  243. @property (nonatomic, copy) NSString *current_region_id;
  244. // 记录对于当前上传的区域,从发送第一个请求到收到最后一个响应 之间的单调时间差,单位为毫秒
  245. @property (nonatomic, assign) int64_t total_elapsed_time;
  246. // 成功上传⾄服务器的分块尺寸总和,单位为字节
  247. @property (nonatomic, assign) int64_t bytes_sent;
  248. // 上次失败时已上传的文件尺⼨(也就是上传恢复点),单位为字节
  249. @property (nonatomic, assign) int64_t recovered_from;
  250. // 要上传的文件总尺寸,单位为字节
  251. @property (nonatomic, assign) int64_t file_size;
  252. // 当前进程 ID
  253. @property (nonatomic, assign) int64_t pid;
  254. // 当前线程 ID
  255. @property (nonatomic, assign) int64_t tid;
  256. // 分⽚上传 API 版本,可选值为 1 和 2
  257. @property (nonatomic, assign) int64_t up_api_version;
  258. @end
  259. @implementation QNReportBlockItem
  260. - (instancetype)init
  261. {
  262. self = [super init];
  263. if (self) {
  264. self.log_type = @"block";
  265. }
  266. return self;
  267. }
  268. + (instancetype)buildWithTargetRegionId:(NSString *)target_region_id
  269. currentRegionId:(NSString *)current_region_id
  270. totalElapsedTime:(int64_t)total_elapsed_time
  271. bytesSent:(int64_t)bytes_sent
  272. recoveredFrom:(int64_t)recovered_from
  273. fileSize:(int64_t)file_size
  274. pid:(int64_t)pid
  275. tid:(int64_t)tid
  276. upApiVersion:(int64_t)up_api_version {
  277. QNReportBlockItem *item = [[QNReportBlockItem alloc] init];
  278. item.target_region_id = target_region_id;
  279. item.current_region_id = current_region_id;
  280. item.total_elapsed_time = total_elapsed_time;
  281. item.bytes_sent = bytes_sent;
  282. item.recovered_from = recovered_from;
  283. item.file_size = file_size;
  284. item.pid = pid;
  285. item.tid = tid;
  286. item.up_api_version = up_api_version;
  287. return item;
  288. }
  289. @end
  290. // quality type item - 用于统计上传结果
  291. @interface QNReportQualityItem ()
  292. // 记录上传结果
  293. @property (nonatomic, copy) NSString *result;
  294. // 记录对于当前上传的⽂文件,从发送第⼀个请求到收到最后⼀个响应之间的单调时间差,单位为毫秒
  295. @property (nonatomic, assign) int64_t total_elapsed_time;
  296. // 为了完成本次上传所发出的 HTTP 请求总数(含 UC Query 和 HTTPDNS Query)
  297. @property (nonatomic, assign) int64_t requests_count;
  298. // 为了完成本次上传所使用的区域数量
  299. @property (nonatomic, assign) int64_t regions_count;
  300. // 为了完成本次上传所发出的 HTTP 请求体尺寸总量(含 UC Query 和 HTTPDNS Query)
  301. @property (nonatomic, assign) int64_t bytes_sent;
  302. @end
  303. @implementation QNReportQualityItem
  304. - (instancetype)init
  305. {
  306. self = [super init];
  307. if (self) {
  308. self.log_type = @"quality";
  309. }
  310. return self;
  311. }
  312. + (instancetype)buildWithResult:(NSString *)result
  313. totalElapsedTime:(int64_t)total_elapsed_time
  314. requestsCount:(int64_t)requests_count
  315. regionsCount:(int64_t)regions_count
  316. bytesSent:(int64_t)bytes_sent {
  317. QNReportQualityItem *item = [[QNReportQualityItem alloc] init];
  318. item.result = result;
  319. item.total_elapsed_time = total_elapsed_time;
  320. item.requests_count = requests_count;
  321. item.regions_count = regions_count;
  322. item.bytes_sent = bytes_sent;
  323. return item;
  324. }
  325. @end
  326. @implementation QNReportConfig
  327. + (instancetype)sharedInstance {
  328. static QNReportConfig *sharedInstance = nil;
  329. static dispatch_once_t onceToken;
  330. dispatch_once(&onceToken, ^{
  331. sharedInstance = [[self alloc] init];
  332. });
  333. return sharedInstance;
  334. }
  335. - (instancetype)init {
  336. self = [super init];
  337. if (self) {
  338. _reportEnable = YES;
  339. _interval = 10;
  340. _serverURL = @"https://uplog.qbox.me/log/4";
  341. _recordDirectory = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"com.qiniu.report"];
  342. _maxRecordFileSize = 2 * 1024 * 1024;
  343. _uploadThreshold = 4 * 1024;
  344. _timeoutInterval = 10;
  345. }
  346. return self;
  347. }
  348. @end
  349. static const NSString *recorderFileName = @"recorder";
  350. static const NSString *reportTypeValueList[] = {@"form", @"mkblk", @"bput", @"mkfile", @"block"};
  351. @interface QNUploadInfoReporter ()
  352. @property (nonatomic, strong) QNReportConfig *config;
  353. @property (nonatomic, assign) NSTimeInterval lastReportTime;
  354. @property (nonatomic, strong) NSFileManager *fileManager;
  355. @property (nonatomic, strong) NSString *recorderFilePath;
  356. @property (nonatomic, strong) dispatch_queue_t recordQueue;
  357. @property (nonatomic, strong) dispatch_semaphore_t semaphore;
  358. @property (nonatomic, copy, readwrite) NSString *X_Log_Client_Id;
  359. @end
  360. @implementation QNUploadInfoReporter
  361. + (instancetype)sharedInstance {
  362. static QNUploadInfoReporter *sharedInstance = nil;
  363. static dispatch_once_t onceToken;
  364. dispatch_once(&onceToken, ^{
  365. sharedInstance = [[self alloc] init];
  366. });
  367. return sharedInstance;
  368. }
  369. - (instancetype)init {
  370. self = [super init];
  371. if (self) {
  372. _config = [QNReportConfig sharedInstance];
  373. _lastReportTime = 0;
  374. _recorderFilePath = [NSString stringWithFormat:@"%@/%@", _config.recordDirectory, recorderFileName];
  375. _fileManager = [NSFileManager defaultManager];
  376. _recordQueue = dispatch_queue_create("com.qiniu.reporter", DISPATCH_QUEUE_SERIAL);
  377. }
  378. return self;
  379. }
  380. - (void)clean {
  381. if ([_fileManager fileExistsAtPath:_recorderFilePath]) {
  382. NSError *error = nil;
  383. [_fileManager removeItemAtPath:_recorderFilePath error:&error];
  384. if (error) {
  385. NSLog(@"remove recorder file failed: %@", error);
  386. return;
  387. }
  388. }
  389. }
  390. - (BOOL)checkReportAvailable {
  391. if (!_config.isReportEnable) return NO;
  392. if (!(_config.maxRecordFileSize > _config.uploadThreshold)) {
  393. NSLog(@"maxRecordFileSize must be larger than uploadThreshold");
  394. return NO;
  395. }
  396. return YES;
  397. }
  398. - (void)report:(NSString *)jsonString token:(NSString *)token {
  399. if (![self checkReportAvailable] || !jsonString) return;
  400. // 串行队列处理文件读写
  401. dispatch_async(_recordQueue, ^{
  402. [self innerReport:jsonString token:token];
  403. });
  404. }
  405. - (void)innerReport:(NSString *)jsonString token:(NSString *)token {
  406. // 检查recorder文件夹是否存在
  407. NSError *error = nil;
  408. if (![_fileManager fileExistsAtPath:_config.recordDirectory]) {
  409. [_fileManager createDirectoryAtPath:_config.recordDirectory withIntermediateDirectories:YES attributes:nil error:&error];
  410. if (error) {
  411. NSLog(@"create record directory failed, please check record directory: %@", error.localizedDescription);
  412. return;
  413. }
  414. }
  415. // 拼接换行符
  416. NSString *finalRecordInfo = [jsonString stringByAppendingString:@"\n"];
  417. if (![_fileManager fileExistsAtPath:_recorderFilePath]) {
  418. // 如果recordFile不存在,创建文件并写入首行,首次不上传
  419. [finalRecordInfo writeToFile:_recorderFilePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
  420. } else {
  421. // recordFile存在,拼接文件内容、上传到服务器
  422. QNFile *file = [[QNFile alloc] init:_recorderFilePath error:&error];
  423. if (error) {
  424. NSLog(@"create QNFile with path failed: %@", error.localizedDescription);
  425. return;
  426. }
  427. // 判断recorder文件大小是否超过maxRecordFileSize
  428. if (file.size < _config.maxRecordFileSize) {
  429. @try {
  430. // 上传信息写入recorder文件
  431. NSFileHandle *fileHandler = [NSFileHandle fileHandleForUpdatingAtPath:_recorderFilePath];
  432. [fileHandler seekToEndOfFile];
  433. [fileHandler writeData: [finalRecordInfo dataUsingEncoding:NSUTF8StringEncoding]];
  434. [fileHandler closeFile];
  435. } @catch (NSException *exception) {
  436. NSLog(@"NSFileHandle cannot write data: %@", exception.description);
  437. }
  438. }
  439. // 判断是否满足上传条件:文件大于上报临界值 && (首次上传 || 距上次上传时间大于_config.interval)
  440. NSTimeInterval currentTime = [[NSDate dateWithTimeIntervalSinceNow:0] timeIntervalSince1970];
  441. if (file.size > _config.uploadThreshold && (_lastReportTime == 0 || currentTime - _lastReportTime > _config.interval * 60)) {
  442. NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:_config.serverURL]];
  443. [request setValue:[NSString stringWithFormat:@"UpToken %@", token] forHTTPHeaderField:@"Authorization"];
  444. [request setValue:[[QNUserAgent sharedInstance] getUserAgent:[QNUpToken parse:token].access] forHTTPHeaderField:@"User-Agent"];
  445. if (self.X_Log_Client_Id) {
  446. [request setValue:self.X_Log_Client_Id forHTTPHeaderField:@"X-Log-Client-Id"];
  447. }
  448. [request setHTTPMethod:@"POST"];
  449. [request setTimeoutInterval:_config.timeoutInterval];
  450. __block NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
  451. NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromFile:[NSURL fileURLWithPath:_recorderFilePath] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
  452. NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
  453. if (httpResponse.statusCode == 200) {
  454. self.lastReportTime = [[NSDate dateWithTimeIntervalSinceNow:0] timeIntervalSince1970];
  455. NSDictionary *respHeader = httpResponse.allHeaderFields;
  456. if (!self.X_Log_Client_Id && [respHeader.allKeys containsObject:@"x-log-client-id"]) {
  457. self.X_Log_Client_Id = respHeader[@"x-log-client-id"];
  458. }
  459. [self clean];
  460. } else {
  461. NSLog(@"upload info report failed: %@", error.localizedDescription);
  462. }
  463. [session finishTasksAndInvalidate];
  464. dispatch_semaphore_signal(self.semaphore);
  465. }];
  466. [uploadTask resume];
  467. // 控制上传过程中,文件内容不被修改
  468. _semaphore = dispatch_semaphore_create(0);
  469. dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
  470. }
  471. }
  472. }
  473. @end