iOS错误日志收集及分析

    • 错误日志收集
      • 崩溃日志收集
      • 自定义其他错误日志上传
    • 崩溃日志分析

错误日志收集

    很多APP统计分析SDK都集成了崩溃日志收集功能,如“百度移动统计SDK”。但由于各种原因,这些有时候并不能满足我们的需求,比如我还想知道用户在什么网络状态下崩溃,什么位置崩溃,什么时间崩溃,崩溃前都请求了哪些接口(这个需要跟后台接口请求日志联合分析),而且有时候我们还想知道除了崩溃之外的错误信息,如请求某个重要接口失败原因,用户某个操作失败频率及原因,用户使用APP时候的性能等等。
    所以我们要自定义符合自己需求的错误日志收集策略,我们公司就是把前端后端移动端的日志都存入了同一个数据库,然后就可以进行联合各端的日志进行错误分析。举个例子,我得到了一个iOS端崩溃日志,然后还可以知道这个用户在崩溃之前5分钟,请求了哪些接口,执行了哪些操作,非常方便,只需要一个SQL语句就能实现。
     废话说了这么多,下面说一下iOS端要怎么去收集。总体来说就是崩溃之后获取到奔溃信息,然后把信息存入沙盒文件(可以txt,也可以用sqlite,plist文件都可以,我用最简单的txt来举例),然后在下一次打开APP的时候判断沙盒里面有没有错误日志,有就上传,上传成功后清空错误日志。注意:千万不要崩溃时直接上传到服务器,来不及上传到后台APP进程就结束了,要先写入沙盒,然后下一次打开APP再上传。

崩溃日志收集

在AppDelegate实现如下方法

// 异常日志获取,崩溃把日志写入本地,等下次开启app再上传
void UncaughtExceptionHandler(NSException *exception){

    NSArray  *excpArr = [exception callStackSymbols];
    NSString *reason = [exception reason];
    NSString *name = [exception name];
    NSDictionary * userInfo = [exception userInfo];
    NSString *excpCnt = [NSString stringWithFormat:@"exceptionType: %@ \n reason: %@ \n stackSymbols: %@ \n userInfo: %@",name,reason,excpArr,userInfo];
    //NSDOCUMENTPATH是沙盒路径
    NSString * errorMessageFile = [NSString stringWithFormat:@"%@/error.txt",NSDOCUMENTPATH];
    //将崩溃信息写入沙盒里的error.txt文件
    [excpCnt writeToFile:errorMessageFile atomically:YES encoding:NSUTF8StringEncoding error:nil];
}

然后在启动APP的时候判断日志是否存在,如果存在就上传,上传成功后删除

 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
    //上传崩溃日志
    NSString * errorMessageFile = [NSString stringWithFormat:@"%@/error.txt",NSDOCUMENTPATH];
    if ([[NSFileManager defaultManager]fileExistsAtPath:errorMessageFile]) {

        [HttpTool uploadErrorMessage:[[NSString alloc]initWithContentsOfFile:errorMessageFile encoding:NSUTF8StringEncoding error:nil] Success:^(NSDictionary *resultDic) {
        //上传成功后删除
        [[NSFileManager defaultManager]removeItemAtPath:errorMessageFile error:nil];
        } failure:^(NSError *error) {

        }];
    }}
}

自定义其他错误日志上传

     这里简单说一下除了崩溃日志外其他错误信息收集。咱们本地真机调试可以NSLog输出来排查问题,但是线上的APP这些东西咱们获取不到,所以有必要将一些重要的信息打成日志上传到服务端,信息越详细,越能方便我们在后台排查问题,但是最好筛选出比较重要的信息,比较这种做法是在浪费用户的流量。
     我们之前做过一个问卷调查的APP,问卷完全答完才把答案上传服务器,但是答到一半有可能会因为问卷设置有问题或者用户不符合条件直接退出答题,后来在这个地方加了个日志,保存一些用户的操作记录,直接就可以从后台分析到底哪里出了问题。这个不用写入本地,直接上传服务器即可。
     一些特殊的接口请求失败的时候我也会把失败信息,原因,接口地址,参数,网络状态等存到错误日志里等下次打开APP就上传。

