Overview of iOS Crash Reporting Tools
Believe it or not, developers are not perfect, and every once in a while you might have a (gasp!) bug in your app.
You will try your best to ship your apps with no bugs in them, but more often than not you realise afterwards that a bug has slipped through the net. Sometimes such bugs result in crashes, which no user likes to encounter. Never fear though, for there are many excellent iOS crash reporting tools to help you out! They can detect crashes and log details so you can investigate later, and even send you an email summary of when crashes occur and how often.
However, you may wonder which of these tools is the best? Read on to find out!
Introduction
You’ve spent months working on your newest application, you’ve spent weeks beta testing it, you’ve published it on the App Store, and the positive reviews are starting to roll in. Everything looks great!
But then one user posts a one-star review noting the worst thing possible — the app crashed. Yikes! Nothing is worse than hearing about crashes from the user community.
Although it’s nearly impossible to test for every possible scenario, you can make the triage and troubleshooting portion of your job a little easier by using crash reporting tools in your apps. In fact, they are possibly the most important tool you can make use of to improve your application. Even if you test it thoroughly before the submission to the store, chances are you may have forgotten to check a particular sequence or scenario which leads to a crash.
From a user perspective, a crash is more than just an annoyance. When the application just stops working without any feedback, this often results in lost data and unhappy users. Installing a crash reporting system in your app allows you to obtain all the information you need to be able to fix these annoyances.
App crashes are like death and taxes – they happen. But it’s up to you how to handle them!
When it comes to troubleshooting, the more information that is collected — such as the OS version, the device type, and the application version — the more details you have to track down the source of the bug and fix it. That way, you can turn each crash incident into an opportunity to improve your app!
In this two-part series, you’ll get an overview of the different options of crash reporting tools available to developers. You’ll learn about various free and commercial tools, as well as the various pros and cons of each tool. By the end of the first part, you’ll be able to make an informed choice as to the best tool for your needs. In the second part, you will learn how to get started with each crash reporting service and tie it into your app.
Sound good? Then let’s take a behind-the-scenes look at what goes into a crash reporting tool.
Crash Reporting Basics
A crash reporting tool is actually a combination of two components: a reporting library and a server-side collector. You can think of these items in terms of a restaurant: the reporting library is the kitchen and the collector is the waiter. The developer — i.e. you — plays the part the customer.
You can have the best organized kitchen in the world, but you’ll never eat anything if there isn’t a waiter to bring food to the table. Likewise, your waiter can provide excellent service, but if the food is not well prepared, your experience will not be a good one!
As a great restaurant should include a great kitchen and great waiters, a crash reporting tool should include a great reporting library and a great server-side collector. The role of the reporting library is to prepare the details about a crash. The role of the server-side component is to collect the crash data and present it in a meaningful way.
Symbolication
In iOS crash reporting, there’s one more thing that’s crucial to understanding your crash reports – symbolication. But what is symbolication? You can think of symbolication as the chef in the kitchen, who transforms raw ingredients like bananas, ice cream and rum into something meaningful and tasty such as Bananas Foster.
A crash report contains a stack trace of every thread that was running when the application terminated. This trace is similar to the traces you see in the debugger when paused, although a debugger’s trace will include both instance names and methods (often referred as symbols). In comparison, when an app is built for release, usually these symbol names are stripped from the binary. This means that crash reports coming into your crash reporter service contain strange looking hexadecimal addresses instead of symbol names.
You will no doubt have at least one crash log on your device, so to see what these hexadecimal addresses look like, find such a log like this:
- Connect your device to your Mac.
- Start Xcode and open the Organizer (CMD+Shift+2).
- Find your device on the left and select the “Device Logs” item as shown in the screenshot below:
A list of crash reports will show up, unless your device is brand new and you haven’t used it much. Select one application, and have a look to the right. You should see text displayed similar to the following:
Notice how there are some symbols, for example -[__NSArrayI objectAtIndex:]
, and some hexadecimal addresses, for example 0x000db142 0xb1000 + 172354
. This is what is known as a partially symbolicated crash log. The top of the stack trace is where the crash occurred, and going down the list indicates which other methods were called to get to where the crash occurred.
The reason that it’s a “partly” symbolicated log is because Xcode is able to symbolicate just the system components like UIKit and CoreFoundation (lines 6 to 18 in the screenshot above). But crashes aren’t usually generated by system libraries, instead they have their roots in the code you write. For example, line 2 indicates that the code path that lead to the crash went through the objectAtIndex:
method on NSArray
.
However, take a look at line 3 in the screenshot above. What’s the meaning of this?
0x000db142 0xb1000 + 172354 |
This is an example of an un-symbolicated portion of the log. All this tells us is that the crash occurred at the instruction at memory address 0x000db142
which is equal to 0xb1000 + 172354
. The reason for it telling us that seemingly obscure bit of math is that 0xb1000
is the start address of the main portion of the app. The 172354 is the important bit as it is the offset into the app.
But, that’s not terribly useful for debugging purposes. A raw crash report contains what you need to understand the cause of the crash, but you can’t fully comprehend it unless you dive a little deeper and match it up with the source code through a process known as “symbolication”. This is the process of turning these raw numbers into symbols and even pinpointing the exact line of code.
The Symbolication Process
In the case of iOS, this requires two things. The first is the exact application binary that caused the crash. The second is the dSYM file created during the compilation of the binary.
How do you get a hold of these two items? Well, if you are using the “Build and Archive” command in Xcode then you are already storing them! In Xcode, you can see these in the “Organizer” window. To see for yourself, open Xcode then go to Window\Organizer. Then select the “Archives” tab where you will see a list of your apps on the left and on the right you’ll see a list of the archives for the selected app.
However, if you haven’t been using “Build and Archive”, or don’t have any applications to choose from, simply create a dummy app to see where Xcode stores these files.
To create a dummy app. Start up Xcode, go to File\New\New Project, choose the iOS\Application\Empty Application template, and click Next, as shown in the screenshot below:
On the next page, feel free to put in any name for the application. The details don’t matter because this is just a throwaway app to create an archive with a binary and dSYM in it. Click Next, then choose a location to save your project. When Xcode presents the workspace, select iOS Device as the compilation target, as shown below:
Finally, select Product\Archive, as shown below:
This will generate an archive for your application, which can then be found in the Organizer.
Find that dSYM!
Go to the Organizer, by selecting Window\Organizer, select the Archives tab and then find either an archive for one of your own apps or the test app you made if you followed the previous section. In case you’re struggling to find the archive, here’s a screenshot of what the Organizer looks like:
Right-click on your choice of archive, and choose Show in finder. This will open up a Finder window with a file whose extension is .xcarchive. This is not a file, per se, but rather a folder.
To inspect the archive, first open a new terminal window. To open a terminal window, open a new Finder window, navigate to your Applications folder, then navigate to the Utilities subfolder, and finally run the Terminal application. Or you could simply type “terminal” into Spotlight and press return when it’s found Terminal. :]
Once Terminal is open, drag the .xcarchive file into the terminal window and you’ll see the complete path to the folder. Press Control-A to jump to the start of the path and type: "cd "
(note that there is a space after the “cd”).
Next, press enter. You’ll notice that the terminal prompt (the bit before where the cursor sits) now has the name of the .xcarchive before the dollar sign.
Next, type the following command: "find . -type d"
. You should see some output similar to the following:
$ find . -type d . ./Products ./Products/Applications ./Products/Applications/breezi.app ./Products/Applications/breezi.app/_CodeSignature ./Products/Applications/breezi.app/en.lproj ./dSYMs ./dSYMs/breezi.app.dSYM ./dSYMs/breezi.app.dSYM/Contents ./dSYMs/breezi.app.dSYM/Contents/Resources ./dSYMs/breezi.app.dSYM/Contents/Resources/DWARF |
The command you just typed is using the “find” program, which does exactly what it sounds like – it finds things! The “.” means “search in the current directory” and the “-type d” means “find things that are directories”. In the resulting list of files, notice the .dSYM
directory, along with the .app
directory. Those are what you’ll need to finish the symbolication process and tie the crash report to your code.
It’s the .dSYM that’s really the most important bit. Recall from earlier that a release build has all the symbols stripped from the binary, which is why crash reports from release builds contain only hexadecimal addresses. The .dSYM contains all the information required to convert back into symbols through the process of symbolication.
Xcode has a method of symbolication built into it and it’ll generally do a good job of making sure any crash log you ever see has been symbolicated. It uses spotlight to find the appropriate .dSYM file for the crash log in question, but sometimes it can’t find it (maybe because you didn’t have it on your computer at the time as the build was made on a build server). In these cases, you can force Xcode to re-symbolicate once you have the .dSYM copied to your computer. To force Xcode to re-symbolicate a crash you can do so in the Organizer. Go to the Organizer again, select the “Devices” tab and then select “Device Logs” on the left. Then select the crash log that needs re-symbolicating and press the button at the bottom of the screen:
It’s not just Xcode that uses the .dSYM to symbolicate crash reports. It’s also required by crash reporting tools. As you’ll see throughout the reviews of the various crash reporting tools, the way you get this file to the crash reporting tool is different, but it’s always required for symbolication. There’s no point having crash logs if you can’t read them after all!
Making a Case for Crash Reporting
It’s an incredibly clunky process to collect crash logs manually and since a user will probably not have Xcode installed, they would have to use iTunes to access the crash logs. While not a technique I recommend asking your users to do, here’s how you’d do it in the absence of crash reporting tools:
- Connect the device to the Mac, synch it via iTunes and navigate to the logs folder, which is different on each operating system:
- OS X: ~/Library/Logs/CrashReporter/MobileDevice/(your iPhone’s name)/(your app name)
- Windows XP: C:\Documents and Settings\Application Data\Apple computer\Logs\CrashReporter\(your iPhone’s name)\(your app name)
- Windows Vista: C:\Users\AppData\Roaming\Apple computer\Logs\CrashReporter\MobileDevice\(your iPhone’s name)\(your app name)
- Look for files with the extension of “.crash”.
- Ask the user to send those files to you.
- Once you have the files, open the Organizer in Xcode and select “Device Logs” on the top left, as shown below:
- At the bottom select import to add the log to Xcode and, if necessary because Xcode hasn’t noticed the log yet, trigger a re-symbolication as explained earlier.
Phew! Can you imagine any user volunteering to do this for you?
This solution is pretty tedious, to say the least, and it requires the active involvement of your users. That might be OK when you’re in the beta testing phase, and your testers expect a bit of weirdness with the app. However, once you’re in production don’t expect that your users will be happy to go through all that work to give you a crash report!
Interestingly enough, Apple helps out and collects crash reports for applications that are published through the App Store. However, it only happens for users who opted to automatically send diagnostic data to Apple. If you have an app available through, the App Store login to iTunes Connect, select “Manage Your Applications”, choose one of your applications and the click the blue button “View details”, as shown below:
This will open a detailed view of your app’s properties. At the top right there is a button “Crash Reports”, as shown in the following screenshot:
This section will contain the crash logs collected by Apple. In my experience, these aren’t collected as frequently as with other crash reporting tools, probably because not all that many people have automatic sending of diagnostic reports turned on. Plus, they still require some manual work from developers, like importing and symbolicating.
The smart developer, who would rather spend time writing code than manually retrieving and symbolicating crash dumps, would prefer a process like this:
- Release an application (either in beta or in production) which is able to capture and store crash logs.
- The application periodically (or even immediately!) sends crash reports to a server which collects and organizes them for you.
- The developer logs in to the server, finds the crash reports already symbolicated, and can easily find the root cause of the issue.
It’s not a far-fetched dream — tools like this do exist in the real world! However, there are quite a few to choose from. Some are free and open source, while others are commercial products. Of the many available, I’ve reviewed some of the most prominent offerings:
- Crashlytics
- Crittercism
- Bugsense
- TestFlight
- HockeyApp
Have a read through the reviews to see which one provides the particular solutions you’re looking for in your app. I have my own favorites, but I’d love to hear your thoughts on your personal choices in the comments section!
Crashlytics
Recently bought by Twitter, Crashlytics is pretty famous in the iOS community. It’s used by well-known companies such as Path and Yammer. It is a full-stack service, meaning that the framework provides both client-side and server-side parts. At the moment, Crashlytics supports only iOS, although the website does indicate that Android support is coming soon.
Crashlytics Setup and Dashboard
Once you have logged into the Crashlytics site, you’ll be prompted to download a Mac application. This application will guide you through the process of configuring your iOS application to work with the Crashlytics service. The application works like a wizard. It first asks you to pick an Xcode project on your disk. Next, it will install the Crashlytics framework, and finally, it will add a step to the building phase of your project.
Any crashes captured will appear on the Crashlytics back-end. Just login, select the application you’re interested in, and you’ll see something like this:
The data is definitely well-organized. At a glance, you can see the issues reported, number of crashes, and the number of users affected. On the right, a graph shows the distribution of crashes over time. Each crash is classified by application version to avoid any confusion.
Crashlytics Reports
Crashlytics displays the list of crashes at the bottom, already symbolicated, and you can immediately see the line generating the crash. In the example above, you can quickly see that line 343 in SMEngine.m was where the crash occurred.
If you click on a crash entry, further details are displayed, as shown in the screenshot below:
At the top of the report, there are contextual details about the average environment where the crash occurred, like the free space on the device, which is handy if you are caching data on disk; RAM, which is handy if you are caching data in memory; and whether the device is jailbroken, along with other juicy details.
At the bottom, you’ll see a stack trace with the sequence of calls that occurred right before the app crashed. As noted, it is very likely some of your code is causing the crash, so you should look for the names of your classes and methods. In this example, the crash is in getHourlyForecast
, which is called by parseResultForConnection
, which in turn is called by connectionDidFinishLoading
.
Once you have fixed the bug and deployed the new version of the app, you can mark the issue as closed, using the control shown below:
Once an issue is closed, it won’t appear again in the list of crashes that require your attention.
Crashlytics 3rd Party Integration
You can integrate Crashlytics with third party bug trackers and project management tools, including the following:
Simply enter your credentials for the above services, and Crashlytics servers will forward crash log data to the appropriate service. Cool! :]
For those developers who love to craft their own solution, you can even set up a web hook. This is a custom URL where Crashlytics will send you data in JSON format, and you’re then free to manipulate the data as you like.
The logging functions that Crashlytics provides are worth mentioning as well. Once you have configured your project for Crashlytics, you can quickly set up a macro to log events, actions and most importantly, the values of variables, which can be helpful while hunting for the source of a crash.
This is the equivalent of using NSLog
statements, except instead of logging to the console, the lines are sent to Crashlytics. How many times have you wished you could see the console output as your customers use your app in the real world? That’s a reality now! :]
You can also set up notifications via email to receive messages about crashes either as they are received, or as a daily email digest.
Crashlytics Usage Tiers
Previously, Crashlytics had two usage tiers: a free tier, as well as a paid enterprise tier. However, the enterprise tier is now free as well. Thanks Twitter!
To go back to the restaurant analogy, Crashlytics is a good restaurant with a proficient kitchen (symbolication). The food is good and the wait staff are attentive (browsability and usability of logs on the server). Unfortunately, there is not much variety in the menu (just iOS). There is also a wait list, much like a very good restaurant, which means you may have to wait to be seated. However the wait time at the moment doesn’t appear to be very long and you are often seated within minutes.
Crittercism
Crittercism is another full-stack tool to keep track of your crash logs. It has been adopted by companies such as Netflix, Eventbrite and Linkedin. It provides support for iOS, as well as Android, HTML5 and Windows 8 (which is in beta at the moment).
Crittercism Setup and Dashboard
Setup is pretty simple. You create an account, create an application on the server, download an SDK, add it to your project, and initialize it. Then you’re ready to rock!
The screenshot below shows the dashboard view of Crittercism, along with a symbolicated crash log:
With Crittercism, not every detail is available at first glance. For example, to view the application version or contextual data, you have to navigate through the tabbed menu in the middle of the page.
The “breadcrumbs” feature of Crittercism allows you to place log statements throughout your code to get contextual information about what happened before the crash, as shown below:
Like other tools, you can mark a crash log as “known” or “solved”. Finally, the SDK includes the possibility to schedule a “Rate My App” popup via the backend, with the ability to customize when the popup appears, as well as the message displayed.
Crittercism Incident Mapping
Another interesting feature of Crittercism is the map that shows you where your logs were recorded, as shown below:
Personally, I have some concern about this, since the user is never asked for permission to calculate a user’s current position. You can use your own judgment call on this.
You can receive email notifications when a crash log is uploaded to the server, and you can set up alarms to receive SMS or email messages when crash counts pass a given threshold.
Crittercism 3rd Party Integration
As far as third party integration goes, you can hook Crittercism up to HelpShift or Uservoice. Both are customer support help-desk applications.
Crittercism Usage Tiers
There are three pricing tiers for Crittercism at the moment, as shown below:
When you sign up, you are offered a 30-day free trial which includes enterprise level features like breadcrumbs and phone support.
The price of the full-tier enterprise solution isn’t published — you’ll have to contact Crittercism directly to get enterprise pricing after the trial expires.
Crittercism is an interesting restaurant. The menu is very rich (offering iOS, Android, HTML5 and Windows 8 in beta) but the wait staff is not really friendly (requiring manual upload of dSYM files, and the usability of the website isn’t the best).
Bugsense
Bugsense is another full-stack service, used by big companies such as Samsung, Intel and Groupon. It supports iOS, Android, Windows 8, Windows Phone and HTML5.
Bugsense Setup and Dashboard The setup is pretty similar to other platforms: create an account, create an application, download an SDK, include it in your project, and set the API key.
The Bugsense dashboard for an application looks like this:
If you click one of the logs, you are presented with a detailed view, like so:
You can see the class that caused the crash — in this case, NSInvalidArgumentException
— the function generating it, and the corresponding line of code (SMViewController.m:26).
Bugsense Crash Reports
The log presentation is chock-full of detail, and the user interface allows the ability to customize which attributes are displayed. It also displays the number of times a crash has occurred. As usual, you can mark issues as resolved.
One of the downsides of Bugsense is that you have to manually upload the dSYM file of your build to allow symbolication on the server-side. You can configure the app to run symbolication right on the device, although the documentation discourages it because you don’t actually get full information such as code line numbers.
To keep track of the application’s context during run-time, you can drop breadcrumbs into your code at key points and they will be uploaded to the server, together with data about the crash. You can also use events to perform this functionality, but they still look pretty similar to breadcrumbs.
Bugsense Push Notifications
An interesting feature is “Fix notifications”. This feature allows you to send your users a push notification to indicate that an upgrade is available for the app. This is pretty handy when you have resolved a bug and released a new version, but some of your users is still running an old version.
Bugsense 3rd Party Integration
The backend of Bugsense can be integrated with JIRA to push data about crash logs. Like other tools, you will receive email notifications when the platform receives a crash log.
Bugsense Usage Tiers
There are four pricing tiers for Bugsense as shown below:
Bugsense is a very nice restaurant. The menu is really varied (iOS, Android, Windows 8, Windows Phone and HTML5) and well organized. The wait staff is not always impeccable, but given the other great features of the restaurant, it’s something you can manage to live with.
TestFlight
TestFlight was born as a tool to manage the distribution of beta releases. Over time the developers added many more features, like action logging and crash reporting. It’s like a restaurant that has a bar or a club to entertain the client before or after dinner.
TestFlight has been adopted by companies such as Adobe, Instagram and tumblr to manage over-the-air deployment, tracking and crash reporting. Both iOS and Android are supported at present.
TestFlight Setup and Dashboard
Getting started with TestFlight is similar to other crash reporting services: create an account, create an application, download and integrate the SDK, and finally, setup an application key.
On the server-side, you have to produce a build of your app, upload the .ipa
file of your distribution, at which point you can ping your testers to download the new build. This can either be done directly through the TestFlight website, or through the supplied Mac app. This app also determines when Xcode’s archive process has been completed, then prompts you to upload the new build.
The dashboard on the server-side looks like this:
A handy menu on the left allows you to browse different aspects of your build like sessions, checkpoints crashes (similar to breadcrumbs), and user feedback.
TestFlight User Feedback
TestFlight provides a feedback view in your application to collect feedback from your testers. The feedback view appears to users like this:
TestFlight Crash Reports
A crash log on the web server looks like this:
As noted previously, you can log when a user reaches a critical point in your app, such as opening a particular view. This provides you with contextual information which is useful when hunting for the root cause of a crash.
TestFlight 3rd Party Integration
At the moment there is no integration with third party tools, although you can hook up with the upload API to automate the upload phase.
TestFlight Usage Tiers
The TestFlight service is free at the moment; there’s no separate free or paid tiers. As for the restaurant analogy — this restaurant/bar/club is an interesting place to hang out if you want to spend the whole night in one spot. The menu is pretty limited (iOS and Android only), and the neighborhood is bit desolate (there’s no integration with third-party tools).
HockeyApp
HockeyApp is pretty well known in the indie developer world — probably because it’s made by indie developers! :]. It supports iOS, Android, MacOS, and Windows Phone. Like TestFlight, it’s a restaurant with perks, not just food. In fact, besides crash reporting and just like TestFlight, it includes distribution management.
HockeyApp Setup and Dashboard
Once you’ve created an account you are invited to create an app and download the corresponding SDK. The setup is pretty standard: import a framework, set up the API key and you are ready to go. Alternatively, you can opt for the more complicated approach of including the full source to the framework instead. This is good to know that option exists, incase you find a bug in the framework and desperately need the fix before the HockeyApp team fix it themselves. That’s a rare occurrence, but it’s good to know that the option exists!
Here’s what the HockeyApp dashboard looks like:
The dashboard gives you an overview of your builds with a handy menu at the top to show different sections of the dashboard along with a nice graph of various stats at the bottom.
The companion desktop application detects when the archive procedure has finished and prompts you to upload the new build, including the dSYM. Once you’ve manually uploaded the dSYM file to the server, you can select the Crashes tab, pick a log and you’ll see a symbolicated crash, as below:
The HockeyApp back-end provides the ability to perform advanced searches through your logs, using criteria like “show all of the crashes that happened on iOS6 but not on an iPad”.
HockeyApp Crash Uploads
A unique feature is the ability to upload crashes you receive via email from your users. This is made possible by the fact that the HockeyApp crash log format is similar to the format adopted by Apple. You can also log events, much like using breadcrumbs, and attach them to a crash log via a simple API.
HockeyApp 3rd Party Integration
The integration with third-party tools is quite rich. HockeyApp integrates with the following tools and services, among others:
Like all other crash reporting tools, you can set the frequency and type of email notifications that you receive from HockeyApp.
HockeyApp Usage Tiers
HockeyApp boasts four pricing tiers at the moment, with discounts available if you sign up for yearly subscriptions:
It’s worth mentioning that HockeyApp is the hosted version of the open source solution Quincykit. If you’re keen on setting up your own back-end to collect logs, check out http://quincykit.net.
HockeyApp is a nice restaurant and the organization and variety of the menu is great (supporting iOS, Android, MacOS, and Windows Phone). The neighborhood (3rd party integration) is lovely and well-populated.
Summary and Comparison Chart
I’ve provided a table below comparing the features of the tools reviewed above:
The Bottom Line
If most of your development work is on iOS, the best crash reporting tool in my opinion is Crashlytics. It’s free, with tons of features and a very usable back-end. Moreover, the reporting process is fully automated, as you get logs on the server with no need to manually upload dSYM files for each release. One drawback is that it doesn’t manage distribution of your app.
If you’re looking for a complete service cross-platform choice, I’d suggest Bugsense, because of the usability of its dashboard. Be aware, though — the cheaper tiers only retain data for a short period, ranging from 7 to 30 days.
However, all of the services reviewed above are still valid alternatives, depending on the features and usability you’re looking for.
In the second part of this article, I will show how to get started with each of these services, integrate it with your app, and give you a tour of the various crash logs and other features.
Thanks for joining me for the second part of this two-part series on crash reporting services!
The first part introduced you to the architecture of crash reporting services, including storage, symbolication, and server-side management. As well, I provided a basic overview and comparison chart of the most popular crash reporting services today.
In this second part, I’ll take you through the steps to get started with each service covered in the last article: Crashlytics, Crittercism, Bugsense, TestFlight and HockeyApp.
Throughout this article, you will work with a very simple iPhone application that just contains a table view, as shown in the screenshot below:
This particular app has been constructed to generate crash events. That’s bad news in the real word — but for our purposes, it will serve perfectly!
Getting Started
The app provides a pizza menu with two functions:
- Swipe to delete a pizza from the menu.
- Scroll to the bottom to load more pizzas.
The code for our pizza application can be found here.
Download the project, then build and run in the simulator. To cause a crash, swipe to delete a row, or simply scroll to the bottom of the list.
Although I know it’s killing you not to fix the bugs, don’t! You want the application to crash so that a reporting tool can help you to identify the source of the problem.
Here are a list of the crash reporting services that I’ll cover in this article:
- Crashlytics (www.crashlytics.com)
- Crittercism (www.crittercism.com)
- Bugsense (www.bugsense.com)
- TestFlight (www.testflightapp.com)
- HockeyApp (www.hockeyapp.net)
Keep in mind that there is a waiting period for Crashlytics where it may take several days for Crashlytics to contact you. However, if it does take a long time then no fear because Crashlytics has generously provided RayWenderlich.com with a special link that will help you jump the queue. Try that and see if you get in the VIP door a little faster! :]
Before continuing, make sure you create an account with each of the crash reporting frameworks and download each of the crash reporting SDKs.
Since some crash reporting tools require a unique identifier to function correctly, make sure to change the sample project’s bundle identifier to something of your own design, as shown below:
Once you have a set a custom bundle identifier, make sure to make a backup copy of the project. Each framework has their own installation instructions so it is best to use a “vanilla” project with each demo.
In order to test out these crash reporting frameworks, you will need to run the app on a real device. This requires an Apple developer account as well as a provisioned device. If you are a new to iOS development, you can learn how to create a developer account, as well as provisioning your own device in this tutorial.
- Build and run on Xcode.
- Press the stop button.
- Run the app on your iOS device.
- Make the app crash.
- Run the app again.
As well, make sure the device has connectivity via wi-fi or 3G so that the crash reports can be sent. Now, let’s get crashing! :]
Crashlytics
Recently bought by Twitter, Crashlytics is pretty famous in the iOS community. It’s used by well-known companies such as Path and Yammer. It is a full-stack service, meaning that the framework provides both client-side and server-side parts.
At the moment, Crashlytics supports only iOS, although the website does indicate that Android support is coming soon.
Crashlytics — Configuring the Project
Once you have logged into Crashlytics, you will be prompted to download a Mac OSX application that will help you to set up your first project.
To save you time, here’s the direct link to the Mac application. When running, the application appears in the menu bar on the top right as shown below:
Click on the icon and enter your credentials when prompted. If this is your first time using Crashlytics, you should see an empty list of applications and your account name at the bottom, as shown below:
Click on the New App button on the right. This will show you the list of recent projects opened in Xcode, like so:
If your app is not listed, you’ll need to perform the following steps:
- Click the “Other” button at the bottom to open a Finder window.
- Select the .xcodeproj file and click “Open”.
- Select your project from the list in Crashlytics and hit “Next”.
This will automatically open the project in Xcode and ask you to add a script to the Build Phase. From this point onward, the Crashlytics app will walk you through each step of installation process. Follow the instructions, and when you’re done, continue with the next step of the tutorial.
Crashlytics — Running the App
Now that you’ve completed the setup portion, you are now ready to try out Crashlytics. Run the application on your iOS device, swipe on a cell and tap Delete. The application will crash, as expected.
Restart the app and wait about a minute to make sure the crash report has propagated to the Crashlytics server. Then take a peek at the website to see if the crash report is live. You should also receive an email notification about a new crash in your application.
The new issue on the back-end should look like the following:
At first glance, you will see the guilty line of code (SMViewController.m at line 80), the number of users affected, and the number of occurrences of this crash. Launch the app and make it crash again. You’ll now see the number of crashes of the same issue increase.
Click the row of that issue and you’ll see the extended report, as shown below:
Looking at the crash log, it’s clear that you need to counterbalance the deletion of a cell with the deletion of its corresponding element in the array populating the table view. To fix this bug, open SMViewController.m and modify tableView:commitEditingStyle:forRowAtIndexPath:
to look like the following code:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { [pizzaOrder removeObjectAtIndex:indexPath.row]; [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade]; } } |
After you are done, build and run, push the app to your device, and test the application again. Once you have determined that you have fixed the issue, return to the Crashlytics crash report then set the issue status as closed, as shown below:
On to the second bug!
Crashlytics allows you to add log statements to your code to help track down bugs. Open SMViewController.m and add the following import statement at the top of the file:
#import <Crashlytics/Crashlytics.h> |
Next, modify tableView:willDisplayCell:forRowAtIndexPath:
in SMViewController.m as follows.
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.row == pizzaOrder.count-1) { CLS_LOG(@"posting notification"); [Crashlytics setIntValue:pizzaOrder.count forKey:@"numberOfPizzas"]; [[NSNotificationCenter defaultCenter] postNotificationName:LOAD_MORE_NOTIFICATION object:nil]; } }
CLS_LOG is a macro provided by the Crashlytics framework that enables remote logging. In debug builds, it acts just like NSLog, passing strings to the console. For release builds, the log is sent along with the crash reports and optimized to be as fast as possible. In fact, Crashlytics’ own documentation boasts a 10x improvement over using regular NSLog()
calls.
The method also uses custom logging which allows you to store information in key-value form. Think of this as a type of NSDictionary on the web server: you set the values of keys in the code, and you can read the values on the server when hunting down a bug.
In this case you are using setIntValue:forKey, but there are other methods available to store objects, floats and booleans as well. You can find more details about custom logging in the Crashlytics knowledgebase.
Run the application in Xcode and then stop it. Run it again on your device, scroll to the bottom, allow the app to crash, and restart the application after the crash.
The new issue will look like this on the Crashlytics dashboard:
It is clear that this is due to the lack of the method definition loadMore
in SMWebEngine.m. If you were to add the definition for this method, and the crash would disappear. Although that’s not the point of this tutorial, so don’t worry about actually implementing it!
Notice that the detailed view of each issue provides information about the device like iOS version, and free space on disk and in memory. All these details might help you when you are hunting down the root cause of a crash.
Click on “More details…” in the middle of the page. This will show even more details like the type of device, orientation, plus the keys and log statements that you have spread throughout your code, as shown in the following screenshot:
This way you can have a path of breadcrumbs that can help to hunt down the cause of the crash. It is important to note that logs and keys are sent to the server as attachments to a crash log, so you won’t see them if there aren’t any crashes.
Crashlytics, unlike other services like TestFlight, is not a generic remote logging application, but rather a true crash reporting application. So even though your app may be full of logging statements, if the app never crashes, you’ll never see any of your logs!
One useful feature for beta testing is the ability to ask your users to provide some data to identify themselves. Crashlytics provides three ways to attach this data to a crash log:
[Crashlytics setUserIdentifier:@”123456”]; [Crashlytics setUserName:@”cesarerocchi”]; [Crashlytics setUserEmail:@”cesare@mailaddress.com”]; |
This way, you can identify who is experiencing a crash, and contact that tester to get more details.
Finally, you can configure some logging features on the back-end. Each application has a settings section that you can open by clicking the gear button, as shown below:
From here you can disable reports from a specific version, or request a user for permission before sending data about the report. When you enable it (and you should!), you can customize the message as shown in the screenshot below:
Crashlytics — Summing It Up
This concludes the guided tour of Crashlytics. If you are just targeting the iOS market, I highly recommend that you consider Crashlytics. The service is very quick to upload crashes, there are no hassles with dSYM files, and the back-end service is quite intuitive.
Crittercism
Crittercism is another full-stack tool to keep track of your crash logs. It has been adopted by companies such as Netflix, Eventbrite and Linkedin. It provides support iOS, as well as Android, HTML5 and Windows 8 (which is in beta at the moment).
Crittercism — Configuring the Project
To get started with Crittercism, there are a few steps to follow:
- Register an application.
- Download and import the SDK.
- Configure the Xcode project.
After you login to Crittercism, assuming it’s your first time, you will end up at the following screen:
Tap the big blue button on the top left. You’ll be presented with the following screen:
This will ask you to assign a name; for this project, put “crashy”. Then select the iOS platform, and don’t bother to invite any collaborators. You’re just testing the application, so choose No for the question “In App Store?”. When you’re done, click the big blue Register App button at the bottom.
Next you will be prompted to download the most recent version of the SDK (which is 3.5.1 at the time of this writing). Here’s the link to the download page, just in case.
Open a new, unfixed copy of the crashy-starter project in Xcode, unzip the Crittercism SDK, drag and drop the folder named CrittercismSDK onto the root of our project, and make sure “Copy items into destination group’s folder” is checked.
Go to the Build Phases tab of the target. Open the Link Binary with Libraries section by clicking on it. You’ll notice that libCrittercism
is already linked. Add the SystemConfiguration framework and QuartzCore by clicking on the plus sign, as shown below:
Next, you need to find the App ID. This is the ID for Crittercism and has nothing to do with Apple’s application ID. Head to the Crittercism dashboard, select your application from the list, and click the settings tab on the left. You will see a section that looks like this:
This provides a portion of code to copy and paste into application:didFinishLaunchingWithOptions:
of SMAppDelegate.m. First, add the following import statement at the top of SMAppDelegate.m:
#import "Crittercism.h" |
Now add the Crittercism supplied code to the top of application:didFinishLaunchingWithOptions:
:
[Crittercism enableWithAppID:@"<YOUR_CRITTERCISM_APP_ID>"]; |
And that’s it, your project is configured to work with Crittercism!
Crittercism — Uploading the dSYM
Unlike Crashlytics, you have to manually upload your dSYM file to the server. To find the dSYM file, follow the instructions in the first part of the tutorial in the Symbolication section.
Recall from the first part of this tutorial that a dSYM is actually a directory. For this reason, you’ll need to zip your dSYM folder first before uploading it to Crittercism. To upload your zipped dSYM file use the tab Upload dSYMs in the “Settings” section of your app, as shown below:
Once you have uploaded the dSYM folder, run your application, either on your simulator or on your device, and the console should print the following message:
Crittercism successfully initialized. |
Let’s start by tracking down the first bug.
Crittercism — Running the App
As you did before, run the application without Xcode, attempt to delete a cell, and restart the application. Now visit the Crash Reports section of your application on the back-end. You should see your crash report as shown below:
The dashboard will show an overview of your application with a big graph at the top and the list of issues at the bottom. Just pretend you don’t know the what the source of the bug is, and click the issue in the list to inspect it. You’ll see a detailed report similar to the following:
The “Reason” column should lead you to think there is an issue with the table view. You might see more details, but the log is not yet symbolicated, so you don’t know which file and line of code is responsible for the crash.
You’ll need to add the log to a queue to be symbolicated, by clicking the Add to Symbolication Queue link highlighted in the screenshot above. Once it has been symbolicated, you will see the decoded thread of the crash, which indicated an issue on line 80 in file SMViewController.m, as shown below:
One shortcoming of Crittercism is that I couldn’t find a way to queue crash logs automatically as they get uploaded. If you’ve found a way around this, please let me know in the comments!
If you select the “Users” tab, you’ll notice that Crittercism automatically generates IDs to identify users, as shown in the following screenshot:
You can now mark the issue as resolved using the drop down menu on the left side of the graph as shown below:
If you’d like to collect more specific data about users, the Crittercism class also provides the following methods:
[Crittercism setUsername:(NSString *)username]; [Crittercism setEmail:(NSString *)email]; [Crittercism setGender:(NSString *)gender]; [Crittercism setAge:(int)age]; [Crittercism setValue:(NSString *)value forKey:(NSString *)key]; |
An interesting feature is the ability to log handled exceptions on the server as follows:
@try { [NSException raise:NSInvalidArgumentException format:@"Argument must be a string"]; } @catch (NSException *exc) { [Crittercism logHandledException:exc] } |
Crittercism also offers breadcrumb logging for enterprise accounts and an assortment of other features.
Crittercism — Summing it Up
This concludes the how-to portion on Crittercism. The Crittercism framework also supports Android, Windows 8 and HTML5.
I’ve found that the user interface for the back-end is a bit complex, requiring a few extra clicks to get to the required information. It’s a bit clumsy to manually upload the dSYM for each build, but overall, it’s a solid framework.
Okay Class — Time for a Break!
Okay, it’s time for a short break from your crash reporting studies. Answer the following tricky question.
Solution Inside: Is divide-by-zero an operation that causes a crash on ARM? | Show |
---|---|
BugSense
Bugsense is another full-stack service, used by big companies such as Samsung, Intel and Groupon. It supports iOS, Android, Windows 8, Windows Phone and HTML5.
Bugsense — Configuring the Project
Once you are logged in to Bugsense, you will end up in the dashboard which should be empty if this is your first time using Bugsense. Click the “Add New Project” button, enter a name for your project, select iOS for Technology, Testing for the stage, then click Submit, as shown below:
The website will then show a dashboard with no data, since you have not yet integrated the framework. When you first visit your project page, a welcoming lightbox will greet you which will contain your API key, as so:
Copy down this key as you’ll be needing it later!
Unfortunately there is no direct link to download the SDK, so you will have to click the Docs item in the top right bar of the dashboard. This will open the generic documentation page, from which you have to select iOS. You will end up at this link. This page should contain a URL to download the iOS SDK (which is version 3.2 at the time of this writing).
Unzip the downloaded ZIP file and it will create a folder named BugSense-iOS.framework. Next, open a new copy of the starter project and go to the Build Phases tab. As previously, expand Link Binary With Libraries, click the + button, choose Add other, and select the unzipped BugSense-iOS.framework folder. Also add SystemConfiguration and libz.dylib. The link section should now look like the following:
Switch to the Build Settings tab and make sure that “Strip Debug Symbols During Copy” and “Strip Linked Product” are both set to YES as shown here:
Now you just need to insert a few hooks to integrate your app with Bugsense. Insert the following statement at the top of SMAppDelegate.h:
#import <BugSense-iOS/BugSenseController.h> |
Next, open SMAppDelegate.m and add the following code to the beginning of application:didFinishLaunchingWithOptions:
, using the API key you copied down earlier:
[BugSenseController sharedControllerWithBugSenseAPIKey:@"YOUR API KEY"]; |
Now you’re ready to test Bugsense with your first bug!
Bugsense — Running the App
Just like in the previous sections, build and archive your project, install the application (.ipa file) on the device, and zip the dSYM file for later upload. Once the application is installed, run it, make it crash by deleting a row, and restart the app. Head over to the dashboard of Bugsense and open your project. You’ll see the following report:
A nice graph shows the number of crashes per day. Underneath is the list of error logs — just one in this case. You can immediately see the generic reason behind this issue; click the issue to open the detailed report as shown below:
The stack trace as shown is complete, but has not yet been symbolicated. Click the Symbolicate link on the top right. In this case, it will fail to symbolicate the trace because the dSYM is not yet on the server, and the button will turn green with a label of “Upload Symbols”.
Click the Upload Symbols button, and you will be redirected to the settings section of the application, where you can upload your zipped dSYM folder, as shown below:
Once you have uploaded your dSYM files, head back to the detailed view of the error log and again click the Symbolicate link. This time you will see that line 7 is decoded, clearly showing the line responsible for the crash:
Below the crash log, there is also a handy table showing lots of details about the crash. This table can be customized using the gear icon on the right to show many more pieces of data like locale, milliseconds elapsed from the start of the application, data about the mobile carrier and WiFi status, as shown below:
Personally, I am a bit concerned about the UDID, which has been deprecated since iOS5.
To mark a crash as resolved, return to the application dashboard, select the crash in question, then press the button labeled Resolve Selected, as below:
You can also provide the number of the release that includes the fix by clicking the Settings link in the navigation bar then selecting Fix Notifications. You can even personalize the message to be sent via push notification like so:
Bugsense also supports breadcrumbs, but they are only accessible in higher paid plans.
Bugsense — Summing it Up
This ends the how-to portion of Bugsense. The platform is quite feature-rich and supports Windows8, Windows Phone and HTML5.
A desktop application to automate the upload of dSYMs would definitely be appreciated, as well as the automatic symbolication of crash logs when they are received on the server.
TestFlight
TestFlight was born as a tool to manage the distribution of beta releases. Over time the developers added many more features, like action logging and crash reporting.
TestFlight has been adopted by companies such as Adobe, Instagram and tumblr to manage over-the-air deployment, tracking and crash reporting. iOS is the only platform supported at the moment.
TestFlight — Configuring the Project
As mentioned in the previous part of this article, TestFlight is a bit more than a crash reporting system, for it also allows you to recruit testers and distribute your test builds. Once you have logged in, head to the SDK download page to download the SDK. The current version at the time of this writing is 1.2.4.
Open a new copy of the starter project, unzip the TestFlight SDK, drag the entire folder onto the root of the project, and make sure “Copy items into destination folder (if needed)” is checked.
In the target settings, select the Build Phases tab and then open the Link Binary With Libraries section. libTestFlight.a should appear in the list of linked libraries. Also click the “+” button and add the libz.dylib as shown below:
Select the Build Settings tab and select “NO” for the following flags:
- Deployment Postprocessing
- Strip Debug Symbols During Copy
- Strip Linked Product
Compile the application using Cmd+B to check if everything is ok. Next you’ll need a particular token to make your app work with TestFlight. To get this token, go to your TestFlight team dashboard and copy the team token displayed. That’s the key you’ll need to initialize the framework in the code.
Open SMAppDelegate.m and add the following import:
#import "TestFlight.h" |
Insert the following code at the beginning of application:didFinishLaunchingWithOptions:
in SMAppDelegate.m:
[TestFlight takeOff:@"YOUR TOKEN HERE"]; ///< Replacing "YOUR TOKEN HERE" with the token you found on your team dashboard
Now you’re ready to test the application against the first bug. I suggest you download and install the companion desktop application before doing this, which you can download from here. This app is smart enough to detect when you have archived an application and offers to upload both the .ipa file and the dSYM. This will save you some pointing and clicking on the TestFlight website.
If you’ve never set up a binary for adhoc distribution, TestFlight has provided some detailed instructions to get you started.
If you use the desktop application (which I highly suggest you do!), follow the wizard’s instructions and at the end you’ll be prompted to copy out the share URL. That’s the URL of the new build you’ve just submitted — you can send this link to your testers.
To install the app to your device, simply open that URL on your device. It’s that easy! :]. Run the app, crash it by swiping and hitting “Delete”, and restart the app. Now check to see if your crash report shows up on the web back-end by heading to the TestFlight dashboard. If all went as expected, you should see something similar to the following:
This view already provides some interesting data about your build – the number of crashes, feedback and total installations. In your case, you should see one install and one crash. Click the Latest build link to get the detailed report, as shown below:
A menu on the left gives you an overview of the data related to the build, such as the number of session and the total number of crashes. In the middle section you can see the list of users and on the far right, a summary of the recent activity performed on the application by users.
Tap on the Crashes tab on the left and expand the log using the little arrow on the right. You’ll see the complete stack trace, as below:
Another interesting feature in TestFlight is the ability to add checkpoints. Checkpoints are much like breadcrumbs or events – you spread some log statements in key places in your code to get a better idea of what happened before a crash.
To place a checkpoint in your code, use the method passCheckpoint
.
Open SMViewController.m, import the TestFlight framework:
#import "TestFlight.h" |
Now insert the following code at the end of viewDidLoad
:
[TestFlight passCheckpoint:[NSString stringWithFormat:@"view loaded with %i pizzas", pizzaOrder.count]];
Create a new build and distribute it. Install it on your device, run it, scroll down the table view to make it crash, and restart the app to allow the sending of logs to the server. Now open the dashboard, select the new build and click the Checkpoints tab on the left to see the list of checkpoints. You’ll see something similar to the screenshot below:
If you click the Crashes tab, the second bug report will appear:
TestFlight offers a feature which is especially nice during beta testing – collecting feedback from your users directly in your app! Open SMViewController.m, and the following import statement to the top of the file:
#import "TestFlight.h" |
Now modify tableView:commitEditingStyle:forRowAtIndexPath: as follows:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{[TestFlight openFeedbackView];}
Create a new build and upload it. Open the app, swipe, and tap to delete. Once the delete button is tapped, a view controller like the following will appear:
From here the user can send you direct feedback about the current build. You can see the messages sent by users in the “Feedback” section of your build, as so:
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
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:
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:
- Create an API token.
- Download and install the desktop app.
- Configure your project with the API token.
- Configure your Xcode project.
- Archive the project.
First, click your account name on the top right and select “API Tokens”, as shown below:
Next, you can set the access privileges to the platform in the dialog presented below:
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:
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+<), expand the Archive action, and finally select Post-actions, as shown below:
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}" |
Click OK to save. Now archive your current project. The desktop application should detect the archive and show the following dialog:
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:
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:
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:
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.
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:
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:
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:
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.
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.
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:
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:
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.
HockeyApp — Summing it Up
HockeyApp is a great tool to manage distribution, remote logging and crash reporting. A disadvantage of the service is that it does not include built-in logging. I really appreciated the automation of hooking up with the archive action of Xcode, which saves you some time and stress when you create a new build.
Sometimes the information you need is a bit hidden, such as remote logs, and requires quite a number of clicks to be displayed. I have already expressed my preference for Crashlytics if you need just crash reporting and logging, but I’d definitely consider HockeyApp if you need also distribution management.
Conclusion
For your benefit, I’ll repeat the comparison table included in the first part of the tutorial:
I’ll confirm the verdict that I expressed in the first tutorial: Crashlytics is an excellent solution if you need to be up and running quickly and you like a well done back-end.
If you need more than just crash reports and remote logging, and if you are targeting platforms other than iOS, HockeyApp and Bugsense will work really well for you.