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.