Crash reporting: Sentry as HockeyApp alternative

Since the beginning I have been using and loving HockeyApp, because it was made by very skilled developers who exactly knew what makes the live of a developer easier. It started with symbolicated crash reports, which previously were difficult to get and improved the overall quality of software products that integrated and still integrate with it.

Success attracts buyers and so HockeyApp was acquired by Microsoft and is now transitioning to their own platform called AppCenter, which will certainly do a good job in the future but isn't yet where HockeyApp is right now.

Sentry

I took that as an opportunity to look out for alternatives and found Sentry. First of all it is OpenSource and can be installed on premise. But usually you will go with their service which comes with a reasonable pricing.

It has a nice and clear web interface and supports everything I need, especially Objective-C and Javascript, which is a great fit for my Electron alternative.

Integration with macOS / iOS

The integration is easy e.g. by using their CocoaPod. With a few lines of code and providing the DSN of your project you can send Events. In my case I also wanted to let the user decide if he is ready to share the data and therefore added an NSAlert in the setShouldSendEvent block, which looks similar to this:

[SentryClient.sharedClient setShouldSendEvent:^BOOL(SentryEvent * _Nonnull event) {
    __block NSInteger ret = 0;
    dispatch_sync(dispatch_get_main_queue(), ^(void) {
       NSAlert *alert;
       // ... 
       ret = [alert runModal];
    });
    return ret == NSAlertFirstButtonReturn;
}];

With some more code the user sees this:

Sentry Dialog

Support

What I liked a lot at HockeyApp was their feedback dialog which allowed the user to give information about what situation lead to the problem and that I could begin a dialog with them and involve them into beta testing the fix. Mainly I did this using the great support tool from my friends at replies.io. I wrote about Replies earlier in this blog.

So my goal was to integrate replies.io again with my new Sentry setup and this is what the Send and Support button is doing. It opens the support for of replies.io and the user has the chance to get in contact and provide more info. The link between the ticket on Sentry and the one on replies.io is the userId set for both services, which is a unique ID per client.

Errors

Another aspect I changed while integrating the new service, was the tracking of errors. I'm referring to those errors you would usually write to the log file and only see if the user sends the log. This is very valuable information that you should use to improve your software.

I'm using a logging framework, which I described earlier here in this blog. So the most natural thing to do to hook into the logError part and create an event for Sentry:

- (void)logLevel:(SeaLogFlag)level file:(const char *)file function:(const char *)function line:(NSUInteger)line context:(NSString *)context message:(NSString *)message {
    int slevel = 0;
    if (level == SeaLogFlagInfo) slevel = kSentrySeverityInfo;
    if (level == SeaLogFlagWarning) slevel = kSentrySeverityWarning;
    if (level == SeaLogFlagError) slevel = kSentrySeverityError;
    if (slevel) {
        SentryBreadcrumb *bc = [[SentryBreadcrumb alloc] initWithLevel:slevel category:@"log"];
        bc.data = @{ @"message": message ?: @"",
                     @"file": [NSString stringWithFormat:@"<%@:%@>", @(file ?: ""), @(line)] 
                     };
        [[SentryClient.sharedClient breadcrumbs] addBreadcrumb:bc];
    }
    if (level == SeaLogFlagError) {
        [SentryClient.sharedClient snapshotStacktrace:^{
            SentryEvent *event = [[SentryEvent alloc] initWithLevel:kSentrySeverityError];
            event.message = message;
            [SentryClient.sharedClient appendStacktraceToEvent:event];
            [SentryClient.sharedClient sendEvent:event withCompletionHandler:^(NSError * _Nullable error) {
                ;
            }];
        }];
    }

You might notice the breadcrumb object. This is the way to pass logging info to Sentry, which I do by creating breadcrumb objects for log on the info and warning level.

Symbolication

In order to get the most out of the stack trace the service should symbolicate the crash report and show the line numbers, where the problem originated. HockeyApp had its own awesome macOS app doing the job. But Sentry also provides a command line tool that integrates well with the archive step in Xcode and confirms the upload with a notification:

Upload confirmation

Integration with Javascript

The same applies to Javascript. The integration again is straight forward, but I encountered one problem, because I was using it in WKWebView. But setting the transport option fixed the issue for me:

Sentry.init({
    // ...
    transport: Sentry.Transports.FetchTransport
})

It is also important to get the release right. With some wepback and process.env tricks it worked quite well to get the version info from package.json as release information in the distributed code.

Of course you should upload your SourceMaps to Sentry to get the most out of the errors. Also the breadcrumbs will automatically be generated from the console.xyz calls.

What I learned

I enjoyed modernizing my crash and error handling code and infrastructure and finally get more valuable info out of the logging data. I am also happy to have found a good way to respect my users privacy and leave the decision to them if they would like to share their data or not, since my products may contain sensitive data and therefore it is important to win the users trust.

Sentry seems to be a good alternative to HockeyApp. I would have stayed with HockeyApp, but since they are migrating to a new platform, I felt like some effort to put into migration for my projects would have come anyway, so why not doing it right now. That Sentry is OpenSource is another plus on the list.

If you would like to see it in action - although I hope you'll never need to see the dialog 😉 - please try my products: