ios程序发布后,收集Crash崩溃信息

有人想在iOS程序发布之后,收集程序崩溃的信息以便改进程序,今天我给大家介绍个比较实用的方法。

崩溃信息的收集完成后会在沙盒生成txt报告,但是这个crash收集也不能收集所有的程序崩溃信息,例如栈溢出或者对象指针损坏等等,这些崩溃会导致Crash收集的代码不会执行。

废话不多说,先直接讲添加方法,源代码一起奉献;

在appdelegate里的 application:didFinishLoading方法中添加

[[CrashManager sharedInstance] manageCrashes];

当然,你也能设置代理让你的delegate接受崩溃信息,

[[CrashManager sharedInstance] setCrashDelegate:self selector:@selector(notifyException:stackTrace:)];

下面是源代码:CrashManager.h

#import <Foundation/Foundation.h>
#import "SynthesizeSingleton.h"


/**
 * Manages any crashes that occur while the app is running.
 * If a crash occurs while CrashManager is managing crashes, it will write
 * a crash report to a file, allow a user-defined delegate to do some more processing,
 * then let the application finish crashing.
 */
@interface CrashManager : NSObject
{
    /** The full path to the error report that this manager will write to. */
    NSString* errorReportPath;

    /** The delegate to inform when a crash occurs (can be nil). */
    id delegate;

    /** The selector to call on the delegate. */
    SEL callbackSelector;
}

SYNTHESIZE_SINGLETON_FOR_CLASS_HEADER(CrashManager);

/**
 * Start managing crashes.
 */
- (void) manageCrashes;

/**
 * Stop managing crashes.
 */
- (void) stopManagingCrashes;

/** The error report's path.
 * Note: If you set this to a value that doesn't start with "/", it will
 *       be expanded to a full path relative to the Documents directory.
 */
@property(readwrite,nonatomic,retain) NSString* errorReportPath;

/** The error report.  If there is no report, this will be nil. */
@property(readonly,nonatomic) NSString* errorReport;

/** If true, there is an error report present. */
@property(readonly,nonatomic) bool errorReportPresent;

/**
 * Delete the error report (if any).
 */
- (void) deleteErrorReport;

/**
 * Set the delegate and selector to call when a crash occurs.
 * The selector must take an NSException* and NSArray* parameter like so: <br>
 * - (void) notifyException:(NSException*) exception stackTrace:(NSArray*) stackTrace <br>
 * stackTrace will be an array of StackTraceEntry.
 *
 * @param delegate The delegate to call.
 * @param selector The selector to invoke.
 */
- (void) setCrashDelegate:(id) delegate selector:(SEL) selector;


@end


然后是CrashManager.m

#import "CrashManager.h"
#include <unistd.h>
#import "StackTracer.h"
#import <UIKit/UIKit.h>
#import "MacAdress.h"
#import "NSString+SHA.h"

/** Maximum number of stack frames to capture. */
#define kStackFramesToCapture 40

/** The default file to store error reports to. */
#define kDefaultReportFilename @"error_report.txt"

/** The exception name to use for raised signals. */
#define kSignalRaisedExceptionName @"SignalRaisedException"


@interface CrashManager (Private)

/**
 * Called automatically to handle an exception.
 *
 * @param exception The exception that was raised.
 * @param stackTrace The stack trace.
 */
- (void) handleException:(NSException*) exception stackTrace:(NSArray*) stackTrace;

/**
 * Called automatically to handle a raised signal.
 *
 * @param signal The signal that was raised.
 * @param stackTrace The stack trace.
 */
- (void) handleSignal:(int) signal stackTrace:(NSArray*) stackTrace;

/**
 * Get the name of a signal.
 *
 * @param signal The signal to get a name for.
 * @return The name of the signal.
 */
- (NSString*) signalName:(int) signal;

/**
 * Store an exception and stack trace to disk.
 * It will be stored to the file specified in reportFilename.
 *
 * @param exception The exception to store.
 * @param stackTrace The stack trace to store.
 */
- (void) storeErrorReport:(NSException*) exception stackTrace:(NSArray*) stackTrace;

@end


/**
 * Install the exception and signal handlers.
 */
