iOS集成Sentry進(jìn)行異常收集


異常捕獲/收集的平臺(tái)有很多,我們選用了Sentry;Sentry支持搭建在自己的服務(wù)器上(self-hosted),支持多種編程語(yǔ)言,號(hào)稱(chēng)是有超過(guò)5萬(wàn)家公司的100萬(wàn)名開(kāi)發(fā)人員在使用;Sentry提供了3種類(lèi)型賬號(hào):Developer,Team,Business;Developer類(lèi)型是免費(fèi)的,但功能有限,且異常記錄每個(gè)月最多5K條(這個(gè)數(shù)量,筆者還特意測(cè)試了下,異常記錄到達(dá)5K后,記錄列表將會(huì)提示讓你升級(jí)到付費(fèi)版);如果免費(fèi)版不能滿(mǎn)足要求,就需付費(fèi)使用;有條件的最好是自建服務(wù)器,異常記錄數(shù)是沒(méi)有限制的;關(guān)于self-hosted搭建官方文檔有詳細(xì)的教程,本文我們主要只講解iOS端集成Sentry的過(guò)程;

創(chuàng)建項(xiàng)目

Sentry官網(wǎng)注冊(cè)賬號(hào),創(chuàng)建一個(gè)Objective-C或者Swift項(xiàng)目;

然后找到項(xiàng)目設(shè)置選擇Client Keys(DSN),復(fù)制DSN后面代碼中需要用到(如果是self-hosted則是Public DSN);

代碼實(shí)現(xiàn)

pod導(dǎo)入Sentry庫(kù);
appDelegate的didFinishLaunchingWithOptions方法中啟動(dòng)sentry捕獲;

- (void)startSentry {
    NSError *error = nil;
    // 根據(jù)DSN創(chuàng)建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; // 環(huán)境 例如:debug
[SentryClient.sharedClient enableAutomaticBreadcrumbTracking]; // 開(kāi)啟面包屑功能
SentryClient.sharedClient.maxBreadcrumbs = 30; // 面包屑最多棧數(shù)

// 用戶(hù)信息
SentryUser *user = [[SentryUser alloc] initWithUserId:guid]; // 日志記錄以此區(qū)別、歸類(lèi)不同用戶(hù)
user.username = userName;
user.extra = @{@"cellphone":cellphone}; // 自定義字段用戶(hù)信息
SentryClient.sharedClient.user = user;

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

至此就已經(jīng)實(shí)現(xiàn)對(duì)異常的監(jiān)聽(tīng)、捕獲了;

源碼窺探

sentry比較強(qiáng)大,監(jiān)聽(tīng)了各種各樣情況的Crash異常;從源碼中可以大致窺探其支持的Crash異常類(lèi)型:

這其中包括C++、死鎖、僵尸對(duì)象等等異常;我們比較熟悉的可能就是NSException了,它只包括Foundation框架的比如數(shù)組越界、數(shù)組,字典插入nil對(duì)象等情況;接下來(lái)我們就看下SentryCrashMonitor_NSException源碼實(shí)現(xiàn),(其他類(lèi)型暫時(shí)不管,看著頭大);

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;
}

如果開(kāi)啟捕獲(調(diào)試階段sentry是不開(kāi)啟捕獲的),則先使用g_previousUncaughtExceptionHandler記錄之前捕獲異常的函數(shù)指針;然后通過(guò)NSSetUncaughtExceptionHandler設(shè)置sentry的異常捕獲函數(shù);

異常發(fā)生時(shí)就會(huì)調(diào)用函數(shù)

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);
        }
    }
}

主要配置一些異常的信息,然后將信息存儲(chǔ)起來(lái);以備下次啟動(dòng)應(yīng)用時(shí)再調(diào)用接口上傳這些數(shù)據(jù);最后再調(diào)用之前的捕獲異常函數(shù),這里主要的作用就是兼容其他異常捕獲功能;因?yàn)槠渌a也可能調(diào)用了NSSetUncaughtExceptionHandler設(shè)置捕獲函數(shù);

自定義Events

除了捕獲異常,sentry還支持發(fā)送自定義的日志信息,比如網(wǎng)絡(luò)請(qǐng)求失敗就可以將失敗信息上傳;

SentryEvent *event = [[SentryEvent alloc] initWithLevel:kSentrySeverityWarning]; // 指定事件的嚴(yán)重級(jí)別 Fatal/Error/Warnig
event.message = message; // 錯(cuò)誤信息
event.environment = environment;
event.extra =@{@"url":url,@"code":@(code),@"param":param}; // 自定義字段
[SentryClient.sharedClient sendEvent:event withCompletionHandler:^(NSError * _Nullable error) {
    if (nil != error) {
        NSLog(@"%@", error);
    }
}];
測(cè)試

