一次阿里云日志接口导致崩溃问题的经历

简要过程

  • 在工程中引入日志系统,使用了第三方库CocoaLumberjack,网络发送采用了阿里的开源库AliyunLogObjc

  • 自测没什么问题,日志也能发送到阿里云后台

  • 在版本上线日,爆出了崩溃的问题。并且现象比较奇怪,用XCode在模拟器上和真机上跑都没有问题,但是用Jekins打包之后安装到手机上,就崩溃,Release模式和debug模式都崩溃。

  • 这期的改动(非业务)主要是集成了日志系统。所以我把日志系统卸了,然后再试,崩溃现象就没有了。

  • 从崩溃的手机导出崩溃日志,符号化之后,可以看出,应该是日志系统的问题,并且问题出在阿里云日志发送函数上面。

Last Exception Backtrace:
0   CoreFoundation                  0x1904bafd8 __exceptionPreprocess + 124
1   libobjc.A.dylib                 0x18ef1c538 objc_exception_throw + 56
2   CoreFoundation                  0x1904baf20 +[NSException raise:format:] + 116
3   xxxxxxxxx                       0x100c291fc UmengSignalHandler + 128
4   libsystem_platform.dylib        0x18f57930c _sigtramp + 36
5   xxxxxxxxx                       0x100418148 -[NSString(Crypto) SHA1WithSecret:] (NSString+Crypto.m:21)
6   xxxxxxxxx                       0x100418148 -[NSString(Crypto) SHA1WithSecret:] (NSString+Crypto.m:21)
7   xxxxxxxxx                       0x1003fc6e8 -[LogClient GetHttpHeadersFrom:url:body:bodyZipped:] (LogClient.m:101)
8   xxxxxxxxx                       0x1003fbc28 __39-[LogClient PostLog:logStoreName:call:]_block_invoke (LogClient.m:63)
9   libdispatch.dylib               0x18f3729e0 _dispatch_call_block_and_release + 24
10  libdispatch.dylib               0x18f3729a0 _dispatch_client_callout + 16
11  libdispatch.dylib               0x18f382bac _dispatch_root_queue_drain + 888
12  libdispatch.dylib               0x18f3827d0 _dispatch_worker_thread3 + 124
13  libsystem_pthread.dylib         0x18f57b1d0 _pthread_wqthread + 1096
14  libsystem_pthread.dylib         0x18f57ad7c start_wqthread + 4

崩溃日志符号化

  • 遇到崩溃,一种方法是登录友盟后台,查崩溃日志,然后将里面的地址用一个工具,(名字叫DSYMTools),可以看到有意义的信息。不过这种方式比较原始,不是很方便

  • 如果是调试过程中遇到崩溃,不需要这么麻烦,将XCode的“僵尸对象”那个选项勾上,一般都能够定位到。

  • 如果是测试机上发生的崩溃,可以把崩溃日志导出,后缀名是.ips可以直接改后缀为.crash

  • 如果不符号化,那么崩溃日志基本没用,一堆地址,看不出什么东西。

  • 崩溃日志符号化,需要symbolicatecrash工具、xxx.appxxx.app.dSYM这几个要素

  • 然后记住几句命令行,比如:

./symbolicatecrash /Users/xxxx/Desktop/crash/InOrder.crash /Users/xxxx/Desktop/crash/InOrder.app.dSYM > Control_symbol.crash

新生成的Control_symbol.crash就是符号化过的,里面的信息对于定位崩溃发生点还是有帮助的。

简要分析

崩溃报告中,以下几行跟应用有关,其他的基本上是系统库。这些函数,来自阿里云提供的日志上传接口

5   xxxxxxxxx                       0x100418148 -[NSString(Crypto) SHA1WithSecret:] (NSString+Crypto.m:21)
6   xxxxxxxxx                       0x100418148 -[NSString(Crypto) SHA1WithSecret:] (NSString+Crypto.m:21)
7   xxxxxxxxx                       0x1003fc6e8 -[LogClient GetHttpHeadersFrom:url:body:bodyZipped:] (LogClient.m:101)
8   xxxxxxxxx                       0x1003fbc28 __39-[LogClient PostLog:logStoreName:call:]_block_invoke (LogClient.m:63)
  • LogClient.m:63内容如下:调用函数GetHttpHeadersFrom
NSDictionary<NSString*,NSString*>* httpHeaders = [self GetHttpHeadersFrom:name url:httpUrl body:httpPostBody bodyZipped:httpPostBodyZipped];
  • LogClient.m:101内容如下:在函数GetHttpHeadersFrom中调用函数SHA1WithSecret
