對於崩潰的收集有不少第三方的不錯的工具,好比Bugly。可是若是想要本身收集崩潰日誌(好比對於企業內部發布的應用,在局域網內使用),天然也是有方法的。linux
這裏介紹兩種抓取崩潰堆棧信息的方法,若是發現問題,但願不吝賜教。也算是給本身作個筆記。:-)git
方法一:本身調用c函數抓取堆棧信息,話很少說,代碼來objective-c
#import <Foundation/Foundation.h>服務器
extern NSString *const SKSAppExceptionInfo;架構
@interface SKSExceptionRecord : NSObject函數
+ (void)startExceptionHandler;工具
@endoop
#import "SKSExceptionRecord.h"post
#include <execinfo.h>ui
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
以上都是在抓取到崩潰後,拋出消息,而後能夠在任意地方作崩潰處理。能夠依據本身架構需求作相應地改動。