static void installHandlers();

/**
 * Remove the exception and signal handlers.
 */
static void removeHandlers();

/**
 * Exception handler.
 * Sets up an appropriate environment and then calls CrashManager to
 * deal with the exception.
 *
 * @param exception The exception that was raised.
 */
static void handleException(NSException* exception);

/**
 * Signal handler.
 * Sets up an appropriate environment and then calls CrashManager to
 * deal with the signal.
 *
 * @param exception The exception that was raised.
 */
static void handleSignal(int signal);



static void installHandlers()
{
    NSSetUncaughtExceptionHandler(&handleException);
    signal(SIGILL, handleSignal);
    signal(SIGABRT, handleSignal);
    signal(SIGFPE, handleSignal);
    signal(SIGBUS, handleSignal);
    signal(SIGSEGV, handleSignal);
    signal(SIGSYS, handleSignal);
    signal(SIGPIPE, handleSignal);
}

static void removeHandlers()
{
    NSSetUncaughtExceptionHandler(NULL);
    signal(SIGILL, SIG_DFL);
    signal(SIGABRT, SIG_DFL);
    signal(SIGFPE, SIG_DFL);
    signal(SIGBUS, SIG_DFL);
    signal(SIGSEGV, SIG_DFL);
    signal(SIGSYS, SIG_DFL);
    signal(SIGPIPE, SIG_DFL);
}

static void handleException(NSException* exception)
{
    removeHandlers();
    
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    
    NSArray* stackTrace = [[StackTracer sharedInstance] generateTraceWithMaxEntries:kStackFramesToCapture];
    [[CrashManager sharedInstance] handleException:exception stackTrace:stackTrace];
    
    [pool drain];
    [exception raise];
}

static void handleSignal(int signal)
{
    removeHandlers();
    
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    
    NSArray* stackTrace = [[StackTracer sharedInstance] generateTraceWithMaxEntries:kStackFramesToCapture];
    [[CrashManager sharedInstance] handleSignal:signal stackTrace:stackTrace];
    
    [pool drain];
    // Note: A signal doesn't need to be re-raised like an exception does.
}


@implementation CrashManager


SYNTHESIZE_SINGLETON_FOR_CLASS(CrashManager);

- (id) init
{
    if(nil != (self = [super init]))
    {
        self.errorReportPath = kDefaultReportFilename;
    }
    return self;
}

- (void) dealloc
{
    [errorReportPath release];
    [super dealloc];
}

- (void) setCrashDelegate:(id) delegateIn selector:(SEL) selectorIn
{
    delegate = delegateIn;
    callbackSelector = selectorIn;
}

- (void) manageCrashes
{
    installHandlers();
}

- (void) stopManagingCrashes
{
    removeHandlers();
}

- (NSString*) errorReportPath
{
    return errorReportPath;
}

- (void) setErrorReportPath:(NSString*) filename
{
    if([filename characterAtIndex:0] != '/')
    {
        NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString* dir = [paths objectAtIndex:0];
        errorReportPath = [[dir stringByAppendingPathComponent:filename] retain];
    }
    else
    {
        errorReportPath = [filename retain];
    }
    
}

- (bool) errorReportPresent
{
    return [[NSFileManager defaultManager] fileExistsAtPath:errorReportPath];
}

- (NSString*) errorReport
{
    NSError *error = nil;
    return [NSString stringWithContentsOfFile:errorReportPath encoding:NSUTF8StringEncoding error:&error];
}

- (void) deleteErrorReport
{
    NSError *error = nil;
    [[NSFileManager defaultManager] removeItemAtPath:errorReportPath error:&error];
    if(nil != error)
    {
        NSLog(@"Warning: %s: Could not delete %@: %@", __PRETTY_FUNCTION__, errorReportPath, error);
    }
}

- (void) handleException:(NSException*) exception stackTrace:(NSArray*) stackTrace
{
    [self storeErrorReport:exception stackTrace:stackTrace];
    [delegate performSelector:callbackSelector withObject:exception withObject:stackTrace];
}

