对于崩溃的收集有很多第三方的不错的工具,比如Bugly。但是如果想要自己收集崩溃日志(比如对于企业内部发布的应用,在局域网内使用),自然也是有方法的。
这里介绍两种抓取崩溃堆栈信息的方法,如果发现问题,希望不吝赐教。也算是给自己做个笔记。:-)
方法一:自己调用c函数抓取堆栈信息,话不多说,代码来
#import <Foundation/Foundation.h>
extern NSString *const SKSAppExceptionInfo;
@interface SKSExceptionRecord : NSObject
+ (void)startExceptionHandler;
@end
#import "SKSExceptionRecord.h"
#include <execinfo.h>
NSString *const SKSAppExceptionInfo = @"SKSAppExceptionInfo";
static int s_fatal_signals[] = {
SIGABRT,
SIGBUS,
SIGFPE,
SIGILL,
SIGSEGV,
SIGTRAP,
SIGTERM,
SIGKILL,
};
static const char* s_fatal_signal_names[] = {
"SIGABRT",
"SIGBUS",
"SIGFPE",
"SIGILL",
"SIGSEGV",
"SIGTRAP",
"SIGTERM",
"SIGKILL",
};
static int s_fatal_signal_num = sizeof(s_fatal_signals) / sizeof(s_fatal_signals[0]);
//信号处理函数
void signalHandler(int signal) {
for (int i = 0; i < s_fatal_signal_num; ++i) {
if (signal == s_fatal_signals[i]) {
//signal 异常不上传log,但还需捕捉,不然Exception异常也不上传
//[SKSExceptionRecord handleException:[NSString stringWithFormat:@"%s",s_fatal_signal_names[i]] description:[SKSExceptionRecord backtrace]];
break;
}
}
}
void exceptionHandler(NSException *exception) {
[SKSExceptionRecord handleException:[exception reason] description:[exception callStackSymbols]];
}
@implementation SKSExceptionRecord
+ (NSArray *)backtrace {
void *callstack[128];
int frames = backtrace(callstack, 128);
char **strs = backtrace_symbols(callstack, frames);
NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
for (int i = 0; i < frames; ++i) {
[backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
}
free(strs);
return backtrace;
}
+ (void)startCrashHandler
{
// 1 linux错误信号捕获
for (int i = 0; i < s_fatal_signal_num; ++i) {
signal(s_fatal_signals[i], signalHandler);
}
// 2 objective-c未捕获异常的捕获
NSSetUncaughtExceptionHandler(&exceptionHandler);
}
+ (void)handleException:(NSString *)reason description:(id)description
{
//这里抛出消息,应用中可根据需要监听该消息,做相应的崩溃处理,比如上传到服务器
NSDictionary *dicInfo = [NSDictionary dictionaryWithObjectsAndKeys:reason, @"reason", description, @"description", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:SKSAppExceptionInfo object:nil userInfo:dicInfo];
//[self keepAppSurvive];
}
//+ (void)keepAppSurvive
//{
// CFRunLoopRef runLoop = CFRunLoopGetCurrent();
// CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
//
// //BOOL dismissed = NO;
// while (YES)
// {
// for (NSString *mode in (__bridge NSArray *)allModes)
// {
// CFRunLoopRunInMode((__bridge CFStringRef)mode, 0.001, false);
// }
// sleep(2);
// //dismissed = YES;
// }
//
// CFRelease(allModes);
//}
@end
此方法的缺陷,如果符号表被disable,那么抓取的信息将不具有可读性,只是纯粹的内存堆栈。
方法二:使用KSCrash,它的好处是可以将崩溃堆栈直接解析成可读字符。首先需要去git下载KSCrash,将其中的source目录添加到应用工程,或是将KSCrash工程直接添加到应用。添加后基本需要扩展自己的方法来处理崩溃信息,因为其中已经包含的方法(比如邮件)可能并不是你想要的长久的崩溃追踪方法。
以下是我定义的崩溃log收集方法:
#import <Foundation/Foundation.h>
#import "KSCrashInstallation.h"
#import "KSCrash.h"
#import "KSCrashAdvanced.h"
@interface SKSCrashManager : NSObject
+ (SKSCrashManager *)sharedManager;
- (void)checkLocalCrashLogs:(KSCrashReportFilterCompletion)block;
@end
#import "SKSCrashManager.h"
#import "KSCrashInstallation+Private.h"
#import "KSSingleton.h"
#import "KSCrashReportFilterAppleFmt.h"
#import "SkySeaManager.h"
#import "SKSEncrypt.h"
NSString *const SKSAppExceptionWithKSCrash = @"SKSAppExceptionWithKSCrash";
//-----------------SKSCrashReportSink----------------------------------------------
@interface SKSCrashReportSink : NSObject <KSCrashReportFilter>
- (id <KSCrashReportFilter>) defaultCrashReportFilterSet;
@end
@implementation SKSCrashReportSink
- (id <KSCrashReportFilter>) defaultCrashReportFilterSet
{
//这个方法很重要,KSAppleReportStyleSymbolicated这种格式的才能返回可读的crash log
return [KSCrashReportFilterPipeline filterWithFilters:
[KSCrashReportFilterAppleFmt filterWithReportStyle:KSAppleReportStyleSymbolicated],
self,
nil];
}
- (void) filterReports:(NSArray*) reports
onCompletion:(KSCrashReportFilterCompletion) onCompletion
{
NSDictionary *dicInfo = [NSDictionary dictionaryWithObjectsAndKeys:reports, @"reports", onCompletion, @"callback", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:SKSAppExceptionWithKSCrash object:nil userInfo:dicInfo];
}
@end
//--------------------SKSCrashInstallation-----------------------------------------
@interface SKSCrashInstallation : KSCrashInstallation
+ (SKSCrashInstallation*) sharedInstance;
@end
@implementation SKSCrashInstallation
IMPLEMENT_EXCLUSIVE_SHARED_INSTANCE(SKSCrashInstallation)
- (id) init
{
if((self = [super initWithRequiredProperties:nil]))
{
}
return self;
}
- (id<KSCrashReportFilter>) sink
{
SKSCrashReportSink* sink = [[SKSCrashReportSink alloc] init];
return [KSCrashReportFilterPipeline filterWithFilters:[sink defaultCrashReportFilterSet], nil];
}
@end
static SKSCrashManager *g_crashManager = NULL;
@interface SKSCrashManager()
@property (strong, nonatomic) KSCrashInstallation* crashInstallation;
@end
@implementation SKSCrashManager
+ (SKSCrashManager *)sharedManager {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (g_crashManager == NULL) {
g_crashManager = [[SKSCrashManager alloc] init];
[g_crashManager installCrashHandler];
}
});
return g_crashManager;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void) installCrashHandler
{
[self configureAdvancedSettings];
self.crashInstallation = [SKSCrashInstallation sharedInstance];
[self.crashInstallation install];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(uploadExceptionLog:) name:SKSAppExceptionWithKSCrash object:nil];
}
static void advanced_crash_callback(const KSCrashReportWriter* writer)
{
// You can add extra user data at crash time if you want.
writer->addBooleanElement(writer, "some_bool_value", NO);
}
- (void) configureAdvancedSettings
{
KSCrash* handler = [KSCrash sharedInstance];
handler.deleteBehaviorAfterSendAll = KSCDeleteOnSucess;
handler.zombieCacheSize = 16384*4;
handler.deadlockWatchdogInterval = 0;
handler.userInfo = @{@"someKey": @"someValue"};
handler.onCrash = advanced_crash_callback;
handler.printTraceToStdout = NO;
handler.searchThreadNames = YES;
handler.searchQueueNames = YES;
handler.handlingCrashTypes = KSCrashTypeAll;
}
- (void)checkLocalCrashLogs:(KSCrashReportFilterCompletion)block {
[self.crashInstallation sendAllReportsWithCompletion:block];
}
- (void)uploadExceptionLog:(NSNotification *)note
{
//这里做相应的崩溃处理
}
@end
以上都是在抓取到崩溃后,抛出消息,然后可以在任意地方做崩溃处理。可以依据自己架构需求做相应地改动。