崩溃日志收集

主要思路:
一.捕获两种崩溃的方法:
1.通过 NSSetUncaughtExceptionHandler 设置全局的异常处理器, 能够捕获的异常有: 数组越界/字典赋值 nil/ 调用方法不存在..
2.通过 Signal 处理,因为像内存访问错误/重复释放等错误, 会抛出 Signal 信号,所以需要专门处理
二.异常回调
1.发生异常之后,进入回调函数,此时获取堆栈中的信息
2.收到异常处理消息时, 开启 runloop,防止程序死亡
3.本机记录异常 or 上传服务器
 

.h

#import <UIKit/UIKit.h>

@interface UncaughtExceptionHandler : NSObject{
    BOOL dismissed;
}

@end
void HandleException(NSException *exception);
void SignalHandler(int signal);


void YunInstallUncaughtExceptionHandler(void);

 

.m 

#import "YunUncaughtExceptionHandler.h"
#include <libkern/OSAtomic.h>
#include <execinfo.h>



NSString * const YunUncaughtExceptionHandlerSignalExceptionName = @"YunUncaughtExceptionHandlerSignalExceptionName";
NSString * const YunUncaughtExceptionHandlerSignalKey = @"YunUncaughtExceptionHandlerSignalKey";
NSString * const YunUncaughtExceptionHandlerAddressesKey = @"YunUncaughtExceptionHandlerAddressesKey";
//volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。
//当前处理的异常个数
volatile int32_t YunUncaughtExceptionCount = 0;
//最大能够处理的异常个数
const int32_t YunUncaughtExceptionMaximum = 10;

const NSInteger YunUncaughtExceptionHandlerSkipAddressCount = 4;
const NSInteger YunUncaughtExceptionHandlerReportAddressCount = 5;

@implementation UncaughtExceptionHandler