- (void) handleSignal:(int) signal stackTrace:(NSArray*) stackTrace
{
    NSException* exception = [NSException exceptionWithName:kSignalRaisedExceptionName
                                                     reason:[self signalName:signal]
                                                   userInfo:nil];
    [self storeErrorReport:exception stackTrace:stackTrace];
    [delegate performSelector:callbackSelector withObject:exception withObject:stackTrace];
}

- (void) storeErrorReport:(NSException*) exception stackTrace:(NSArray*) stackTrace
{
    NSString* data = [NSString stringWithFormat:@"=============异常崩溃报告=============\n%@\nApp: %@\nVersion: %@\nID: %@\nReason: %@: %@\n%@\n\n\n",
                      [NSDate date],
                      [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"],
                      [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"],
                      [[MacAdress macAdress] sha1],
            [exception name],
                      [exception reason],
                      [[StackTracer sharedInstance] printableTrace:stackTrace]];
    
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if ([fileManager fileExistsAtPath:errorReportPath]) {
        NSString *tmp = [NSString stringWithContentsOfFile:errorReportPath encoding:NSUTF8StringEncoding error:nil];
        data = [tmp stringByAppendingFormat:@"%@",data];
    }
    [data writeToFile:errorReportPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
}

- (NSString*) signalName:(int) signal
{
    switch(signal)
    {
        case SIGABRT:
            return @"Abort";
        case SIGILL:
            return @"Illegal Instruction";
        case SIGSEGV:
            return @"Segmentation Fault";
        case SIGFPE:
            return @"Floating Point Error";
        case SIGBUS:
            return @"Bus Error";
        case SIGPIPE:
            return @"Broken Pipe";
        default:
            return [NSString stringWithFormat:@"Unknown Signal (%d)", signal];
    }
}

@end



具体错误路径的StackTracer.h

#import <Foundation/Foundation.h>
#import "SynthesizeSingleton.h"


/**
 * Generates and formats stack traces.
 */
@interface StackTracer : NSObject
{
    /** The current application process's name. */
    NSString* processName;
}

/** Make this class into a singleton. */
SYNTHESIZE_SINGLETON_FOR_CLASS_HEADER(StackTracer);

/**
 * Generate a stack trace with the default maximum entries (currently 40).
 *
 * @return an array of StackTraceEntry.
 */
- (NSArray*) generateTrace;

/**
 * Generate a stack trace with the specified maximum entries.
 *
 * @param maxEntries The maximum number of lines to trace.
 * @return an array of StackTraceEntry.
 */
- (NSArray*) generateTraceWithMaxEntries:(unsigned int) maxEntries;

/**
 * Create an "intelligent" trace from the specified trace.
 * This is designed primarily for stripping out useless trace lines from
 * an exception or signal trace.
 *
 * @param stackTrace The trace to convert (NSArray containing StackTraceEntry).
 * @return An intelligent trace with the useless info stripped.
 */
- (NSArray*) intelligentTrace:(NSArray*) stackTrace;

/**
 * Turn the specified stack trace into a printabe string.
 *
 * @param stackTrace The stack trace to convert (NSArray containing StackTraceEntry).
 * @retun A string representation of the stack trace.
 */
- (NSString*) printableTrace:(NSArray*) stackTrace;

/**
 * Turn the specified stack trace into a condensed printable string.
 * The condensed entries are space separated, and only contain the object class
 * (if any) and the selector call.
 *
 * @param stackTrace The stack trace to convert (NSArray containing StackTraceEntry).
 * @return A condensed string representation of the stack trace.
 */
- (NSString*) condensedPrintableTrace:(NSArray*) stackTrace;

@end





/**
 * A single stack trace entry, recording all information about a stack trace line.
 */
@interface StackTraceEntry: NSObject
{
    unsigned int traceEntryNumber;
    NSString* library;
    unsigned int address;
    NSString* objectClass;
    bool isClassLevelSelector;
    NSString* selectorName;
    int offset;
    NSString* rawEntry;
}
/** This entry's position in the original stack trace. */
@property(readonly,nonatomic) unsigned int traceEntryNumber;

/** Which library, framework, or process the entry is from. */
@property(readonly,nonatomic) NSString* library;

/** The address in memory. */
@property(readonly,nonatomic) unsigned int address;

/** The class of object that made the call. */
@property(readonly,nonatomic) NSString* objectClass;

/** If true, this is a class level selector being called. */
@property(readonly,nonatomic) bool isClassLevelSelector;

/** The selector (or function if it's C) being called. */
@property(readonly,nonatomic) NSString* selectorName;

/** The offset within the function or method. */
@property(readonly,nonatomic) int offset;

/** Create a new stack trace entry from the specified trace line.
 * This line is expected to conform to what backtrace_symbols() returns.
 */
+ (id) entryWithTraceLine:(NSString*) traceLine;

/** Initialize a stack trace entry from the specified trace line.
 * This line is expected to conform to what backtrace_symbols() returns.
 */
- (id) initWithTraceLine:(NSString*) traceLine;

@end




StackTracer.m

#import "StackTracer.h"
#include <execinfo.h>
#include <unistd.h>


/** The maximum number of stack trace lines to use if none is specified. */
#define kDefaultMaxEntries 40

@implementation StackTracer

SYNTHESIZE_SINGLETON_FOR_CLASS(StackTracer);

- (id) init
{
    if(nil != (self = [super init]))
    {
        processName = [[[NSProcessInfo processInfo] processName] retain];
    }
    return self;
}

- (void) dealloc
{
    [processName release];
    [super dealloc];
}

- (NSArray*) generateTrace
{
    return [self generateTraceWithMaxEntries:kDefaultMaxEntries];
}

- (NSArray*) generateTraceWithMaxEntries:(unsigned int) maxEntries
{
    // Get the stack trace from the OS.
    void* callstack[maxEntries];
    int numFrames = backtrace(callstack, maxEntries);
    char** symbols = backtrace_symbols(callstack, numFrames);
    
    // Create StackTraceEntries.
    NSMutableArray* stackTrace = [NSMutableArray arrayWithCapacity:numFrames];
    for(int i = 0; i < numFrames; i++)
    {
        [stackTrace addObject:[StackTraceEntry entryWithTraceLine:[NSString stringWithUTF8String:symbols[i]]]];
    }
    
    // symbols was malloc'd by backtrace_symbols() and so we must free it.
    free(symbols);
    
    return stackTrace;
}

- (NSArray*) intelligentTrace:(NSArray*) stackTrace
{    
    int startOffset = 0;

    // Anything with this process name at the start is going to be part of the
    // exception/signal catching.  We skip that.
    for(int i = startOffset; i < [stackTrace count]; i++)
    {
        StackTraceEntry* entry = [stackTrace objectAtIndex:i];
        if(![processName isEqualToString:entry.library])
        {
            startOffset = i;
            break;
        }
    }
    
    // Beneath that is a bunch of runtime error handling stuff.  We skip this as well.
    for(int i = startOffset; i < [stackTrace count]; i++)
    {
        StackTraceEntry* entry = [stackTrace objectAtIndex:i];
        
        if(0xffffffff == entry.address)
        {
            // Signal handler stack trace is useless up to "0xffffffff 0x0 + 4294967295"
            startOffset = i + 1;
            break;
        }
        if([@"__objc_personality_v0" isEqualToString:entry.selectorName])
        {
            // Exception handler stack trace is useless up to "__objc_personality_v0 + 0"
            startOffset = i + 1;
            break;
        }
    }

    // Look for the last point where it was still in our code.
    // If we don't find anything, we'll just use everything past the exception/signal stuff.
    for(int i = startOffset; i < [stackTrace count]; i++)
    {
        StackTraceEntry* entry = [stackTrace objectAtIndex:i];
        // If we reach the "main" function, we've exhausted the stack trace.
        // Since we couldn't find anything, start from the previously calculated starting point.
        if([@"main" isEqualToString:entry.selectorName])
        {
            break;
        }
        
        // If we find something from our own code, use one level higher as the starting point.
        if([processName isEqualToString:entry.library])
        {
            startOffset = i - 1;
            break;
        }
    }
    
    NSMutableArray* result = [NSMutableArray arrayWithCapacity:[stackTrace count] - startOffset];
    for(int i = startOffset; i < [stackTrace count]; i++)
    {
        StackTraceEntry* entry = [stackTrace objectAtIndex:i];
        // We don't care about intermediate forwarding functions.
        if(![@"___forwarding___" isEqualToString:entry.selectorName] &&
           ![@"_CF_forwarding_prep_0" isEqualToString:entry.selectorName])
        {
            [result addObject:[stackTrace objectAtIndex:i]];
        }
    }
    
    return result;
}


- (NSString*) printableTrace:(NSArray*) stackTrace
{
    NSMutableString* string = [NSMutableString stringWithCapacity:[stackTrace count] * 100];
    for(StackTraceEntry* entry in stackTrace)
    {
        [string appendString:[entry description]];
        [string appendString:@"\n"];
    }
    return string;
}

- (NSString*) condensedPrintableTrace:(NSArray*) stackTrace
{
    NSMutableString* string = [NSMutableString stringWithCapacity:[stackTrace count] * 50];
    bool firstRound = YES;
    for(StackTraceEntry* entry in stackTrace)
    {
        if(firstRound)
        {
            firstRound = NO;
        }
        else
        {
            // Space separated.
            [string appendString:@" "];
        }

        if(nil != entry.objectClass)
        {
            // -[MyClass myMethod:anExtraParameter] or
            // +[MyClass myClassMethod:anExtraParameter]
            NSString* levelPrefix = entry.isClassLevelSelector ? @"+" : @"-";
            [string appendFormat:@"%@[%@ %@]", levelPrefix, entry.objectClass, entry.selectorName];
        }
        else
        {
            // my_c_function
            [string appendFormat:@"%@", entry.selectorName];
        }
    }
    return string;
}

@end



@implementation StackTraceEntry

static NSMutableCharacterSet* objcSymbolSet;

+ (id) entryWithTraceLine:(NSString*) traceLine
{
    return [[[self alloc] initWithTraceLine:traceLine] autorelease];
}

- (id) initWithTraceLine:(NSString*) traceLine
{
    if(nil == objcSymbolSet)
    {
        objcSymbolSet = [[NSMutableCharacterSet alloc] init];
        [objcSymbolSet formUnionWithCharacterSet:[NSCharacterSet alphanumericCharacterSet]];
        [objcSymbolSet formUnionWithCharacterSet:[NSCharacterSet characterSetWithRange:NSMakeRange('!', '~' - '!')]];
    }
    
    if(nil != (self = [super init]))
    {
        rawEntry = [traceLine retain];
        
        NSScanner* scanner = [NSScanner scannerWithString:rawEntry];
        
        if(![scanner scanInt:(int*)&traceEntryNumber]) goto done;
        
        if(![scanner scanUpToString:@" 0x" intoString:&library]) goto done;
        
        if(![scanner scanHexInt:&address]) goto done;
        
        if(![scanner scanCharactersFromSet:objcSymbolSet intoString:&selectorName]) goto done;
        if([selectorName length] > 2 && [selectorName characterAtIndex:1] == '[')
        {
            isClassLevelSelector = [selectorName characterAtIndex:0] == '+';
            objectClass = [[selectorName substringFromIndex:2] retain];
            if(![scanner scanUpToString:@"]" intoString:&selectorName]) goto done;
            if(![scanner scanString:@"]" intoString:nil]) goto done;
        }
        
        if(![scanner scanString:@"+" intoString:nil]) goto done;
        
        if(![scanner scanInt:&offset]) goto done;
        
    done:
        if(nil == library)
        {
            library = @"???";
        }
        if(nil == selectorName)
        {
            selectorName = @"???";
        }
        [library retain];
        [objectClass retain];
        [selectorName retain];
    }
    return self;
}

- (void) dealloc
{
    [rawEntry release];
    [library release];
    [objectClass release];
    [selectorName release];
    [super dealloc];
}

@synthesize traceEntryNumber;
@synthesize library;
@synthesize address;
@synthesize objectClass;
@synthesize isClassLevelSelector;
@synthesize selectorName;
@synthesize offset;

- (NSString*) description
{
    return rawEntry;
}

@end


有想要完整demo工程的,请移步下载。稍后给出链接。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值