Overview of iOS Crash Reporting Tools: Part 2/2

In this second part of a series on iOS Crash Reporting Tools, you’ll learn how to get started with 5 of the most popular services: Crashlytics, Crittercism, Bugsense, TestFlight and HockeyApp. By Cesare Rocchi.

Leave a rating/review
Save for later
Share
You are currently viewing page 5 of 6 of this article. Click here to view the first page.

TestFlight — Summing it Up

TestFlight is a complete service that covers distribution, crash reporting and remote logging. However, I have noticed that sometimes the dSYM is not uploaded successfully by the desktop application and you'll have to re-upload it using the website.

HockeyApp is pretty well known in the indie developer world — probably because it’s made by indie developers! :]

HockeyApp supports iOS, Android, MacOS, and Windows Phone. HockeyApp, like TestFlight, is more than a crash reporting tool. It also allows you to manage the distribution of builds to beta testers, as well as providing a platform to collect feedback.

HockeyApp — Configuring the Project

Once you have logged into the site, you will be greeted by an empty dashboard like so:

HockeyApp empty dashboard

You can create a new app using the web site, but using the Desktop application is far easier. Here's the steps to create your new app with HockeyApp:

  1. Create an API token.
  2. Download and install the desktop app.
  3. Configure your project with the API token.
  4. Configure your Xcode project.
  5. Archive the project.

First, click your account name on the top right and select “API Tokens”, as shown below:

Create API token on HockeyApp

Next, you can set the access privileges to the platform in the dialog presented below:

API token settings on HockeyApp

Leave the settings at their defaults (All Apps and Full Access) so that the API key will give you what is effectively “root access” to the platform. Click “Create” and the API token will appear. Copy it and save it somewhere.

Unfortunately there is no direct download link for the desktop application, but the link should be available in the "Installation" section on this knowledge base page.

Run the app and paste the API token in the Preferences pane, as shown here:

Configuring the HockeyApp desktop application

How cool would it be if after every archive was built in Xcode, the HockeyApp desktop app opened automatically, ready to upload the new build? Well, you can do that!

