iOS集成Sentry进行异常收集


异常捕获/收集的平台有很多,我们选用了Sentry;Sentry支持搭建在自己的服务器上(self-hosted),支持多种编程语言,号称是有超过5万家公司的100万名开发人员在使用;Sentry提供了3种类型账号:Developer,Team,Business;Developer类型是免费的,但功能有限,且异常记录每个月最多5K条(这个数量,笔者还特意测试了下,异常记录到达5K后,记录列表将会提示让你升级到付费版);如果免费版不能满足要求,就需付费使用;有条件的最好是自建服务器,异常记录数是没有限制的;关于self-hosted搭建官方文档有详细的教程,本文我们主要只讲解iOS端集成Sentry的过程;

创建项目

Sentry官网注册账号,创建一个Objective-C或者Swift项目;

然后找到项目设置选择Client Keys(DSN),复制DSN后面代码中需要用到(如果是self-hosted则是Public DSN);

代码实现

pod导入Sentry库;
appDelegate的didFinishLaunchingWithOptions方法中启动sentry捕获;

- (void)startSentry {
    NSError *error = nil;
    // 根据DSN创建SentryClient
    SentryClient *client = [[SentryClient alloc] initWithDsn:kSentryDSN didFailWithError:&error];
    SentryClient.sharedClient = client;
    [SentryClient.sharedClient startCrashHandlerWithError:&error];
    
    if (nil != error) {
        NSLog(@"%@", error);
    }
}

Sentry提供了一系列属性,供我们自定义一些信息;

SentryClient.sharedClient.environment = environment; // 环境 例如:debug
[SentryClient.sharedClient enableAutomaticBreadcrumbTracking]; // 开启面包屑功能
SentryClient.sharedClient.maxBreadcrumbs = 30; // 面包屑最多栈数

// 用户信息
SentryUser *user = [[SentryUser alloc] initWithUserId:guid]; // 日志记录以此区别、归类不同用户
user.username = userName;
user.extra = @{@"cellphone":cellphone}; // 自定义字段用户信息
SentryClient.sharedClient.user = user;

SentryClient.sharedClient.extra = @{@"other":otherMsg}; // 自定义字段信息

至此就已经实现对异常的监听、捕获了;

源码窥探

sentry比较强大,监听了各种各样情况的Crash异常;从源码中可以大致窥探其支持的Crash异常类型:

这其中包括C++、死锁、僵尸对象等等异常;我们比较熟悉的可能就是NSException了,它只包括Foundation框架的比如数组越界、数组,字典插入nil对象等情况;接下来我们就看下SentryCrashMonitor_NSException源码实现,(其他类型暂时不管,看着头大);

g_isEnabled = isEnabled;
if(isEnabled)
{
    SentryCrashLOG_DEBUG(@"Backing up original handler.");
    g_previousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler();

    SentryCrashLOG_DEBUG(@"Setting new handler.");
    NSSetUncaughtExceptionHandler(&handleUncaughtException);
    SentryCrash.sharedInstance.uncaughtExceptionHandler = &handleUncaughtException;
    SentryCrash.sharedInstance.currentSnapshotUserReportedExceptionHandler = &handleCurrentSnapshotUserReportedException;
}

如果开启捕获(调试阶段sentry是不开启捕获的),则先使用g_previousUncaughtExceptionHandler记录之前捕获异常的函数指针;然后通过NSSetUncaughtExceptionHandler设置sentry的异常捕获函数;

异常发生时就会调用函数

static void handleException(NSException* exception, BOOL currentSnapshotUserReported) {
    SentryCrashLOG_DEBUG(@"Trapped exception %@", exception);
    if(g_isEnabled)
    {
        ....
        
        SentryCrash_MonitorContext* crashContext = &g_monitorContext;
        memset(crashContext, 0, sizeof(*crashContext));
        crashContext->crashType = SentryCrashMonitorTypeNSException;
        crashContext->eventID = eventID;
        crashContext->offendingMachineContext = machineContext;
        crashContext->registersAreValid = false;
        crashContext->NSException.name = [[exception name] UTF8String];
        crashContext->NSException.userInfo = [[NSString stringWithFormat:@"%@", exception.userInfo] UTF8String];
        
        ...
        
        if (g_previousUncaughtExceptionHandler != NULL)
        {
            SentryCrashLOG_DEBUG(@"Calling original exception handler.");
            g_previousUncaughtExceptionHandler(exception);
        }
    }
}