NSString *sign = [signString SHA1WithSecret:_mAccessKeySecret];
  • NSString+Crypto.m:21内容如下:
-(NSString *)SHA1WithSecret:(NSString *)secret {
    const char *cKey   = [secret cStringUsingEncoding:NSUTF8StringEncoding];
    const char *cData  = [self cStringUsingEncoding:NSUTF8StringEncoding];
    
    unsigned char cHMAC[CC_SHA1_DIGEST_LENGTH];
    
    // 这里就是第21行
    CCHmac(kCCHmacAlgSHA1, cKey, strlen(cKey), cData, strlen(cData), cHMAC);
    
    NSData *HMAC   = [[NSData alloc] initWithBytes:cHMAC length:sizeof(cHMAC)];
    
    NSString *hash = [HMAC base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
    
    return hash;
}
  • 问题1,这里只是进行SH1加密,怎么会导致崩溃?而且这种写法也能在网上找到相关的文章,看不出什么问题。
    Objective-C sample code for HMAC-SHA1 [closed]这个和这里一模一样
    iOS 使用HMAC这个基本也差不多,只是用了NSData数据类型参与运算。

  • 问题2,-[NSString(Crypto) SHA1WithSecret:] (NSString+Crypto.m:21)调用了两次,代码中明明只有一个地方调用。

  • 问题3,用XCode安装或者打包都不会崩溃。用Jekins打包,用iTools安装的包才会崩溃。打包只是调用命令行啊,怎么会有差别?

  • 问题4,Jekins打包,iTools安装,崩溃之后将手机连上XCode,看不到崩溃日志。这种情况也是少见的。什么原因?
    比如下面两篇文章就有图文并茂的介绍
    ios crash的原因与抓取crash日志的方法
    Xcode崩溃日志分析工具symbolicatecrash用法

  • 实际试验结果:改用第二种NSData数据类型参与运算,用Jekins打包后的程序不崩溃了。 原因不清楚,实在太奇怪了。这种情况目前当个例来看,其他地方应该不会遇到。

-(NSString *)SHA1WithSecret:(NSString *)secret {
    // 这块代码在网上也能找到出处,比如https://stackoverflow.com/questions/756492/objective-c-sample-code-for-hmac-sha1
    // 不过实际使用中,用XCode安装没有问题,但是用Jekins打包会导致崩溃。原因暂时不清楚
//    const char *cKey   = [secret cStringUsingEncoding:NSUTF8StringEncoding];
//    const char *cData  = [self cStringUsingEncoding:NSUTF8StringEncoding];
    
//    unsigned char cHMAC[CC_SHA1_DIGEST_LENGTH];
    
//    CCHmac(kCCHmacAlgSHA1, cKey, strlen(cKey), cData, strlen(cData), cHMAC);
    
    // 这种写法在网上也能找到出处,比如http://blog.csdn.net/u011604049/article/details/55044483
    // 实际试验了一下,这种写法用Jekins打出的包不崩溃。
    NSData *datas = [self dataUsingEncoding:NSUTF8StringEncoding];
    size_t dataLength = datas.length;
    NSData *keys = [secret dataUsingEncoding:NSUTF8StringEncoding];
    size_t keyLength = keys.length;
    unsigned char cHMAC[CC_SHA1_DIGEST_LENGTH];
    
    CCHmac(kCCHmacAlgSHA1, [keys bytes], keyLength, [datas bytes], dataLength, cHMAC);
    
    NSData *HMAC   = [[NSData alloc] initWithBytes:cHMAC length:sizeof(cHMAC)];
    
    NSString *hash = [HMAC base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
    
    return hash;
}

搜集崩溃信息

  • IOS SDK中提供了一个现成的函数 NSSetUncaughtExceptionHandler 用来做异常处理。

  • 上面的崩溃日志中包含这么一句xxxxxxxxx 0x100c291fc UmengSignalHandler + 128估计友盟也是通过这个API进行日志搜集的吧。

  • 崩溃信息抓到之后,一般的做法是调用邮件程序,让用户给开发者发邮件,比如全面的理解和分析IOS的崩溃日志中列举的例子

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [[AFNetworkReachabilityManager sharedManager] startMonitoring];
    appDelegate = self;
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];
    UILocalNotification *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
    _notification = notification;

    NSSetUncaughtExceptionHandler(&caughtExceptionHandler);
    /*Changes the top-level error handler.
    Sets the top-level error-handling function where you can perform last-minute logging before the program terminates
    */
    return YES;
}

