review

articleCrash monitor platform Sentry iOS SDK source code analysis (a)We have seen how Sentry catches various exception events. One of the most important ones is as follows



So in this article we are going to take a closer look at how abnormal events are handled and reported.

The source code parsing

Abnormal events are normalized

An exception captured by a Monitor classified by CrashMonitors will be returned to the sentrycrashcm_handleException function of SentryCrashMonitor

void sentrycrashcm_handleException(struct SentryCrash_MonitorContext* context)
{
    context->requiresAsyncSafety = g_requiresAsyncSafety;
    // If a second exception occurs while handling the exception
    if(g_crashedDuringExceptionHandling)
    {
        context->crashedDuringCrashHandling = true;
    }
    // Traverse all Monitors, adding context to the exception event if Monitor is on
    for(int i = 0; i < g_monitorsCount; i++)
    {
        Monitor* monitor = &g_monitors[i];
        if(isMonitorEnabled(monitor)) { addContextualInfoToEvent(monitor, context); }}// Handle exception events
    g_onExceptionEvent(context);

    if (context->currentSnapshotUserReported) {
        g_handlingFatalException = false;
    } else {
        // If an exception is not reported by the user, all monitors are shut down
        if(g_handlingFatalException && ! g_crashedDuringExceptionHandling) { SentryCrashLOG_DEBUG("Exception is fatal. Restoring original handlers."); sentrycrashcm_setActiveMonitors(SentryCrashMonitorTypeNone); }}}Copy the code

The function addContextualInfoToEvent, which adds the event context, is mainly used to record the on-site data of exceptions, such as SentryCrashMonitor_AppState, which records the code of APP status information

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; }}Copy the code

G_onExceptionEvent is a function pointer to handle exception events. The function pointer is assigned at initialization, as follows

SentryCrashMonitorType sentrycrash_install(const char* appName, const char* const installPath)
{...// Set the cache address of various data
    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);

    // Set the interval for sending logs to 60 seconds
    sentrycrashccd_init(60);

    // Set the exception event handling callback function
    sentrycrashcm_setEventCallback(onCrash);
    // Open various exception capture Monitors
    SentryCrashMonitorType monitors = sentrycrash_setMonitoring(g_monitoring);

    SentryCrashLOG_DEBUG("Installation complete.");
    return monitors;
}
Copy the code

Event persistence

Let’s take a look at the event handling callback function onCrash set up above

static void onCrash(struct SentryCrash_MonitorContext* monitorContext)
{
    // If the user does not actively report the exception, update and record the APP crash status
    if (monitorContext->currentSnapshotUserReported == false) {
        SentryCrashLOG_DEBUG("Updating application state to note crash.");
        sentrycrashstate_notifyAppCrash();
    }
    monitorContext->consoleLogPath = g_shouldAddConsoleLogToReport ? g_consoleLogPath : NULL;

    if(monitorContext->crashedDuringCrashHandling)
    {
        // If a second exception occurs during the processing of an exception event, the last exception is logged
        sentrycrashreport_writeRecrashReport(monitorContext, g_lastCrashReportFilePath);
    }
    else
    {
        char crashReportFilePath[SentryCrashFU_MAX_PATH_LENGTH];
        // Obtain the log path of abnormal events
        sentrycrashcrs_getNextCrashReportPath(crashReportFilePath);
        strncpy(g_lastCrashReportFilePath, crashReportFilePath, sizeof(g_lastCrashReportFilePath));
        // Write the exception event to the filesentrycrashreport_writeStandardReport(monitorContext, crashReportFilePath); }}Copy the code

Event to send

When an exception is recorded during APP Crash, when to send an exception log? As you may have noticed in the last article, the next time the APP starts the Sentry initialization, it will start. We can see this in the Sentry initialization

- (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];
        // Send all cached logs
        [installation sendAllReports];
    });
    return YES;
}
Copy the code

The sequence diagram of the main function called is shown below

The core code sent is as follows

- (void) sendAllReportsWithCompletion:(SentryCrashReportFilterCompletion) onCompletion
{
    // Get all exception logs
    NSArray* reports = [self allReports];

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

    // Send all logs
    [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)
         {
             // Delete all logs after they are sent successfully
             sentrycrash_deleteAllReports();
         }
         // Perform the callback
         sentrycrash_callCompletion(onCompletion, filteredReports, completed, error);
     }];
}

Copy the code

conclusion

Generally speaking, the logic of Sentry’s Crash log collection is relatively simple, that is, to intercept Crash events, record and report them. It seems simple, but the actual threshold is relatively high, for example, the methods that involve the kernel are not very common, so it still takes a lot of time to digest.