主要配置一些异常的信息,然后将信息存储起来;以备下次启动应用时再调用接口上传这些数据;最后再调用之前的捕获异常函数,这里主要的作用就是兼容其他异常捕获功能;因为其他代码也可能调用了NSSetUncaughtExceptionHandler设置捕获函数;

自定义Events

除了捕获异常,sentry还支持发送自定义的日志信息,比如网络请求失败就可以将失败信息上传;

SentryEvent *event = [[SentryEvent alloc] initWithLevel:kSentrySeverityWarning]; // 指定事件的严重级别 Fatal/Error/Warnig
event.message = message; // 错误信息
event.environment = environment;
event.extra =@{@"url":url,@"code":@(code),@"param":param}; // 自定义字段
[SentryClient.sharedClient sendEvent:event withCompletionHandler:^(NSError * _Nullable error) {
    if (nil != error) {
        NSLog(@"%@", error);
    }
}];
测试

sentry代码都写好后,我们手动抛个异常测下sentry平台是否能正常统计异常的数据;
一切没问题的话,后台我们就能看到日志记录了:

  • 面包屑信息
  • 方法调用栈

因为我们还没有上传dSYM符号文件,sentry不能解析crash日志定位到具体方法;

上传dSYM文件

sentry平台创建Token:User Setting ----> Auth Tokens ---> Create New Token;创建时按需勾选选项;

拿到Token后就可以上传文件了

有两种上传方式

  • shell脚本上传
  1. 先打包,然后拿到dSYM文件;
Archives列表选择Show in Finder
显示包内容
  1. 安装sentry-cli:
    brew install getsentry/tools/sentry-cli
  2. 编写并执行以下脚本即可,其中URL如果是sentry服务器则是https://sentry.io,如果是self-hosted则填写自己服务器url;
sentry-cli --url URL --auth-token YOUR_TOKEN upload-dif --org YOUR_ORG --project  YOUR_PROJECT   dSYM_PATH --log-level=debug
  • 通过fastlane上传
  1. 安装fastlane
    sudo gem install fastlane -NV或是brew cask install fastlane命令安装;
    安装完后执行命令fastlane --version,确认安装成功;
  2. 初始化fastlane
    cd到项目目录下,执行命令fastlane init;

这里有4个选项,因为我们需要的只是上传dSYM文件选择4就可以了;如果需要能自动打包并提交到AppStore功能则可以选3;选择3后续会要求配置Apple ID相关信息;

初始化成功后,项目目录下会有一个fastlane文件;

  1. 编辑Fastfile文件,编写脚本
default_platform(:ios)

platform :ios do
  desc "上传到sentry"
  lane :upload_symbols do
  sentry_api_host = "http://sentry.io”
  org_slug = “YOUR_ORG”
  project_slug = “YOUR_PROJECT”
  auth_token = “YOUR_TOKEN”
  #download_dsyms
  gym(
      scheme: “YOUR_SCHEME”,
      workspace: “xxxx.xcworkspace",
      include_bitcode: false #根据项目bitcode设置情况
      )
  sentry_upload_dsym(
    url: "#{sentry_api_host}",
    auth_token: "#{auth_token}",
    org_slug: "#{org_slug}",
    project_slug: "#{project_slug}"
  )
  end
end
  1. 打包并上传
    cd到项目中fastlane目录下,执行命令fastlane sentry_upload_dsym;这步将会自动打包并拿到dSYM文件上传到sentry(省去手动打包这个步骤);

上传成功后,sentry项目设置中Debug Files就能看到文件了

之后再次捕获crash异常,查看堆栈信息就已经能解析出具体的方法了:


fastlane更多详细使用可参考:
iOS效率神器fastlane自动打包

网上没有找到关于sentry原理分析的文章,但各种异常收集框架原理大致相同,这里有篇讲解KSCrash的也可作参考;
KSCrash崩溃收集原理浅析

你可能感兴趣的:(iOS集成Sentry进行异常收集)