Copy your unmodified starter project and open it in Xcode. Select the root of the project, then select Product/Scheme/Edit Scheme (or CMD+Archive action, and finally select Post-actions, as shown below:

Adding a custom action to the archive phase

Click the + button on the bottom left, select New Run Script Action and paste the following command into the field shown below:

open -a HockeyApp "${ARCHIVE_PATH}"

New action after archive

Click OK to save. Now archive your current project. The desktop application should detect the archive and show the following dialog:

The desktop app of HockeyApp

As you can see, the desktop application has automatically detected the applicationID, version number and has located both .ipa and .dSYM files to be uploaded on the server. Make sure “Download Allowed” is checked, and click Upload to send it to the server.

Open the dashboard and click on the build you just uploaded, as shown below:

The new build on HockeApp

Notice that users and devices have been automatically detected. Neat! Copy the “App ID” and save it somewhere. You'll need it to configure the crash reporting system. Now the application is ready to be distributed. You'll find the download phase to be quite streamlined as well.

Head to http://config.hockeyapp.net/ on your iPhone and tap the “Install” link. This will ask you to install the HockeyApp profile and register your device. It will also install a webclip that will appear as an application icon on your device. Once that's complete, you'll be able to see the list of applications available for your device, as below:

Build distribution on the iPhone

Tap your app to install it and check that it runs correctly.

So far you have been dealing just with the distribution part. It's time to integrate crash reporting. Head to http://hockeyapp.net/releases/ to download the client SDK for iOS (the current version as of this writing is 3.0). Download the binary version and unzip it. Drag and drop the folder HockeySDK.embeddedframework onto your Xcode project and make sure “Copy items into destination group's folder” is checked.

Select the root in Project Navigator, select Project and in the Info tab set Configurations to “HockeySDK” as shown below:

Project configuration for HockeApp

Open SMAppDelegate.h and add the following import to the top of the file:

#import <HockeySDK/HockeySDK.h>

You now need to implement the HockeyApp protocols in the delegate. Open SMAppDelegate.h and modify the delegate interface as follows:

@interface SMAppDelegate : UIResponder <UIApplicationDelegate,BITHockeyManagerDelegate, BITUpdateManagerDelegate, BITCrashManagerDelegate>

...

@end

Next, open SMAppDelegate.m and add the following code to the beginning of application:didFinishLaunchingWithOptions:, using the App ID you saved earlier:

[[BITHockeyManager sharedHockeyManager] configureWithIdentifier:@"YOUR APP ID"
                                                           delegate:self];
[[BITHockeyManager sharedHockeyManager] startManager];

Add the following method to SMAppDelegate.m:

- (NSString *)customDeviceIdentifierForUpdateManager:(BITUpdateManager *)updateManager {
#ifndef CONFIGURATION_AppStore
  if ([[UIDevice currentDevice] respondsToSelector:@selector(uniqueIdentifier)])
    return [[UIDevice currentDevice] performSelector:@selector(uniqueIdentifier)];
#endif
  return nil;
}

All of this code will allow you to collect data about the user installations only when the build is not targeting the App Store; that is, when you are in the beta testing phase. To distinguish your new app version from the old version, update the app version in APPNAME-Info.plist. Now you are ready to test your first crash report! First, archive your application and use the desktop application to upload it to the server.

Note: If you have already uploaded a version of the app to HockeyApp, you will need to increase the version number of the app. To do so, click on the root project. Select "crashy" underneath "Targets", click the "Summary" tab, then increase your version number, as so:

Increase Version Number

Note: If you have already uploaded a version of the app to HockeyApp, you will need to increase the version number of the app. To do so, click on the root project. Select "crashy" underneath "Targets", click the "Summary" tab, then increase your version number, as so:

Increase Version Number

On your device, delete the previous version of your pizza app, and open the web clip that was previously installed to your device. Alternately, you can visit https://rink.hockeyapp.net/apps to do the same thing. There you will see the new release.

Install the new app version, run it, make it crash by deleting a row, and restart the app to allow the crash reports to be uploaded to the sever. After the restart, the application will show an alert view to ask if you want to send a crash report. To do so, tap Send Report. Now visit the HockeyApp dashboard and select your build. It will appear like the following:

The new build on HockeyApp

There are two versions of the app, as expected, and the newest version shows a crash. Click the row corresponding to the newest version and click the Crashes tab at the top, as shown below:

The first crash log on HockeyApp

As you can see the crash report has already been symbolicated and shows the line responsible for the crash. Click it and it will show the details of the stack trace, as below:

Details of the first bug on HockeyApp

Once you have fixed the bug, you can close it using the status drop-down at the top left. For the next crash, you will implement some logging. HockeyApp does not include a built-in logger so you will need to add one. You will use the well-known CocoaLumberjack. Download the zip file from GitHub, unzip it, and drag and drop the folder named “Lumberjack” onto the project root.

Now open SMAppDelegate.m and add the following import statements at the top:

#import "DDLog.h"
#import "DDASLLogger.h"
#import "DDTTYLogger.h"
#import "DDFileLogger.h"

Directly below the import statements in SMAppDelegate.m, add the following code:

@interface SMAppDelegate ()
@property (nonatomic, strong) DDFileLogger *fileLogger;
@end

Still working in SMAppDelegate.m, add the following code to application:didFinishLaunchingWithOptions: just before the initialization code for HockeyApp:

_fileLogger = [[DDFileLogger alloc] init];
_fileLogger.maximumFileSize = (1024 * 64);
_fileLogger.logFileManager.maximumNumberOfLogFiles = 1;
[_fileLogger rollLogFile];
[DDLog addLogger:_fileLogger];
    
[DDLog addLogger:[DDASLLogger sharedInstance]];
[DDLog addLogger:[DDTTYLogger sharedInstance]];

Now add the following two methods to SMAppDelegate.m:

- (NSString *) getLogFilesContentWithMaxSize:(NSInteger)maxSize {
  NSMutableString *description = [NSMutableString string];
  
  NSArray *sortedLogFileInfos = [[_fileLogger logFileManager] sortedLogFileInfos];
  NSUInteger count = [sortedLogFileInfos count];
  
  for (NSInteger index = count - 1; index >= 0; index--) {
    DDLogFileInfo *logFileInfo = [sortedLogFileInfos objectAtIndex:index];
    
    NSData *logData = [[NSFileManager defaultManager] contentsAtPath:[logFileInfo filePath]];
    if ([logData length] > 0) {
      NSString *result = [[NSString alloc] initWithBytes:[logData bytes]
                                                  length:[logData length]
                                                encoding: NSUTF8StringEncoding];
      
      [description appendString:result];
    }
  }
  
  if ([description length] > maxSize) {
    description = (NSMutableString *)[description substringWithRange:NSMakeRange([description length]-maxSize-1, maxSize)];
  }
  
  return description;
}

- (NSString *)applicationLogForCrashManager:(BITCrashManager *)crashManager {
    NSString *description = [self getLogFilesContentWithMaxSize:5000];
    if ([description length] == 0) {
        return nil;
    } else {
        return description;
    }
}

The first method retrieves logged contents from a local file, while the second method implements a delegate method for the crash manager of HockeyApp.

The application is now set up to collect logs locally using the Lumberjack framework, and to send them to the server via the HockeyApp framework.

Open SMViewController.m and add the following import:

#import "DDLog.h"

In SMViewController.m add the following statement to the end of viewDidLoad:

DDLogVerbose(@"SMViewController did load");

This small logging statement will tell us if the view loaded successfully or not. But as it stands, you won't actually be able to see that log line because the log level has not been set. Add the following code directly after the #import statements in SMViewController.m.

static const int ddLogLevel = LOG_LEVEL_VERBOSE;

I generally set the logging level to verbose, since I prefer to have as many details as possible when debugging.

Finally, modify tableView:willDisplayCell:forRowAtIndexPath: to reflect the code below:

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.row == pizzaOrder.count-1) {
        DDLogError(@"willDisplayCell - pizzas are %i", pizzaOrder.count);
        [[NSNotificationCenter defaultCenter] postNotificationName:LOAD_MORE_NOTIFICATION
                                                            object:nil];
    }
}