void caughtExceptionHandler(NSException *exception){
    /**
     *  获取异常崩溃信息
     */
    NSArray *callStack = [exception callStackSymbols];
    NSString *reason = [exception reason];
    NSString *name = [exception name];
    NSString *content = [NSString stringWithFormat:@"========异常错误报告========\\nname:%@\\nreason:\\n%@\\ncallStackSymbols:\\n%@",name,reason,[callStack componentsJoinedByString:@"\\n"]];

    //把异常崩溃信息发送至开发者邮件
    NSMutableString *mailUrl = [NSMutableString string];
    [mailUrl appendString:@"mailto:xxx@qq.com"];
    [mailUrl appendString:@"?subject=程序异常崩溃信息,请配合发送异常报告,谢谢合作!"];
    [mailUrl appendFormat:@"&body=%@", content];
    // 打开地址
    NSString *mailPath = [mailUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:mailPath]];
}

小插曲

  • 改了阿里云接口的SHA1加密函数的接口之后,我自己测试了好多次,终于没有崩溃了。

  • 让运维帮忙看日志,那些自己生成的日志也能在阿里云后台看到了。

  • 将用来测试的自己生成的日志的代码删除,一个定时器,1秒产生的log,内容就是当前的时间。1秒钟1条,很快就能达到50条,这样就能触发向阿里云发送日志的动作。

[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
    XXXLog(@"测试log[NSDate date]: %@", [NSDate date]);
}];
  • 第二天,测试跟我说,今天早上的包不崩溃了,但是用我昨天打的包(测试代码还没有删除),就会崩溃。而且程序都没起来,闪一下就崩溃了。

  • 我有点不大相信,不过看了一下确实崩溃了。不过我昨天确实验证过了。我拿来了我昨天验证用的手机。不管是今天早上打的包还是昨天打的包,我的手机都不崩溃。

  • 用测试的手机连上XCode,跑得也很正常。(这个时候测试代码已经删除)。并且这次的崩溃也是Jekins打包崩溃,XCode联调不崩溃,说明不了任何问题。

  • 查看崩溃报告,通过XCode链接,或者通过iTools,都看不到崩溃日志。

  • 接着想到用NSSetUncaughtExceptionHandler函数自己抓崩溃,将崩溃信息存到手机本地,比如Document/exception.txt。不过,比较运气不好的事,崩溃的那个手机用我的MAC上的iTools看不到沙盒文件。我的那台不崩溃的手机,反而能看到沙盒文件。

  • 后来想到iTools有个实时日志输出功能,从日志中可以看到scheduledTimerWithTimeInterval函数是不存在的selector

  • 看到这里明白了,崩溃的手机没有这个API,看来是系统版本兼容性问题,我用的那台手机版本是10.3的,崩溃那两台,系统是8.x,9.x

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
  • 可以看到,这个定时器函数是10.0引入的。今天早上的版本,删除了测试代码,所以没有发生崩溃。昨天的版本,测试代码还在,所以出现了有点手机不崩溃,有的手机崩溃的奇怪现象。

  • 一开始以为不可思议的事情,找到原因之后,又那么理所当然。为了保险起见,早上加的抓崩溃日志保存在本地的代码我也删除了。因为正式版本,放一个崩溃日志在用户手机上,一点用也没有。

测试代码一定要删除,多余的代码一定要删除。

要注意系统API的版本问题,考虑系统兼容性

  • 事情到这里基本可以结束了。客户端直接调用阿里云接口直接将日志发送到阿里云并不是一个好的做法。这次的问题就是阿里云接口不稳定造成的。更好的做法是客户端先将日志发送到自己的后台,达到一定量之后,后台统一再发送阿里云备份。原因很简单:iOS客户端要发一遍,那么安卓端呢?H5端呢?

参考文章

具体的步骤,下面的参考文章已经写得很详细,照着做就好了。

全面的理解和分析IOS的崩溃日志

iOS .ips崩溃报告文件分析

iOS应用崩溃日志分析

iOS调试之 crash log分析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,619评论 4 59
  • 今天使用快捷键时,突然想整理一下所有的快捷键,希望对大家有帮助: Ctrl+S保存 Ctrl+W关闭程序 ...
    邹小月阅读 393评论 0 2
  • 從上古人出家。本為生死大事。即佛祖出世。亦特為開示此事而已。非於生死外別有佛法。非於佛法外別有生死。所謂迷之則生死...
    幽和阅读 211评论 0 0
  • 我希望,我写的每一个字、每一篇文章都洋溢着柔软❤的味道,我的每一个行为都有如莲花的花瓣,温柔而伸展。 草木无心,也...
    喵姐南阅读 316评论 0 0