Crash監控平臺Sentry的iOS SDK源碼解析(二)

回顧

上篇文章Crash監控平臺Sentry的iOS SDK源碼解析(一)咱們已經瞭解到Sentry是如何捕獲各類異常事件的,其中最重要的一張圖以下

那麼這篇文章咱們就要去細讀異常事件是如何被處理和上報的。json

源碼解析

異常事件歸一

其中CrashMonitors分類下的Monitor捕獲到的異常最後都會歸於一個函數那就是SentryCrashMonitorsentrycrashcm_handleException函數,代碼解讀以下緩存

void sentrycrashcm_handleException(struct SentryCrash_MonitorContext* context) {
    context->requiresAsyncSafety = g_requiresAsyncSafety;
    //若是在處理異常的過程當中發生了第二個異常
    if(g_crashedDuringExceptionHandling)
    {
        context->crashedDuringCrashHandling = true;
    }
    //遍歷全部Monitors,若是Monitor是打開的,就給異常事件添加上下文環境
    for(int i = 0; i < g_monitorsCount; i++)
    {
        Monitor* monitor = &g_monitors[i];
        if(isMonitorEnabled(monitor))
        {
            addContextualInfoToEvent(monitor, context);
        }
    }

    //處理異常事件
    g_onExceptionEvent(context);

    if (context->currentSnapshotUserReported) {
        g_handlingFatalException = false;
    } else {
        //若是不是用戶自主上報的異常將關閉全部Monitor
        if(g_handlingFatalException && !g_crashedDuringExceptionHandling) {
            SentryCrashLOG_DEBUG("Exception is fatal. Restoring original handlers.");
            sentrycrashcm_setActiveMonitors(SentryCrashMonitorTypeNone);
        }
    }
}
複製代碼

其中添加事件上下文的函數addContextualInfoToEvent主要用於記錄異常現場數據,例如SentryCrashMonitor_AppState記錄APP狀態信息的代碼session

static void addContextualInfoToEvent(SentryCrash_MonitorContext* eventContext) {
    if(g_isEnabled)
    {
        eventContext->AppState.activeDurationSinceLastCrash = g_state.activeDurationSinceLastCrash;
        eventContext->AppState.activeDurationSinceLaunch = g_state.activeDurationSinceLaunch;
        eventContext->AppState.applicationIsActive = g_state.applicationIsActive;
        eventContext->AppState.applicationIsInForeground = g_state.applicationIsInForeground;
        eventContext->AppState.appStateTransitionTime = g_state.appStateTransitionTime;
        eventContext->AppState.backgroundDurationSinceLastCrash = g_state.backgroundDurationSinceLastCrash;
        eventContext->AppState.backgroundDurationSinceLaunch = g_state.backgroundDurationSinceLaunch;
        eventContext->AppState.crashedLastLaunch = g_state.crashedLastLaunch;
        eventContext->AppState.crashedThisLaunch = g_state.crashedThisLaunch;
        eventContext->AppState.launchesSinceLastCrash = g_state.launchesSinceLastCrash;
        eventContext->AppState.sessionsSinceLastCrash = g_state.sessionsSinceLastCrash;
        eventContext->AppState.sessionsSinceLaunch = g_state.sessionsSinceLaunch;
    }
}
複製代碼

其中g_onExceptionEvent是一個函數指針,用於處理異常事件的。該函數指針在初始化的時候已經賦值,具體以下app

SentryCrashMonitorType sentrycrash_install(const char* appName, const char* const installPath) {
    ...
    //設置各類數據的緩存地址
    char path[SentryCrashFU_MAX_PATH_LENGTH];
    snprintf(path, sizeof(path), "%s/Reports", installPath);
    sentrycrashfu_makePath(path);
    sentrycrashcrs_initialize(appName, path);

    snprintf(path, sizeof(path), "%s/Data", installPath);
    sentrycrashfu_makePath(path);
    snprintf(path, sizeof(path), "%s/Data/CrashState.json", installPath);
    sentrycrashstate_initialize(path);

    snprintf(g_consoleLogPath, sizeof(g_consoleLogPath), "%s/Data/ConsoleLog.txt", installPath);
    if(g_shouldPrintPreviousLog)
    {
        printPreviousLog(g_consoleLogPath);
    }
    sentrycrashlog_setLogFilename(g_consoleLogPath, true);

    //設置日誌發送的時間週期爲60秒
    sentrycrashccd_init(60);

    //設置異常事件處理回調函數
    sentrycrashcm_setEventCallback(onCrash);
    //打開各類異常捕獲Monitors
    SentryCrashMonitorType monitors = sentrycrash_setMonitoring(g_monitoring);

    SentryCrashLOG_DEBUG("Installation complete.");
    return monitors;
}
複製代碼