崩溃日志分析

网上很多用dSYM文件进行日志分析,我这里直接用.app文件就可以。我看了一些资料,自己写了一个demo进行日志分析,每次只需要改变一下内存地址就可以了,下面教大家由.ipa文件拿到.app文件,然后贴一下代码
1.首先把ipa文件的后缀名,改成.zip,然后解压后得到一个Payload文件夹,然后里面有一个文件,就是你的app文件。
2.下面的代码,把你的APP路径改成刚才得到的那个.app文件,然后改一下内存地址和偏移量,运行,就可以定位到崩溃到哪个.m文件第几行了。下面的例子中,0x000000010016be8c是内存地址,605836是偏移量。
exceptionType: NSRangeException
reason: * -[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array
stackSymbols: [
0 CoreFoundation 0x0000000185b4654c 160,
1 libobjc.A.dylib 0x0000000196b1c0e4 objc_exception_throw 60,
2 CoreFoundation 0x0000000185a2b77c 0,
3 SFCIQuestionnaire 0x0000000100184d2c SFCIQuestionnaire 707884,
4 UIKit 0x000000018a47ce48 1280,
5 UIKit 0x000000018a53a6dc 276,
6 UIKit 0x000000018a3d6b8c 356,
7 UIKit 0x000000018a3485f0 532,
8 CoreFoundation 0x0000000185afed98 32,
9 CoreFoundation 0x0000000185afbd24 360,
10 CoreFoundation 0x0000000185afc104 836,
11 CoreFoundation 0x0000000185a291f4 CFRunLoopRunSpecific 396,
12 GraphicsServices 0x000000018ee4b6fc GSEventRunModal 168,
13 UIKit 0x000000018a3ba10c UIApplicationMain 1488,
14 YourApp 0x000000010016be8c SFCIQuestionnaire 605836,
15 libdyld.dylib 0x000000019719aa08 4
]

简单说一下代码的原理,真正的崩溃内存地址是由日志上的内存地址减去偏移量得到的,即0x000000010016be8c-605836,不过一个是16进制,一个是10进制,都转成10进制,相减,得到结果再转成16进制字符串,就是实际结果。

其实这个是要在终端上运行,不过我就喜欢xcode,就要在xcode上运行,哈哈,所以把命令行弄成字符串,然后用 system([result UTF8String])执行

//你的app在你电脑上的路径(如果找不到,就把你的ipa文件改成后缀zip,然后解压缩,在PayLoad文件夹下,把文件拖进来就是路径)
#define APP_PATH @"/Users/soufun/Desktop/crash/YourApp.app"
//appName,没什么多说的
#define APP_NAME @"YourApp"
//内存地址
#define MODULE_ADDRESS @"0x000000010013fe8c"
//偏移量
#define SLIDE_VALUE 605836
#import 
//MODULE_ADDRESS(16进制)减去 SLIDE_VALUE(10进制),最后得到一个16进制的字符串
NSString * dealWithAddress(){
    unsigned long long result = 0;
    NSScanner *scanner = [NSScanner scannerWithString:[MODULE_ADDRESS substringFromIndex:2]];
    [scanner scanHexLongLong:&result];
    NSString *hexString = [NSString stringWithFormat:@"%@",[[NSString alloc] initWithFormat:@"%16llx",result - SLIDE_VALUE]];
    return hexString;
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //如果内存地址是18位,就是arm64,其余是armv7
        NSString * type = MODULE_ADDRESS.length == 18 ? @"arm64" : @"armv7";
        //把所有参数拼接成命令行
        NSString * result = [NSString stringWithFormat:@"xcrun atos --arch %@ -o %@/%@ -l %@ %@",type,APP_PATH,APP_NAME,dealWithAddress(),MODULE_ADDRESS];
        NSLog(@"程序崩溃在:");
        //执行命令行,定位到在什么位置崩溃
        system([result UTF8String]);
    }
    return 0;
}

你可能感兴趣的:(iOS错误日志收集及分析)