Search code examples
iosobjective-ccrashlyticstwitter-fabric

Find out exact crash time in Crashlytics


In my app I would like to find out the exact time of any crash from previous session, reported via Crashlytics. I'm setting up Crashlytics this way:

- (void) setUpCrashlytics
{
    [[Fabric sharedSDK] setDebug:YES];
    [CrashlyticsKit setDebugMode:YES];
    [CrashlyticsKit setDelegate:self];
    [Fabric with:@[[Crashlytics class]]];
}

I'm simulating an app crash by pressing a button, few minutes after app launches:

[CrashlyticsKit crash];

I tried to get the last session crash time using CrashlyticsDelegate:

#pragma mark - CrashlyticsDelegate Methods
- (void) crashlyticsDidDetectReportForLastExecution:(CLSReport *) report completionHandler:(void (^)(BOOL)) completionHandler
{
    BOOL isCrash = report.isCrash; //this is TRUE

    NSDate *crashDate = report.crashedOnDate;
    NSDate *reportCreation = report.dateCreated;

    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        completionHandler(YES);
    }];
}

But unfortunately, both dates are displaying not the crash time, but last session launch time. Any ideas? Thanks.


Solution

  • Matt here from Crashlytics.

    Unfortunately, you've found a bug :( I've taken note of it, and I'll make sure it gets looked at.

    I am also interested to know what how you intend to use this information. It's just a use-case I haven't heard of before.

    Also, keep in mind that particular delegate callback is problematic. As we indicate in the header documentation, that method's API requires us to sacrifice some reliability features. I'd recommend avoiding it, because our ability to successfully report crashes is much better without it. Yes, we do have plans to add a new read-only API that avoids this tradeoff. For now, I would only recommend it if you have a very pressing need that cannot be satisfied any other way.

    Also, since it was brought up in the comments - this API will never be invoked for an out-of-memory termination. Those are technically not crashes, and are not are not reported as such. The mechanisms we use are completely different, and outlined in our documentation here: https://docs.fabric.io/apple/crashlytics/OOMs.html. We use a heuristic, and is not (nor claims to be) 100% reliable. It's pretty good though.

    Hope this is helpful - and hit up our support forums/email for further help. Stack overflow is harder to monitor :)

    Update:

    I thought it would be useful more info here, now that I understand what Radu is trying to achieve. The upshot is using this delegate method cannot achieve what he'd like, and in fact will only make the situation worse.

    At the moment Crashlytics is initialized (during the -[Fabric with:] call), the SDK prepares and enqueues any crashes on disk to NSURLSession's background uploading facility. We do this because we want make sure our upload process is not interrupted by another crash. To my knowledge, this is a feature unique to our implementation, we've been using it for years, and it works amazingly well. Crashlytics is basically immune to reporting failures caused by crashes on subsequent launches.

    Now, in order to make sure this works as well as possible, we have to do this work synchronously during launch. So, if you start Crashlytics/Fabric on a background thread, or do background work concurrently with the SDK being initialized, it compromises our ability to reliably complete this process before another potential crash could occur.

    But, there's another issue. If the delegate is set, and has this method implemented, we must respect the API's contract and ask the delegate if it is ok for us to enqueue the report before we send it. For better or worse, this API also does not do this synchronously. So, when you implement this delegate method, you are opening a large window of time where:

    • Crashlytics will not send reports because it is waiting for your permission to do so
    • another crash can cause a loss of pending reports

    During the time between the delegate being invoked and the callback being called, you aren't giving more time for reports to be sent. You're just adding delay before the SDK knows that you've given permission for us to send them.

    (Personally, I find this API very problematic, and would like to remove it. But, we need to keep it for backwards compatibility. It's really my fault for not implementing a new API that doesn't allow the delegate to cancel the report. Such an API wouldn't be subject to a delay in enqueue the report and could avoid all these problems. One day, we'll have that and we can finally deprecate this one.)

    So, to improve early-launch crash handling, I'd recommend the following:

    • never initialize Crashlytics on a background thread
    • make sure to initialize Crashlytics as early in launch as you can, ideally the very first thing your application does
    • never use this delegate method

    The only exceptions should really be:

    • you are attempting to implement a user-facing crash report permissions dialog (it was designed for exactly this use case)
    • want to blow away sensitive cache data that may be responsible crashes
    • have some other analytics mechanism and you want to count crashes

    I'd also recommend, again, contacting our support. Missing crashes are common. Missing crashes caused by SDK issues are uncommon, are monitored, and we have a ton of SDK-side and backend-side code to understand and minimize them.