事件持久化

咱們來看看上文設置的事件處理回調函數onCrash函數

static void onCrash(struct SentryCrash_MonitorContext* monitorContext) {
    //若是不是用戶主動上報異常,更新並記錄APP crash狀態
    if (monitorContext->currentSnapshotUserReported == false) {
        SentryCrashLOG_DEBUG("Updating application state to note crash.");
        sentrycrashstate_notifyAppCrash();
    }
    monitorContext->consoleLogPath = g_shouldAddConsoleLogToReport ? g_consoleLogPath : NULL;

    if(monitorContext->crashedDuringCrashHandling)
    {
        //若是在處理異常事件的過程當中出現了二次異常,就記錄最後的一次異常
        sentrycrashreport_writeRecrashReport(monitorContext, g_lastCrashReportFilePath);
    }
    else
    {
        char crashReportFilePath[SentryCrashFU_MAX_PATH_LENGTH];
        //獲取異常事件記錄的日誌路徑
        sentrycrashcrs_getNextCrashReportPath(crashReportFilePath);
        strncpy(g_lastCrashReportFilePath, crashReportFilePath, sizeof(g_lastCrashReportFilePath));
        //將異常事件寫入文件
        sentrycrashreport_writeStandardReport(monitorContext, crashReportFilePath);
    }
}
複製代碼

事件發送

在APP Crash的時候記錄異常,那什麼時機去發送異常日誌呢?細心的同窗可能在上一篇文章的時候已經發現了,就是在APP下一次啓動Sentry初始化的時候啦!在Sentry初始化的地方咱們能夠看到post

- (BOOL)startCrashHandlerWithError:(NSError *_Nullable *_Nullable)error {
    [SentryLog logWithMessage:@"SentryCrashHandler started" andLevel:kSentryLogLevelDebug];
    static dispatch_once_t onceToken = 0;
    dispatch_once(&onceToken, ^{
        installation = [[SentryInstallation alloc] init];
        [installation install];
        //發送全部緩存的日誌
        [installation sendAllReports];
    });
    return YES;
}
複製代碼

主要調用的函數時序圖以下 ui

發送的核心代碼以下spa

- (void) sendAllReportsWithCompletion:(SentryCrashReportFilterCompletion) onCompletion
{
    //獲取全部異常日誌
    NSArray* reports = [self allReports];

    SentryCrashLOG_INFO(@"Sending %d crash reports", [reports count]);

    //發送全部日誌
    [self sendReports:reports
         onCompletion:^(NSArray* filteredReports, BOOL completed, NSError* error)
     {
         SentryCrashLOG_DEBUG(@"Process finished with completion: %d", completed);
         if(error != nil)
         {
             SentryCrashLOG_ERROR(@"Failed to send reports: %@", error);
         }
         if((self.deleteBehaviorAfterSendAll == SentryCrashCDeleteOnSucess && completed) ||
            self.deleteBehaviorAfterSendAll == SentryCrashCDeleteAlways)
         {
             //發送成功後刪除全部日誌
             sentrycrash_deleteAllReports();
         }
         //執行回調
         sentrycrash_callCompletion(onCompletion, filteredReports, completed, error);
     }];
}

複製代碼

總結

總的看下來,Sentry的Crash日誌採集的邏輯仍是比較簡單的,就是攔截Crash事件,記錄並上報。看上去很簡單,可是實際門檻仍是比較高的,好比涉及到內核的方法都不是很常見,因此仍是須要不少時間去消化。3d

相關文章
相關標籤/搜索