Despite my best efforts to the contrary, I ship software with bugs.
After unit testing and integration testing, the bugs that tend to slip through are tricky ones -- race conditions, crashes triggered by bugs in platform vendor's implementation, and issues that only appear in specific configurations, such as a user synchronizing their iPhone's Address Book with Microsoft Outlook.
These are the types of issues that you hope to catch in beta testing. If you don't, however, these bugs leak into the wild.
On the iPhone, Apple generates crash logs for every third-party application crash. These plain text logs include backtraces, thread state, and other information to help you debug your crashes. Unfortunately, these crash logs are not actually readable by third party applications. As a software developer, you're reliant on users to report the bug (rather than, say, simply delete your application), and then at your behest, synchronize their iPhone, locate the (correct!) crash log on disk, and send it to you.
To solve this problem, I decided to implement our own Crash Reporter. It sports the following features:
If your application crashes, a crash report will be written. When the application is next run, you may check for a pending crash report, submit the report to your own HTTP server, send an e-mail, or even introspect the report locally. Additionally, I hope to add support for services like getexceptional to automatically handle uploading, notification, and tracking of crashing issues.
Crash logs are encoded using google protobuf, and may be decoded using the PLCrashReport API. Additionally, the included plcrashutil handles conversion of binary crash reports to the symbolicate-compatible iPhone text format.
/** * Called to handle a pending crash report. */ - (void) handleCrashReport { PLCrashReporter *crashReporter = [PLCrashReporter sharedReporter]; NSData *crashData; NSError *error; /* Try loading the crash report */ crashData = [crashReporter loadPendingCrashReportDataAndReturnError: &error]; if (crashData == nil) { NSLog(@"Could not load crash report: %@", error); goto finish; } /* We could send the report from here, but we'll just print out * some debugging info instead */ PLCrashReport *report = [[[PLCrashReport alloc] initWithData: crashData error: &error] autorelease]; if (report == nil) { NSLog(@"Could not parse crash report"); goto finish; } NSLog(@"Crashed on %@", report.systemInfo.timestamp); NSLog(@"Crashed with signal %@ (code %@, address=0x%" PRIx64 ")", report.signalInfo.name, report.signalInfo.code, report.signalInfo.address); /* Purge the report */ finish: [crashReporter purgePendingCrashReport]; return; } // from UIApplicationDelegate protocol - (void) applicationDidFinishLaunching: (UIApplication *) application { PLCrashReporter *crashReporter = [PLCrashReporter sharedReporter]; NSError *error; /* Check if we previously crashed */ if ([crashReporter hasPendingCrashReport]) [self handleCrashReport]; /* Enable the Crash Reporter */ if (![crashReporter enableCrashReporterAndReturnError: &error]) NSLog(@"Warning: Could not enable crash reporter: %@", error); ... }
The first beta release is now available as open source (MIT licensed) from the PLCrashReporter Project Page. This is intended for developer testing, and your feedback is most appreciated.
If you're interested in additional functionality, integration support, or other development services, feel free to drop me a line. We also gladly accept donations to support our open source development efforts: Donate via Paypal