//回溯, 追踪
+ (NSArray *)backtrace
{
//    backtrace函数用于获取堆栈的地址信息,
//    backtrace_symbols函数把堆栈地址翻译成我们易识别的字符串,
//    backtrace_symbols_fd函数则把字符串堆栈信息输出到文件中
     void* callstack[128];
     int frames = backtrace(callstack, 128);
     char **strs = backtrace_symbols(callstack, frames);
     
     int i;
     NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
     for (
         i = YunUncaughtExceptionHandlerSkipAddressCount;
         i < YunUncaughtExceptionHandlerSkipAddressCount +
            YunUncaughtExceptionHandlerReportAddressCount;
        i++)
     {
         [backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
     }
     free(strs);
     
     return backtrace;
}

- (void)alertView:(UIAlertView *)anAlertView clickedButtonAtIndex:(NSInteger)anIndex
{
    
    if (anIndex == 0)
    {
        dismissed = YES;
    }
}

- (void)validateAndSaveCriticalApplicationData
{
    
}

//错误日志开始发送到服务器
- (void)handleException:(NSException *)exception
{
    [self validateAndSaveCriticalApplicationData];
    
//    UIAlertView *alert =
//        [[UIAlertView alloc]
//            initWithTitle:NSLocalizedString(@"Unhandled exception", nil)
//            message:[NSString stringWithFormat:NSLocalizedString(
//                @"You can try to continue but the application may be unstable.\n\n"
//                @"Debug details follow:\n%@\n%@", nil),
//                [exception reason],
//                [[exception userInfo] objectForKey:YunUncaughtExceptionHandlerAddressesKey]]
//            delegate:self
//            cancelButtonTitle:NSLocalizedString(@"Quit", nil)
//            otherButtonTitles:NSLocalizedString(@"Continue", nil), nil];
//    [alert show];
    
    NSString *errorStr = [NSString stringWithFormat:NSLocalizedString(
                                                                      @"error info details follow:%@%@", nil),
                          [exception reason],
                          [[exception userInfo] objectForKey:YunUncaughtExceptionHandlerAddressesKey]]
    ;
    
   
    if(errorStr)
    {
        [self sendErrorMsg];
    }
    NSLog(@"捕获的异常:error:%@",errorStr);

    //当接收到异常处理消息时,让程序开始runloop,防止程序死亡
    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
    
    while (!dismissed)
    {
        for (NSString *mode in (__bridge  NSArray *)allModes)
        {
            CFRunLoopRunInMode((__bridge CFStringRef)mode, 0.001, false);
        }
    }
    
    CFRelease(allModes);

    NSSetUncaughtExceptionHandler(NULL);
    signal(SIGABRT, SIG_DFL);
    signal(SIGILL, SIG_DFL);
    signal(SIGSEGV, SIG_DFL);
    signal(SIGFPE, SIG_DFL);
    signal(SIGBUS, SIG_DFL);
    signal(SIGPIPE, SIG_DFL);
    
    if ([[exception name] isEqual:YunUncaughtExceptionHandlerSignalExceptionName])
    {
//如果我想程序发一个SIGINT函数,可以使用kill函数 kill(getpid(),SIGINT)。getpid()获得了当前运行的程序id,此时就发送了SIGINT信号给该进程 kill(getpid(), [[[exception userInfo] objectForKey:YunUncaughtExceptionHandlerSignalKey] intValue]); }
else{
    //抛出异常,并导致崩溃 [exception raise]; } }
-(void)sendErrorMsg { // [fetchQueue setGLDelegate:self.gldelegate]; } @end /** 异常回调函数 */ void HandleException(NSException *exception){ //OSAtomicIncrement32:一个自增函数,在库<libkern/OSAtomic.h>中,是线程安全的; int32_t exceptionCount = OSAtomicIncrement32(&YunUncaughtExceptionCount); if (exceptionCount > YunUncaughtExceptionMaximum) { return; } NSArray *callStack = [UncaughtExceptionHandler backtrace]; NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]]; [userInfo setObject:callStack forKey:YunUncaughtExceptionHandlerAddressesKey]; [[[UncaughtExceptionHandler alloc] init] performSelectorOnMainThread:@selector(handleException:) withObject: [NSException exceptionWithName:[exception name] reason:[exception reason] userInfo:userInfo] waitUntilDone:YES]; } /** 捕获信号后的回调函数 */ void SignalHandler(int signal) { int32_t exceptionCount = OSAtomicIncrement32(&YunUncaughtExceptionCount); if (exceptionCount > YunUncaughtExceptionMaximum) { return; } NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:signal] forKey:YunUncaughtExceptionHandlerSignalKey]; NSArray *callStack = [UncaughtExceptionHandler backtrace]; [userInfo setObject:callStack forKey:YunUncaughtExceptionHandlerAddressesKey]; [[[UncaughtExceptionHandler alloc] init] performSelectorOnMainThread:@selector(handleException:) withObject: [NSException exceptionWithName:YunUncaughtExceptionHandlerSignalExceptionName reason: [NSString stringWithFormat: NSLocalizedString(@"Signal %d was raised.", nil), signal] userInfo: [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:signal] forKey:YunUncaughtExceptionHandlerSignalKey]] waitUntilDone:YES]; } //begain void YunInstallUncaughtExceptionHandler(void) { //崩溃报告系统会用NSSetUncaughtExceptionHandler方法设置全局的 异常处理器。 NSSetUncaughtExceptionHandler(&HandleException); signal(SIGABRT, SignalHandler); signal(SIGILL, SignalHandler); signal(SIGSEGV, SignalHandler); signal(SIGFPE, SignalHandler); signal(SIGBUS, SignalHandler); signal(SIGPIPE, SignalHandler); } /* SIGABRT--程序中止命令中止信号 SIGALRM--程序超时信号 SIGFPE--程序浮点异常信号 SIGILL--程序非法指令信号 SIGHUP--程序终端中止信号 SIGINT--程序键盘中断信号 SIGKILL--程序结束接收中止信号 SIGTERM--程序kill中止信号 SIGSTOP--程序键盘中止信号 SIGSEGV--程序无效内存中止信号 SIGBUS--程序内存字节未对齐中止信号 SIGPIPE--程序Socket发送失败中止信号 */

 

 

关于 Signal 的介绍

https://www.cnblogs.com/daxiaxiaohao/p/4466097.html

转载于:https://www.cnblogs.com/daxueshan/p/8512439.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LogReport是一个崩溃日志上传框架 ,当App崩溃的时,把崩溃信息保存到本地的同时,自动给GitHub提交崩溃issue,你只需要几句,就能完成所有配置,更多细节请了解下方另外,崩溃信息支持邮件上传和HTTP上传,自动提交到issue使用的是邮件上传的方式。如果你拥有私人服务器,你也可以使用HTTP上传。特性介绍特性简介自定义日志保存路径默认保存在Android/data/com.xxxx.xxxx/log中自定义日志缓存大小默认大小为30M,超出后会自动清空文件夹支持多种上传方式目前支持邮件上传与HTTP上传,会一并把文件夹下的所有日志打成压缩包作为附件上传日志加密保存提供AES,DES两种加密解密方式支持,默认不加密日志按天保存目前崩溃日志和Log信息是按天保存,你可以继承接口来实现更多的保存样式携带设备与OS信息在创建日志的时候,会一并记录OS版本号,App版本,手机型号等信息,方便还原崩溃自定义日志上传的时机默认只在Wifi状态下上传支持,也支持在Wifi和移动网络下上传支持保存Log日志在打印Log的同时,把Log写入到本地(保存的时候会附带线程名称,线程id,打印时间),还原用户操作路径,为修复崩溃提供更多细节信息GitHub自动提交issue使用邮件发送的形式,把接受崩溃日志的邮箱和GitHub特定的开源项目绑定在一起即可,更多细节请看下面介绍依赖添加在你的项目根目录下的build.gradle文件中加入依赖allprojects {     repositories {         ...         maven { url "https://jitpack.io" }     } }添加依赖dependencies {     compile 'com.github.wenmingvs:LogReport:1.0.3' }初始化在自定义Application文件加入以下几行代码即可,默认使用email发送。如果您只需要在本地存储崩溃信息,不需要发送出去,请把initEmailReport()删掉即可。public class MyApplication extends Application {     @Override     public void onCreate() {         super.onCreate();         initCrashReport();     }     private void initCrashReport() {         LogReport.getInstance()                 .setCacheSize(30 * 1024 * 1024)//支持设置缓存大小,超出后清空                 .setLogDir(getApplicationContext(), "sdcard/"   this.getString(this.getApplicationInfo().labelRes)   "/")//定义路径为:sdcard/[app name]/                 .setWifiOnly(true)//设置只在Wifi状态下上传,设置为false为Wifi和移动网络都上传                 .setLogSaver(new CrashWriter(getApplicationContext()))//支持自定义保存崩溃信息的样式                 //.setEncryption(new AESEncode()) //支持日志到AES加密或者DES加密,默认不开启                 .init(getApplicationContext());         initEmailReporter();     }     /**      * 使用EMAIL发送日志      */     private void initEmailReporter() {         EmailReporter email = new EmailReporter(this);         email.setReceiver("wenmingvs@gmail.com");//收件人         email.setSender("wenmingvs@163.com");//发送人邮箱         email.setSendPassword("apptest1234");//邮箱的客户端授权码,注意不是邮箱密码         email.setSMTPHost("smtp.163.com");//SMTP地址         emai
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值