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崩溃收集原理浅析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 前序 这玩意我是真的不太喜欢,但是应领导需求,因为这玩意可以架在自己的服务器上,从某种程度上能够避免自己项目的信息...
    a_只羊阅读 5,268评论 8 5
  • 主要内容 闪退捕获 日志分析 闪退捕获 内核级异常:Mach异常->Unit信号(Mach层捕获到异常通过发送信号...
    mtry阅读 1,405评论 0 1
  • 本博客讲讲App异常监控,每个app都要保证使用质量,这样才能保住用户量,所以对于应用程序的监控显得尤为重要。想象...
    Hozan阅读 9,261评论 5 12
  • 本文整理下最近对于crash采集的总结,和踩过的坑。 CrashReporter 首先,iOS有自己的CrashR...
    谈Xx阅读 19,739评论 15 66
  • 前言 崩溃是让发人员比较头痛的事情,app崩溃了,说明代码写的有问题,这时如何快速定位到崩溃的地方很重要。调试阶段...
    進无尽阅读 1,972评论 0 9