Error Tracking

The mParticle SDK lets you track “errors” and “exceptions”. These two terms have slightly different meanings:

  • An error is a designed error state in your app, such as a failed login.
  • An exception is an error thrown by the app or site.

As with other events, extra attributes can be passed via the Event Info object.

Log Errors

[[MParticle sharedInstance] logError:@"Login failed"
                           eventInfo:@{@"reason":@"Invalid username"}];

Log Exceptions

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];
}

Crash Reporting

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.

Initialize 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);
    }
}

Log a Crash Report

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];
}

Record a Stack Trace

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];

Stack Trace from PLCrashReporter

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?