This will log the number of pizzas before the notification is posted.

Note: DDLogError is the only macro that works synchronously, so you can call it even right before a crash. All the other methods such as DDLogVerbose and DDLogInfo are asynchronous, so they might not do their job if they are called immediately before a crash.

Note: DDLogError is the only macro that works synchronously, so you can call it even right before a crash. All the other methods such as DDLogVerbose and DDLogInfo are asynchronous, so they might not do their job if they are called immediately before a crash.

Now update the application version, archive it and let the desktop application upload it to the server. Delete the old version on the device and install the new one using the web clip.

Note: HockeyApp will detect older versions of a build and will prompt you to update the build from within the app itself. This happens behind the scenes once you upload a new version, so users will not have to manually visit a web address. They just need to launch the app. Pretty convenient, eh?

Note: HockeyApp will detect older versions of a build and will prompt you to update the build from within the app itself. This happens behind the scenes once you upload a new version, so users will not have to manually visit a web address. They just need to launch the app. Pretty convenient, eh?

Run the application, scroll to the bottom to make it crash, and restart the app to send the crash report. Now visit the dashboard of your app and you will see the crash reported as such:

The second crash log on HockeyApp

Return to the HockeyApp dashboard. Click on the crash to get to the detailed view, then click Crash Logs in the tabs above. This will show the raw log. You should see three tabs below the crashes. Click the Description tab and lo and behold, there are your log statements:

Log statements on HockeyApp

Sometimes applications crash during startup. That makes it impossible to send reports to the server, because the application literally has no time to detect a previous crash and send it over-the-air. HockeyApp is the only tool that allows you to delay the initialization phase (and thus delaying the crash) and gives precedence to the “send to server” procedure so that your crash reports can be uploaded.

You can find some more details on handling startup crashes with HockeyApp here.

Contributors

Over 300 content creators. Join our team.