JXCrashHandler.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. //
  2. // JXCrashHandler.m
  3. // AICity
  4. //
  5. // Feature: 003-ios-api-https
  6. // 剧星平台崩溃处理器
  7. //
  8. #import "JXCrashHandler.h"
  9. #import "JXNetworkManager.h"
  10. #import <UIKit/UIKit.h>
  11. #import <sys/utsname.h>
  12. #import <mach/mach.h>
  13. @implementation JXCrashInfo
  14. @end
  15. @interface JXCrashHandler ()
  16. @property (nonatomic, assign) BOOL isInstalled;
  17. @property (nonatomic, strong) NSDateFormatter *dateFormatter;
  18. @end
  19. @implementation JXCrashHandler
  20. + (instancetype)sharedHandler {
  21. static JXCrashHandler *sharedInstance = nil;
  22. static dispatch_once_t onceToken;
  23. dispatch_once(&onceToken, ^{
  24. sharedInstance = [[JXCrashHandler alloc] init];
  25. });
  26. return sharedInstance;
  27. }
  28. - (instancetype)init {
  29. self = [super init];
  30. if (self) {
  31. _isInstalled = NO;
  32. _dateFormatter = [[NSDateFormatter alloc] init];
  33. _dateFormatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";
  34. _dateFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
  35. }
  36. return self;
  37. }
  38. #pragma mark - 崩溃处理器安装/卸载
  39. - (void)installCrashHandler {
  40. if (self.isInstalled) {
  41. NSLog(@"JXCrashHandler already installed");
  42. return;
  43. }
  44. // 安装异常处理器
  45. NSSetUncaughtExceptionHandler(&JXUncaughtExceptionHandler);
  46. // 安装信号处理器
  47. [self installSignalHandler];
  48. self.isInstalled = YES;
  49. NSLog(@"JXCrashHandler installed successfully");
  50. }
  51. - (void)uninstallCrashHandler {
  52. if (!self.isInstalled) {
  53. return;
  54. }
  55. // 移除异常处理器
  56. NSSetUncaughtExceptionHandler(NULL);
  57. // 移除信号处理器
  58. [self uninstallSignalHandler];
  59. self.isInstalled = NO;
  60. NSLog(@"JXCrashHandler uninstalled");
  61. }
  62. - (void)installSignalHandler {
  63. signal(SIGABRT, JXSignalHandler);
  64. signal(SIGILL, JXSignalHandler);
  65. signal(SIGSEGV, JXSignalHandler);
  66. signal(SIGFPE, JXSignalHandler);
  67. signal(SIGBUS, JXSignalHandler);
  68. signal(SIGPIPE, JXSignalHandler);
  69. }
  70. - (void)uninstallSignalHandler {
  71. signal(SIGABRT, SIG_DFL);
  72. signal(SIGILL, SIG_DFL);
  73. signal(SIGSEGV, SIG_DFL);
  74. signal(SIGFPE, SIG_DFL);
  75. signal(SIGBUS, SIG_DFL);
  76. signal(SIGPIPE, SIG_DFL);
  77. }
  78. #pragma mark - 崩溃处理函数
  79. void JXUncaughtExceptionHandler(NSException *exception) {
  80. JXCrashHandler *handler = [JXCrashHandler sharedHandler];
  81. [handler handleException:exception];
  82. }
  83. void JXSignalHandler(int signal) {
  84. JXCrashHandler *handler = [JXCrashHandler sharedHandler];
  85. [handler handleSignal:signal];
  86. }
  87. - (void)handleException:(NSException *)exception {
  88. @try {
  89. JXCrashInfo *crashInfo = [[JXCrashInfo alloc] init];
  90. crashInfo.timestamp = [[NSDate date] timeIntervalSince1970];
  91. crashInfo.exceptionName = exception.name ?: @"Unknown";
  92. crashInfo.exceptionReason = exception.reason ?: @"Unknown";
  93. crashInfo.callStack = exception.callStackSymbols ?: @[];
  94. crashInfo.deviceInfo = [self collectDeviceInfo];
  95. crashInfo.appInfo = [self collectAppInfo];
  96. // 保存到本地
  97. [self saveCrashInfoToLocal:crashInfo];
  98. // 异步上报
  99. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  100. [self reportCrashInfo:crashInfo];
  101. });
  102. NSLog(@"JXCrashHandler: Exception handled - %@", exception.name);
  103. } @catch (NSException *e) {
  104. NSLog(@"JXCrashHandler: Error handling exception - %@", e);
  105. }
  106. }
  107. - (void)handleSignal:(int)sig {
  108. @try {
  109. NSString *signalName = [self signalNameForSignal:sig];
  110. JXCrashInfo *crashInfo = [[JXCrashInfo alloc] init];
  111. crashInfo.timestamp = [[NSDate date] timeIntervalSince1970];
  112. crashInfo.exceptionName = [NSString stringWithFormat:@"Signal %d", sig];
  113. crashInfo.exceptionReason = signalName;
  114. crashInfo.callStack = [NSThread callStackSymbols];
  115. crashInfo.deviceInfo = [self collectDeviceInfo];
  116. crashInfo.appInfo = [self collectAppInfo];
  117. // 保存到本地
  118. [self saveCrashInfoToLocal:crashInfo];
  119. // 同步上报(信号处理中不能使用异步)
  120. [self reportCrashInfo:crashInfo];
  121. NSLog(@"JXCrashHandler: Signal handled - %@", signalName);
  122. } @catch (NSException *e) {
  123. NSLog(@"JXCrashHandler: Error handling signal - %@", e);
  124. }
  125. // 恢复默认信号处理并重新发送信号
  126. signal(sig, SIG_DFL);
  127. raise(sig);
  128. }
  129. - (NSString *)signalNameForSignal:(int)signal {
  130. switch (signal) {
  131. case SIGABRT: return @"SIGABRT";
  132. case SIGILL: return @"SIGILL";
  133. case SIGSEGV: return @"SIGSEGV";
  134. case SIGFPE: return @"SIGFPE";
  135. case SIGBUS: return @"SIGBUS";
  136. case SIGPIPE: return @"SIGPIPE";
  137. default: return [NSString stringWithFormat:@"Signal %d", signal];
  138. }
  139. }
  140. #pragma mark - 信息收集
  141. - (NSDictionary *)collectDeviceInfo {
  142. struct utsname systemInfo;
  143. uname(&systemInfo);
  144. UIDevice *device = [UIDevice currentDevice];
  145. // 获取内存信息
  146. mach_port_t host_port = mach_host_self();
  147. mach_msg_type_number_t host_size = sizeof(vm_statistics_data_t) / sizeof(natural_t);
  148. vm_size_t pagesize;
  149. vm_statistics_data_t vm_stat;
  150. host_page_size(host_port, &pagesize);
  151. host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size);
  152. unsigned long long totalMemory = [NSProcessInfo processInfo].physicalMemory;
  153. unsigned long long freeMemory = (unsigned long long)vm_stat.free_count * pagesize;
  154. return @{
  155. @"device_model": [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding],
  156. @"system_name": device.systemName,
  157. @"system_version": device.systemVersion,
  158. @"device_name": device.name,
  159. @"identifier_for_vendor": device.identifierForVendor.UUIDString ?: @"unknown",
  160. @"total_memory": @(totalMemory),
  161. @"free_memory": @(freeMemory),
  162. @"used_memory": @(totalMemory - freeMemory),
  163. @"battery_level": @(device.batteryLevel),
  164. @"battery_state": @(device.batteryState)
  165. };
  166. }
  167. - (NSDictionary *)collectAppInfo {
  168. NSBundle *mainBundle = [NSBundle mainBundle];
  169. NSDictionary *infoDictionary = mainBundle.infoDictionary;
  170. return @{
  171. @"bundle_identifier": mainBundle.bundleIdentifier ?: @"unknown",
  172. @"app_version": infoDictionary[@"CFBundleShortVersionString"] ?: @"unknown",
  173. @"build_number": infoDictionary[@"CFBundleVersion"] ?: @"unknown",
  174. @"executable_name": infoDictionary[@"CFBundleExecutable"] ?: @"unknown",
  175. @"bundle_name": infoDictionary[@"CFBundleName"] ?: @"unknown",
  176. @"process_id": @([[NSProcessInfo processInfo] processIdentifier]),
  177. @"process_name": [[NSProcessInfo processInfo] processName],
  178. @"launch_time": @([[NSProcessInfo processInfo] systemUptime])
  179. };
  180. }
  181. #pragma mark - 本地存储
  182. - (void)saveCrashInfoToLocal:(JXCrashInfo *)crashInfo {
  183. @try {
  184. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  185. NSString *documentsDirectory = [paths objectAtIndex:0];
  186. NSString *crashDirectory = [documentsDirectory stringByAppendingPathComponent:@"JXCrashes"];
  187. // 创建崩溃日志目录
  188. NSFileManager *fileManager = [NSFileManager defaultManager];
  189. if (![fileManager fileExistsAtPath:crashDirectory]) {
  190. [fileManager createDirectoryAtPath:crashDirectory withIntermediateDirectories:YES attributes:nil error:nil];
  191. }
  192. // 生成文件名
  193. NSDate *crashDate = [NSDate dateWithTimeIntervalSince1970:crashInfo.timestamp];
  194. NSString *dateString = [self.dateFormatter stringFromDate:crashDate];
  195. dateString = [dateString stringByReplacingOccurrencesOfString:@" " withString:@"_"];
  196. dateString = [dateString stringByReplacingOccurrencesOfString:@":" withString:@"-"];
  197. NSString *fileName = [NSString stringWithFormat:@"crash_%@.txt", dateString];
  198. NSString *filePath = [crashDirectory stringByAppendingPathComponent:fileName];
  199. // 格式化崩溃信息
  200. NSString *crashReport = [self formatCrashInfo:crashInfo];
  201. // 写入文件
  202. [crashReport writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
  203. NSLog(@"JXCrashHandler: Crash info saved to %@", filePath);
  204. } @catch (NSException *e) {
  205. NSLog(@"JXCrashHandler: Failed to save crash info - %@", e);
  206. }
  207. }
  208. - (NSString *)formatCrashInfo:(JXCrashInfo *)crashInfo {
  209. NSMutableString *report = [NSMutableString string];
  210. [report appendString:@"=== JuXing Crash Report ===\n"];
  211. NSDate *crashDate = [NSDate dateWithTimeIntervalSince1970:crashInfo.timestamp];
  212. [report appendFormat:@"Timestamp: %@\n", [self.dateFormatter stringFromDate:crashDate]];
  213. [report appendFormat:@"Exception: %@\n", crashInfo.exceptionName];
  214. [report appendFormat:@"Reason: %@\n", crashInfo.exceptionReason];
  215. [report appendString:@"\n"];
  216. [report appendString:@"=== Device Info ===\n"];
  217. for (NSString *key in crashInfo.deviceInfo) {
  218. [report appendFormat:@"%@: %@\n", key, crashInfo.deviceInfo[key]];
  219. }
  220. [report appendString:@"\n"];
  221. [report appendString:@"=== App Info ===\n"];
  222. for (NSString *key in crashInfo.appInfo) {
  223. [report appendFormat:@"%@: %@\n", key, crashInfo.appInfo[key]];
  224. }
  225. [report appendString:@"\n"];
  226. [report appendString:@"=== Call Stack ===\n"];
  227. for (NSString *symbol in crashInfo.callStack) {
  228. [report appendFormat:@"%@\n", symbol];
  229. }
  230. return report;
  231. }
  232. #pragma mark - 崩溃上报
  233. - (void)reportCrashInfo:(JXCrashInfo *)crashInfo {
  234. @try {
  235. NSDictionary *crashReport = @{
  236. @"timestamp": @(crashInfo.timestamp),
  237. @"exception_name": crashInfo.exceptionName,
  238. @"exception_reason": crashInfo.exceptionReason,
  239. @"call_stack": crashInfo.callStack,
  240. @"device_info": crashInfo.deviceInfo,
  241. @"app_info": crashInfo.appInfo,
  242. @"platform": @"ios"
  243. };
  244. // 这里应该调用实际的API接口上报崩溃信息
  245. // JXNetworkManager *networkManager = [JXNetworkManager sharedManager];
  246. // [networkManager POST:@"monitor/crash" parameters:crashReport success:^(id responseObject) {
  247. // NSLog(@"JXCrashHandler: Crash report sent successfully");
  248. // } failure:^(NSError *error) {
  249. // NSLog(@"JXCrashHandler: Failed to send crash report - %@", error);
  250. // }];
  251. NSLog(@"JXCrashHandler: Crash report prepared for upload");
  252. } @catch (NSException *e) {
  253. NSLog(@"JXCrashHandler: Failed to report crash info - %@", e);
  254. }
  255. }
  256. #pragma mark - 崩溃日志管理
  257. - (NSArray<NSString *> *)getLocalCrashLogs {
  258. @try {
  259. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  260. NSString *documentsDirectory = [paths objectAtIndex:0];
  261. NSString *crashDirectory = [documentsDirectory stringByAppendingPathComponent:@"JXCrashes"];
  262. NSFileManager *fileManager = [NSFileManager defaultManager];
  263. if (![fileManager fileExistsAtPath:crashDirectory]) {
  264. return @[];
  265. }
  266. NSArray *files = [fileManager contentsOfDirectoryAtPath:crashDirectory error:nil];
  267. NSMutableArray *crashLogs = [NSMutableArray array];
  268. for (NSString *fileName in files) {
  269. if ([fileName hasPrefix:@"crash_"] && [fileName hasSuffix:@".txt"]) {
  270. NSString *filePath = [crashDirectory stringByAppendingPathComponent:fileName];
  271. [crashLogs addObject:filePath];
  272. }
  273. }
  274. // 按修改时间排序
  275. [crashLogs sortUsingComparator:^NSComparisonResult(NSString *path1, NSString *path2) {
  276. NSDictionary *attrs1 = [fileManager attributesOfItemAtPath:path1 error:nil];
  277. NSDictionary *attrs2 = [fileManager attributesOfItemAtPath:path2 error:nil];
  278. NSDate *date1 = attrs1[NSFileModificationDate];
  279. NSDate *date2 = attrs2[NSFileModificationDate];
  280. return [date2 compare:date1]; // 降序排列
  281. }];
  282. return [crashLogs copy];
  283. } @catch (NSException *e) {
  284. NSLog(@"JXCrashHandler: Failed to get local crash logs - %@", e);
  285. return @[];
  286. }
  287. }
  288. - (void)cleanupOldCrashLogs:(NSTimeInterval)maxAge {
  289. @try {
  290. NSArray *crashLogs = [self getLocalCrashLogs];
  291. NSFileManager *fileManager = [NSFileManager defaultManager];
  292. NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970];
  293. NSInteger deletedCount = 0;
  294. for (NSString *filePath in crashLogs) {
  295. NSDictionary *attributes = [fileManager attributesOfItemAtPath:filePath error:nil];
  296. NSDate *modificationDate = attributes[NSFileModificationDate];
  297. NSTimeInterval fileAge = currentTime - [modificationDate timeIntervalSince1970];
  298. if (fileAge > maxAge) {
  299. if ([fileManager removeItemAtPath:filePath error:nil]) {
  300. deletedCount++;
  301. }
  302. }
  303. }
  304. NSLog(@"JXCrashHandler: Cleaned up %ld old crash logs", (long)deletedCount);
  305. } @catch (NSException *e) {
  306. NSLog(@"JXCrashHandler: Failed to cleanup old crash logs - %@", e);
  307. }
  308. }
  309. @end