sentry代碼都寫(xiě)好后,我們手動(dòng)拋個(gè)異常測(cè)下sentry平臺(tái)是否能正常統(tǒng)計(jì)異常的數(shù)據(jù);
一切沒(méi)問(wèn)題的話(huà),后臺(tái)我們就能看到日志記錄了:

  • 面包屑信息
  • 方法調(diào)用棧

因?yàn)槲覀冞€沒(méi)有上傳dSYM符號(hào)文件,sentry不能解析crash日志定位到具體方法;

上傳dSYM文件

sentry平臺(tái)創(chuàng)建Token:User Setting ----> Auth Tokens ---> Create New Token;創(chuàng)建時(shí)按需勾選選項(xiàng);

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

有兩種上傳方式

  • shell腳本上傳
  1. 先打包,然后拿到dSYM文件;
Archives列表選擇Show in Finder
顯示包內(nèi)容
  1. 安裝sentry-cli
    brew install getsentry/tools/sentry-cli
  2. 編寫(xiě)并執(zhí)行以下腳本即可,其中URL如果是sentry服務(wù)器則是https://sentry.io,如果是self-hosted則填寫(xiě)自己服務(wù)器url;
sentry-cli --url URL --auth-token YOUR_TOKEN upload-dif --org YOUR_ORG --project  YOUR_PROJECT   dSYM_PATH --log-level=debug
  • 通過(guò)fastlane上傳
  1. 安裝fastlane
    sudo gem install fastlane -NV或是brew cask install fastlane命令安裝;
    安裝完后執(zhí)行命令fastlane --version,確認(rèn)安裝成功;
  2. 初始化fastlane
    cd到項(xiàng)目目錄下,執(zhí)行命令fastlane init;

這里有4個(gè)選項(xiàng),因?yàn)槲覀冃枰闹皇巧蟼鱠SYM文件選擇4就可以了;如果需要能自動(dòng)打包并提交到AppStore功能則可以選3;選擇3后續(xù)會(huì)要求配置Apple ID相關(guān)信息;

初始化成功后,項(xiàng)目目錄下會(huì)有一個(gè)fastlane文件;

  1. 編輯Fastfile文件,編寫(xiě)腳本
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 #根據(jù)項(xiàng)目bitcode設(shè)置情況
      )
  sentry_upload_dsym(
    url: "#{sentry_api_host}",
    auth_token: "#{auth_token}",
    org_slug: "#{org_slug}",
    project_slug: "#{project_slug}"
  )
  end
end
  1. 打包并上傳
    cd到項(xiàng)目中fastlane目錄下,執(zhí)行命令fastlane sentry_upload_dsym;這步將會(huì)自動(dòng)打包并拿到dSYM文件上傳到sentry(省去手動(dòng)打包這個(gè)步驟);

上傳成功后,sentry項(xiàng)目設(shè)置中Debug Files就能看到文件了

之后再次捕獲crash異常,查看堆棧信息就已經(jīng)能解析出具體的方法了:


fastlane更多詳細(xì)使用可參考:
iOS效率神器fastlane自動(dòng)打包

網(wǎng)上沒(méi)有找到關(guān)于sentry原理分析的文章,但各種異常收集框架原理大致相同,這里有篇講解KSCrash的也可作參考;
KSCrash崩潰收集原理淺析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前序 這玩意我是真的不太喜歡,但是應(yīng)領(lǐng)導(dǎo)需求,因?yàn)檫@玩意可以架在自己的服務(wù)器上,從某種程度上能夠避免自己項(xiàng)目的信息...
    a_只羊閱讀 5,533評(píng)論 8 5
  • 主要內(nèi)容 閃退捕獲 日志分析 閃退捕獲 內(nèi)核級(jí)異常:Mach異常->Unit信號(hào)(Mach層捕獲到異常通過(guò)發(fā)送信號(hào)...
    mtry閱讀 1,574評(píng)論 0 1
  • 本博客講講App異常監(jiān)控,每個(gè)app都要保證使用質(zhì)量,這樣才能保住用戶(hù)量,所以對(duì)于應(yīng)用程序的監(jiān)控顯得尤為重要。想象...
    Hozan閱讀 9,586評(píng)論 5 12
  • 本文整理下最近對(duì)于crash采集的總結(jié),和踩過(guò)的坑。 CrashReporter 首先,iOS有自己的CrashR...
    談Xx閱讀 20,597評(píng)論 15 66
  • 前言 崩潰是讓發(fā)人員比較頭痛的事情,app崩潰了,說(shuō)明代碼寫(xiě)的有問(wèn)題,這時(shí)如何快速定位到崩潰的地方很重要。調(diào)試階段...
    進(jìn)無(wú)盡閱讀 2,179評(píng)論 0 9

友情鏈接更多精彩內(nèi)容