app大都会crash,而第三方平台截获的崩溃日志,需要文件描述符才能解析。手机上的崩溃日志有时候也定位不到具体行。
我的建议是为了方便,测试版本可以本地记录崩溃日志。线上版本可以使用fir或bugly.
本地记录的好处是实时获取,不需要本地文件描述符。
本地记录崩溃日志的方法是增加如下代码:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [self configDDLog]; return YES; } - (void)configDDLog { #ifdef DEBUG //把崩溃日志写入文件 [GBLogWrite redirectNSLogToDocumentFolder]; #endif }
打印本地日志的方法是由以前我的同步日志改进过来的。我的同步日志连续快速打印有性能问题,但是应用崩溃前打印一次不在话下,没有任何问题:
GBLogWrite.h文件
// // GBLogWrite.h // GreenBusiness // // Created by 郏国上 on 2017/3/31. // Copyright © 2017年 UXing. All rights reserved. // #import <Foundation/Foundation.h> #define ERR_LOG 1 /* 应用程序无法正常完成操作,比如网络断开,内存分配失败等 */ #define WARN_LOG 2 /* 进入一个异常分支,但并不会引起程序错误 */ #define INFO_LOG 3 /* 日常运行提示信息,比如登录、退出日志 */ #define DEBUG_LOG 4 /* 调试信息,打印比较频繁,打印内容较多的日志 */ #ifndef LOGERR #define LOGERR(format,...) WriteLog(ERR_LOG,__FUNCTION__,__LINE__,format,##__VA_ARGS__) #endif #ifndef LOGWARN #define LOGWARN(format,...) WriteLog(WARN_LOG,__FUNCTION__,__LINE__,format,##__VA_ARGS__) #endif #ifndef LOGINFO #define LOGINFO(format,...) WriteLog(INFO_LOG,__FUNCTION__,__LINE__,format,##__VA_ARGS__) #endif #ifndef LOGDEBUG #define LOGDEBUG(format,...) WriteLog(DEBUG_LOG,__FUNCTION__,__LINE__,format,##__VA_ARGS__) #endif @interface GBLogWrite : NSObject + (void)redirectNSLogToDocumentFolder; @end
GBLogWrite.m文件
// // GBLogWrite.m // GreenBusiness // // Created by 郏国上 on 2017/3/31. // Copyright © 2017年 UXing. All rights reserved. // #import "GBLogWrite.h" static const long g_logFilesCount = 3; static const long g_logFileLines = 65535; @implementation GBLogWrite NSString *getTime(void) { NSDateFormatter * formatter = [[NSDateFormatter alloc]init]; [formatter setDateFormat:@"YYYY-MM-dd HH:mm:ss:SSS"]; NSString* dateTime = [formatter stringFromDate:[NSDate date]]; return dateTime; } void WriteFileLog(NSString *format, ...) { va_list args; va_start(args, format); NSString *string = [[NSString alloc] initWithFormat:format arguments:args]; va_end(args); if(!string) { return; } NSFileManager* fm = [NSFileManager defaultManager]; NSString *str = nil; BOOL flag = NO; NSArray* myPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString* myDocPath = [myPaths objectAtIndex:0]; NSString* path = [myDocPath stringByAppendingPathComponent:@"crash"]; [fm fileExistsAtPath:path isDirectory:&flag]; NSArray *dirarray = nil; NSString* filePath = nil; NSArray *lines = nil; NSError *error = nil; NSData* data = nil; NSInteger n = 0, i = 0, m = 0; NSMutableArray *filesMutableArr = [NSMutableArray array]; NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary]; NSString *app_Name = [infoDictionary objectForKey:@"CFBundleName"]; if(flag) { // dirarray = [fm contentsOfDirectoryAtPath:filePath]; // FLDDLogDebug(@"%@ ",dirarray); dirarray = [fm contentsOfDirectoryAtPath:path error:nil]; NSLog(@"%@ ",dirarray); n = dirarray.count; for(i = 0; i < n; i++) { filePath = [path stringByAppendingPathComponent:dirarray[i]]; if ([fm fileExistsAtPath:filePath]) { [filesMutableArr addObject:filePath]; } } m = filesMutableArr.count; if(m > g_logFilesCount) { NSMutableArray *newFilesMutableArr = [NSMutableArray array]; for(i = m - g_logFilesCount ; i < m; i++) { filePath = filesMutableArr[i]; [newFilesMutableArr addObject:filePath]; } //若想删除超过指定日志文件个数的最老的文件 for(i = 0; i < m - g_logFilesCount ; i++) { filePath = filesMutableArr[i]; [fm removeItemAtPath:filePath error:nil]; } filesMutableArr = newFilesMutableArr; m = filesMutableArr.count; // for(i = 0 ; (m - i > 0) && (i < m - g_logFilesCount); i++) // { // filePath = filesMutableArr[m - i]; // [fm removeItemAtPath:filePath error:nil]; // } } if(g_logFilesCount == m) { filePath = filesMutableArr[m - 1]; lines = [[NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil] componentsSeparatedByString:@"\n"]; if(lines.count < g_logFileLines) { data = [NSData dataWithContentsOfFile:filePath]; str =[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; str = [NSString stringWithFormat:@"%@\n%@",str,string]; [str writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil]; } else { [fm removeItemAtPath:filePath error:nil]; str = [NSString stringWithFormat:@"%@ %@.text", app_Name, getTime()]; filePath = [path stringByAppendingPathComponent:str]; str = string; [str writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil]; } } else if(m > 0) { filePath = filesMutableArr[m - 1]; // str =[NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error]; // str = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; // NSLog(@"str :%@", str); lines = [[NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil] componentsSeparatedByString:@"\n"]; // if(lines.count < 65535) if(lines.count < g_logFileLines) { data = [NSData dataWithContentsOfFile:filePath]; str =[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; str = [NSString stringWithFormat:@"%@\n%@",str,string]; [str writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil]; } else { str = [NSString stringWithFormat:@"%@ %@.text", app_Name, getTime()]; filePath = [path stringByAppendingPathComponent:str]; str = string; [str writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil]; } } else { str = [NSString stringWithFormat:@"%@ %@.text", app_Name, getTime()]; filePath = [path stringByAppendingPathComponent:str]; str = string; [str writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil]; } } else { BOOL res = [fm createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil]; if (res) { // NSLog(@"文件夹创建成功"); str = [NSString stringWithFormat:@"%@ %@.text", app_Name, getTime()]; filePath = [path stringByAppendingPathComponent:str]; str = string; // NSLog(@"filePath :%@", filePath); [str writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error]; // NSLog(@"error :%@", error); } else { NSLog(@"文件夹创建失败"); } } } void WriteLog(int ulErrorLevel, const char *func, int lineNumber, NSString *format, ...) { va_list args; va_start(args, format); NSString *string = [[NSString alloc] initWithFormat:format arguments:args]; va_end(args); NSString *strFormat = [NSString stringWithFormat:@"%@%s, %@%i, %@%@",@"Function: ",func,@"Line: ",lineNumber, @"Format: ",string]; NSString * strModelName = @"文件日志"; //模块名 NSString *strErrorLevel = [[NSString alloc] init]; switch (ulErrorLevel) { case ERR_LOG: strErrorLevel = @"Error"; break; case WARN_LOG: strErrorLevel = @"Warning"; break; case INFO_LOG: strErrorLevel = @"Notice"; break; case DEBUG_LOG: strErrorLevel = @"Debug"; break; default: break; } NSLog(@"ModalName: %@, ErrorLevel: %@, %@.",strModelName, strErrorLevel, strFormat); NSString *str = [NSString stringWithFormat:@"ModalName: %@, ErrorLevel: %@, %@.",strModelName, strErrorLevel, strFormat]; NSLog(@"%@", str); // WriteLog; WriteFileLog(str); } void UncaughtExceptionHandler(NSException* exception) { NSString* name = [ exception name ]; NSString* reason = [ exception reason ]; NSArray* symbols = [ exception callStackSymbols ]; // 异常发生时的调用栈 NSMutableString* strSymbols = [ [ NSMutableString alloc ] init ]; //将调用栈拼成输出日志的字符串 for ( NSString* item in symbols ) { [ strSymbols appendString: item ]; [ strSymbols appendString: @"\r\n" ]; } NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; [formatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"zh_CN"]]; [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; NSString *dateStr = [formatter stringFromDate:[NSDate date]]; LOGERR(@"<- %@ ->[ Uncaught Exception ]\r\nName: %@, Reason: %@\r\n[ Fe Symbols Start ]\r\n%@[ Fe Symbols End ]\r\n\r\n", dateStr, name, reason, strSymbols); } + (void)redirectNSLogToDocumentFolder { //未捕获的Objective-C异常日志 NSSetUncaughtExceptionHandler (&UncaughtExceptionHandler); } @end
打印的崩溃日志:
ModalName: 文件日志, ErrorLevel: Error, Function: UncaughtExceptionHandler, Line: 215, Format: <- 2017-03-31 11:02:48 ->[ Uncaught Exception ] Name: NSInvalidArgumentException, Reason: *** -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[0] [ Fe Symbols Start ] 0 CoreFoundation 0x000000018894eff0 <redacted> + 148 1 libobjc.A.dylib 0x00000001873b0538 objc_exception_throw + 56 2 CoreFoundation 0x00000001888302c8 <redacted> + 308 3 CoreFoundation 0x0000000188942c38 <redacted> + 36 4 CoreFoundation 0x0000000188832cbc <redacted> + 36 5 OutdoorClub 0x0000000100133d58 -[ODCCurrentPathLocationDataSingleObject saveCoordateWithFileName:lastLocationOperateModel:] + 308 6 OutdoorClub 0x0000000100132c90 -[ODCCurrentPathLocationDataSingleObject readmis] + 1672 7 OutdoorClub 0x0000000100132324 __50-[ODCCurrentPathLocationDataSingleObject initData]_block_invoke + 100 8 OutdoorClub 0x00000001003c2a10 +[NSTimer(YYAdd) _yy_ExecBlock:] + 148 9 Foundation 0x0000000189443760 __NSFireTimer + 88 10 CoreFoundation 0x00000001888fda9c <redacted> + 28 11 CoreFoundation 0x00000001888fd7a0 <redacted> + 856 12 CoreFoundation 0x00000001888fd060 <redacted> + 244 13 CoreFoundation 0x00000001888fac84 <redacted> + 1484 14 CoreFoundation 0x000000018882ad94 CFRunLoopRunSpecific + 424 15 GraphicsServices 0x000000018a294074 GSEventRunModal + 100 16 UIKit 0x000000018eae3130 UIApplicationMain + 208 17 OutdoorClub 0x000000010014853c main + 124 18 libdyld.dylib 0x000000018783959c <redacted> + 4 [ Fe Symbols End ] . ModalName: 文件日志, ErrorLevel: Error, Function: UncaughtExceptionHandler, Line: 215, Format: <- 2017-03-31 19:34:33 ->[ Uncaught Exception ] Name: NSInvalidArgumentException, Reason: *** -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[0] [ Fe Symbols Start ] 0 CoreFoundation 0x000000018894eff0 <redacted> + 148 1 libobjc.A.dylib 0x00000001873b0538 objc_exception_throw + 56 2 CoreFoundation 0x00000001888302c8 <redacted> + 308 3 CoreFoundation 0x0000000188942c38 <redacted> + 36 4 CoreFoundation 0x0000000188832cbc <redacted> + 36 5 OutdoorClub 0x00000001000c7d38 -[ODCCurrentPathLocationDataSingleObject saveCoordateWithFileName:lastLocationOperateModel:] + 448 6 OutdoorClub 0x00000001000c6be4 -[ODCCurrentPathLocationDataSingleObject readmis] + 1672 7 OutdoorClub 0x00000001000c6254 __50-[ODCCurrentPathLocationDataSingleObject initData]_block_invoke + 100 8 OutdoorClub 0x0000000100356a70 +[NSTimer(YYAdd) _yy_ExecBlock:] + 148 9 Foundation 0x0000000189443760 __NSFireTimer + 88 10 CoreFoundation 0x00000001888fda9c <redacted> + 28 11 CoreFoundation 0x00000001888fd7a0 <redacted> + 856 12 CoreFoundation 0x00000001888fd060 <redacted> + 244 13 CoreFoundation 0x00000001888fac84 <redacted> + 1484 14 CoreFoundation 0x000000018882ad94 CFRunLoopRunSpecific + 424 15 GraphicsServices 0x000000018a294074 GSEventRunModal + 100 16 UIKit 0x000000018eae3130 UIApplicationMain + 208 17 OutdoorClub 0x00000001000dc52c main + 124 18 libdyld.dylib 0x000000018783959c <redacted> + 4 [ Fe Symbols End ] . ModalName: 文件日志, ErrorLevel: Error, Function: UncaughtExceptionHandler, Line: 215, Format: <- 2017-04-01 09:35:10 ->[ Uncaught Exception ] Name: NSInvalidArgumentException, Reason: *** -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[0] [ Fe Symbols Start ] 0 CoreFoundation 0x000000018894eff0 <redacted> + 148 1 libobjc.A.dylib 0x00000001873b0538 objc_exception_throw + 56 2 CoreFoundation 0x00000001888302c8 <redacted> + 308 3 CoreFoundation 0x0000000188942c38 <redacted> + 36 4 CoreFoundation 0x0000000188832cbc <redacted> + 36 5 OutdoorClub 0x00000001000a3d38 -[ODCCurrentPathLocationDataSingleObject saveCoordateWithFileName:lastLocationOperateModel:] + 448 6 OutdoorClub 0x00000001000a2be4 -[ODCCurrentPathLocationDataSingleObject readmis] + 1672 7 OutdoorClub 0x00000001000a2254 __50-[ODCCurrentPathLocationDataSingleObject initData]_block_invoke + 100 8 OutdoorClub 0x0000000100332a70 +[NSTimer(YYAdd) _yy_ExecBlock:] + 148 9 Foundation 0x0000000189443760 __NSFireTimer + 88 10 CoreFoundation 0x00000001888fda9c <redacted> + 28 11 CoreFoundation 0x00000001888fd7a0 <redacted> + 856 12 CoreFoundation 0x00000001888fd060 <redacted> + 244 13 CoreFoundation 0x00000001888fac84 <redacted> + 1484 14 CoreFoundation 0x000000018882ad94 CFRunLoopRunSpecific + 424 15 GraphicsServices 0x000000018a294074 GSEventRunModal + 100 16 UIKit 0x000000018eae3130 UIApplicationMain + 208 17 OutdoorClub 0x00000001000b852c main + 124 18 libdyld.dylib 0x000000018783959c <redacted> + 4 [ Fe Symbols End ] .
注意:我们的同步日志系统在didFinishLaunchingWithOptions之前的崩溃无能为力,毕竟只有同步日志初始化以后才能记录,估计第三方平台也有这样的问题。