iOS Crash原理及日志收集

作者: 哦小小树 | 来源:发表于2020-04-23 16:36 被阅读0次

    0x01 常见的Crash类型

    KVO
    NSNotification
    数组越界
    内存爆炸
    野指针
    后台任务超时
    主线程卡顿超出阈值
    死锁
    CPU消耗过大


    0x02 系统提供的异常捕获

    NSUncaughtExceptionHandler

    示例代码

    
    - (void)crashMonitor {
        NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);   // 添加异常监听句柄
    }
    
    void UncaughtExceptionHandler(NSException *exception) {
        NSString *name = [exception name];
        NSString *reason = [exception reason];
        NSArray *stackTrace = [exception callStackSymbols];
        NSString *crashInfo = [NSString stringWithFormat:@"\n%@\n%@\n%@\n",name,reason, stackTrace];
        
        [[CrashManager shareInstance] saveCrashToFile:crashInfo];  // 存储crash文件
    }
    
    
    Signal捕获

    操作逻辑

    1. iOS系统出现crash的时候会发出signal
    2. 注册signal及回调配置【句柄】
    // 注册句柄, SIG_Flag为要监听的signal, handleCallBack为回调函数
    signal(SIG_Flag, handleCallBack)
    

    示例代码

        // signal.h中包含所有指令
        signal(SIGABRT, handleSignal);      // 调用abort
        signal(SIGILL, handleSignal);       // 非法指令
        signal(SIGSEGV, handleSignal);      // 无效内存引用
        signal(SIGPIPE, handleSignal);      // 端口消息发送失败
    

    如果需要测试,需要再接收信号前通过lldb配置:
    pro hand -p true -s false SIGABRT

    void handleSignal(int signal) {
        NSLog(@"中断信信号:%@",signal);
        // 打印函数调用栈
    }
    
    

    这内部处理信号的逻辑涉及到各种内核,线程状态操作。后续研究再来完善。


    0x03 日志记录

    • 写入本地文件
    // 日志目录路径
    - (NSString *)logDirectory {
        NSString *directory =  [NSHomeDirectory() stringByAppendingPathComponent:@"log"];
        
        if (![[NSFileManager defaultManager] fileExistsAtPath:directory]) {
            [[NSFileManager defaultManager] createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:nil];
        }
        return directory;
    }
    // 日志文件信息
    - (NSString *)crashFilePath {
        return [[self logDirectory] stringByAppendingPathComponent:@"crash.log"];
    }
    // 保存日志信息
    - (void)saveCrashToFile:(NSString *)crashInfo {
        NSFileManager *manager = [NSFileManager defaultManager];
        if (![manager fileExistsAtPath:[self crashFilePath]]) {
            [manager createFileAtPath:[self crashFilePath] contents:nil attributes:nil];
        }
        NSFileHandle *handler = [NSFileHandle fileHandleForWritingAtPath:[self crashFilePath]];
        [handler seekToEndOfFile];  // 拼接形式操作
        [handler writeData:[crashInfo dataUsingEncoding:NSUTF8StringEncoding]];
    }
    
    • 压缩上传
      先获取需要压缩的日志文件
    - (NSArray *)sortedCrashFiles {
        // 目录中所有日志文件
        NSFileManager *mana = [NSFileManager defaultManager];
        if (![mana fileExistsAtPath:[self logDirectory]]) return nil;
        // 返回文件名列表
        NSArray *filesArr = [mana contentsOfDirectoryAtPath:[self logDirectory] error:nil];
        // 如果需要对文件做些筛选可以在此处处理
        
        NSArray *sortArr = [filesArr sortedArrayUsingComparator:^NSComparisonResult(NSString *  _Nonnull obj1, NSString *  _Nonnull obj2) {
            // 配置全路径,找到文件,以文件创建时间进行排序
    //      NSString *filePath1 = [[self logDirectory] stringByAppendingPathComponent:obj1];
    //      NSString *filePath2 = [[self logDirectory] stringByAppendingPathComponent:obj2];
    //
    //      NSDictionary *file1Dcit = [mana attributesOfItemAtPath:filePath1 error:nil];
    //      NSDictionary *file2Dict = [mana attributesOfItemAtPath:filePath2 error:nil];
    //
    //      NSDate *file1Date = [file1Dcit objectForKey:NSFileCreationDate];
    //      NSDate *file2Date = [file2Dict objectForKey:NSFileCreationDate];
    //      return [file1Date compare:file2Date];
            return [obj1 compare:obj2];     // 直接按照文件名进行排序
        }];
        
        NSMutableArray *tmpArr = @[].mutableCopy;
        for (NSString *fileName in sortArr) {  // 拼接为全路径
            [tmpArr addObject:[[self logDirectory] stringByAppendingPathComponent:fileName]];
        }
        return tmpArr;
        
    }
    
    

    使用SSZipArchive进行资源压缩

    - (NSString *)zipFileForAllCrash {
        // 压缩所有的crash文件, 返回压缩好的文件路径
        NSArray *sortArray = [self sortedCrashFiles];
        
        if (sortArray.count <= 0) return nil;
        // 创建一个压缩文件路径
        NSString *crashZipPath = [[self logDirectory] stringByAppendingPathComponent:[[self timeString] stringByAppendingString:@".zip"]];
        
        // 根据路径压缩所有文件信息
        BOOL ret = [SSZipArchive createZipFileAtPath:crashZipPath withFilesAtPaths:sortArray];
        
        if (!ret) { // 压缩失败
            crashZipPath = nil;
            NSLog(@"创建压缩文件失败");
        }
        return crashZipPath;
    }
    

    获取到压缩后的路径可以执行上传操作,当提交成功后就可以删除本地日志信息

    - (void)clearAllCacheLogFile {
        NSFileManager *mana = [NSFileManager defaultManager];
        // 删除所有文件
        [mana removeItemAtPath:[self logDirectory] error:nil];
    }
    

    示例代码
    https://github.com/linfengwenyou/CrashCollectTest


    0x03 主流的崩溃日志收集框架


    参考资料:
    https://blog.csdn.net/weixin_36139431/article/details/95358484
    https://www.it610.com/article/1191455498289913856.htm

    相关文章

      网友评论

        本文标题:iOS Crash原理及日志收集

        本文链接:https://www.haomeiwen.com/subject/bvswihtx.html