Developers
The mParticle SDK lets you track “errors” and “exceptions”. These two terms have slightly different meanings:
As with other events, extra attributes can be passed via the Event Info object.
[[MParticle sharedInstance] logError:@"Login failed"
eventInfo:@{@"reason":@"Invalid username"}];
Exceptions are signaling mechanisms used by languages and APIs to indicate that an unexpected or impossible action was attempted. If the code that causes an exception is wrapped within a try/catch block, the app will be able to recover, and you can log that exception using the mParticle SDK.
Any available state information, as well as a stack trace at the moment of the exception, is automatically sent to mParticle when you log an exception.
@try {
[self callNonExistingMethod];
}
@catch (NSException *ex) {
[[MParticle sharedInstance] logException:ex];
}
// An exception reporting the topmost context at the moment of the exception
@try {
dictionary[@"key"] = nil;
}
@catch (NSException *ex) {
[[MParticle sharedInstance] logException:ex topmostContext:self];
}
If the code that causes an exception is not wrapped in a try/catch block, the app will not be able to recover and the unhandled exception will result in an app crash. You can log these unhandled exceptions using PLCrashReporter with the mParticle SDK.
See the PLCrashReporter GitHub Repo for details on PLCrashReporter and instructions to add the library to your project.
You can reference the example app on GitHub for a full implementation of crash reporting with PLCrashReporter.
You will need to add PLCrashReporter to your project to generate a crash report for unhandled exceptions. Initialize and enable PLCrashReporter after the mParticle SDK has completed initalization by adding an observer for the mParticleDidFinishInitializing
notification.
// Assumes the PLCrashReporter library has been added to the project
@import CrashReporter;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Initialize PLCrashReporter after the mParticle SDK has completed initialization
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self
selector:@selector(initCrashReporter)
name:mParticleDidFinishInitializing
object:nil];
// Initialize mParticle
MParticleOptions *options = [MParticleOptions optionsWithKey:@"REPLACE WITH APP KEY"
secret:@"REPLACE WITH APP SECRET"];
[[MParticle sharedInstance] startWithOptions:options];
return YES;
}
- (void) initCrashReporter {
// It is strongly recommended that local symbolication only be enabled for non-release builds.
// Use PLCrashReporterSymbolicationStrategyNone for release versions.
PLCrashReporterConfig *config = [[PLCrashReporterConfig alloc] initWithSignalHandlerType: PLCrashReporterSignalHandlerTypeMach
symbolicationStrategy: PLCrashReporterSymbolicationStrategyAll];
PLCrashReporter *crashReporter = [[PLCrashReporter alloc] initWithConfiguration: config];
// Enable the Crash Reporter.
NSError *error;
if (![crashReporter enableCrashReporterAndReturnError: &error]) {
NSLog(@"Warning: Could not enable crash reporter: %@", error);
}
}
Crash reports are collected from PLCrashReporter and submitted with the logCrash
method. The plCrashReport
string will be base64 encoded by the SDK and appear as the pl_crash_report_file_base64
property on crash_report
events.
if ([crashReporter hasPendingCrashReport]) {
NSError *error;
// Try loading the crash report.
NSData *data = [crashReporter loadPendingCrashReportDataAndReturnError:&error];
if (data == nil) {
NSLog(@"Failed to load crash report data: %@", error);
return;
}
// Retrieving crash reporter data.
PLCrashReport *report = [[PLCrashReport alloc] initWithData:data error:&error];
if (report == nil) {
NSLog(@"Failed to parse crash report: %@", error);
return;
}
// Generate text for crash report.
NSString *text = [PLCrashReportTextFormatter stringValueForCrashReport:report withTextFormat:PLCrashReportTextFormatiOS];
// Log crash report.
[[MParticle sharedInstance] logCrash:@"Crash captured with PLCrashReporter" stackTrace:nil plCrashReport:text];
// Purge the report.
[crashReporter purgePendingCrashReport];
}
You can explicitly record stack trace information by populating the stackTrace
parameter when invoking logCrash
. The stack_trace
property on crash_report
events is available as plain text and does not need to be base64 decoded.
// Log crash report with a stack trace.
NSString *stackTrace = @"Stack Trace String";
[[MParticle sharedInstance] logCrash:@"Crash captured with PLCrashReporter" stackTrace:stackTrace plCrashReport:text];
PLCrashReporter does not expose an external method to extract a stack trace as a string from a PLCrashReport
object. One method to generate a stack trace string is to extend the PLCrashReportTextFormatter
class.
An example interface for a PLCrashReportTextFormatter+(StackTrace)
extension can be written as follows.
@interface PLCrashReportTextFormatter (Private)
+ (NSString *) formatStackFrame:(PLCrashReportStackFrameInfo *)frameInfo
frameIndex:(NSUInteger)frameIndex
report:(PLCrashReport *)report
lp64:(BOOL)lp64;
@end
@interface PLCrashReportTextFormatter (StackTrace)
+ (NSString *)stringValueStackTraceForCrashReport:(PLCrashReport *)report;
+ (boolean_t)isLp64Report:(PLCrashReport *)report;
@end
The associated implementation for PLCrashReportTextFormatter+(StackTrace)
can be written as follows.
@implementation PLCrashReportTextFormatter (StackTrace)
+ (NSString *)stringValueStackTraceForCrashReport:(PLCrashReport *)report {
boolean_t lp64 = [PLCrashReportTextFormatter isLp64Report:report];
NSMutableString *stackTrace = [@"" mutableCopy];
if (report.exceptionInfo != nil && report.exceptionInfo.stackFrames != nil && [report.exceptionInfo.stackFrames count] > 0) {
PLCrashReportExceptionInfo *exception = report.exceptionInfo;
for (NSUInteger frame_idx = 0; frame_idx < [exception.stackFrames count]; frame_idx++) {
PLCrashReportStackFrameInfo *frameInfo = [exception.stackFrames objectAtIndex: frame_idx];
[stackTrace appendString: [PLCrashReportTextFormatter formatStackFrame: frameInfo frameIndex: frame_idx report: report lp64: lp64]];
}
[stackTrace appendString: @"\n"];
}
return stackTrace;
}
+ (boolean_t)isLp64Report:(PLCrashReport *)report {
boolean_t lp64 = true; // quiesce GCC uninitialized value warning
/* Map to Apple-style code type, and mark whether architecture is LP64 (64-bit) */
NSString *codeType = nil;
/* Attempt to derive the code type from the binary images */
for (PLCrashReportBinaryImageInfo *image in report.images) {
/* Skip images with no specified type */
if (image.codeType == nil)
continue;
/* Skip unknown encodings */
if (image.codeType.typeEncoding != PLCrashReportProcessorTypeEncodingMach)
continue;
switch (image.codeType.type) {
case CPU_TYPE_ARM:
codeType = @"ARM";
lp64 = false;
break;
case CPU_TYPE_ARM64:
codeType = @"ARM-64";
lp64 = true;
break;
case CPU_TYPE_X86:
codeType = @"X86";
lp64 = false;
break;
case CPU_TYPE_X86_64:
codeType = @"X86-64";
lp64 = true;
break;
case CPU_TYPE_POWERPC:
codeType = @"PPC";
lp64 = false;
break;
default:
// Do nothing, handled below.
break;
}
/* Stop immediately if code type was discovered */
if (codeType != nil)
break;
}
/* If we were unable to determine the code type, fall back on the processor info's value. */
if (codeType == nil && report.systemInfo.processorInfo.typeEncoding == PLCrashReportProcessorTypeEncodingMach) {
switch (report.systemInfo.processorInfo.type) {
case CPU_TYPE_ARM:
codeType = @"ARM";
lp64 = false;
break;
case CPU_TYPE_ARM64:
codeType = @"ARM-64";
lp64 = true;
break;
case CPU_TYPE_X86:
codeType = @"X86";
lp64 = false;
break;
case CPU_TYPE_X86_64:
codeType = @"X86-64";
lp64 = true;
break;
case CPU_TYPE_POWERPC:
codeType = @"PPC";
lp64 = false;
break;
default:
codeType = [NSString stringWithFormat: @"Unknown (%llu)", report.systemInfo.processorInfo.type];
lp64 = true;
break;
}
}
/* If we still haven't determined the code type, we're totally clueless. */
if (codeType == nil) {
codeType = @"Unknown";
lp64 = true;
}
return lp64;
}
@end
See the example app on GitHub for a full implementation of crash reporting with PLCrashReporter.
While debugging a scenario that may lead, or is currently leading to, crashes and/or exceptions, it is often helpful to leave “breadcrumbs” along the way to better understand the context leading to the problem. A breadcrumb is a string explaining what your app code is about to attempt, or what it has just completed, for example “parsing began” or “parsing finished”.
The mParticle SDK lets you leave breadcrumbs with the leaveBreadcrumb
method. You can also include additional custom attributes.
// Leaving breadcrumbs as we parse an object that could throw an exception
- (void)parseResource:(Resource *)resource {
MParticle *mParticle = [MParticle sharedInstance];
@try {
[mParticle leaveBreadcrumb:@"parsing began"];
[mParticle leaveBreadcrumb:@"parsing title"];
[resource parseTitle];
[mParticle leaveBreadcrumb:@"parsing body"];
[resource parseBody];
[mParticle leaveBreadcrumb:@"parsing footer"];
[resource parseFooter];
[mParticle leaveBreadcrumb:@"parsing finished!"];
}
@catch (NSException *ex) {
[mParticle logException:ex topmostContext:self];
}
}
Was this page helpful?