DEV Community

Samuel Edwin
Samuel Edwin

Posted on

Getting the most out of your crash reporter

We developers are not perfect humans, we make mistakes from time to time. Crashes are one of the most common problems we encounter because of those mistakes.

Crash reporters have been an iOS developer's long time friend. They make it easy to find out where things go wrong like the image below.

A stack trace that points to ViewController.buttonDidTap, easy to debug the source of crash

You know immediately that the crash happened in ViewController.btnDidTap. All you have to do then is to try reproducing the problem in that method.

In other cases though, crashes can be hard to debug, like this one
A crash related to UICollectionView, main cause is unknown
This happened when we did something wrong with UICollectionView. This one can tough to debug if we have dozens of view controllers that use UICollectionView. It's not obvious which view controller that caused the crash.

What should you do in this situation?
Add more debugging information.
A lot of people know how to use Crashlytics, but not many know how to improve the debugging information.
Here are some less well known features of Crashlytics that I'm sure will be useful for you.

Note

In this post I'm going to use Firebase Crashlytics as an example because it is the most popular one. Other crash reporters also have similar features so you can still benefit from the techniques I will describe.

1. Filtering by user id

Imagine how easy it would be if you can just limit the search of your crashes by user id. This is what Crashlytics has already provided.

Adding user id is only one line of code away. Add this code below whenever your user has logged in or logged out from your app.

// Call this when the user is logged in
Crashlytics.crashlytics().setUserID(12345)

// Call this when the user is logged out
Crashlytics.crashlytics().setUserID(“”)
Enter fullscreen mode Exit fullscreen mode

Once you added this code, use the search bar to filter crashes made by this user.
Using the search bar to filter crashes made by a specific user

2. Adding custom value

Crashlytics has already provided us a lot of useful information such as the iPhone type, OS version, device orientation and so on.
Default data provided by Crashlytics

This is not enough in some cases, and fortunately adding more information also takes only one line of code.

crashlytics.setCustomValue("My value", forKey: "My key")
Enter fullscreen mode Exit fullscreen mode

Once you're done, it will show up like below.
Debugging information provided by the code snippet above

Here are some ideas of custom values you should put in Crashlytics:

  • Calendar type
  • Language
  • Region
  • Time Zone
  • Accessibility settings

This is how the information going to look like
Debugging information after putting extra keys and values

Case study: A complaint from year 2560

A few years ago we received a crash report in a certain view controller. We know where it happened but not what triggered it.

After looking at the custom values, we noticed a pattern. it is immediately clear that all the crashes happened only when our users use the Buddhist calendar in their phone.

3. Logs/Breadcrumbs

Another tool that I find useful is the logging capability. It shows events in order of time. This improves debugging speed because we know the steps to reproduce it.

Sample log

Where should you add the logs?

App life cycle

Some crashes happen not when the user is using the app, but when the app is in background. By logging every life cycle method, you can know for certain when the crash happened.

Here are some of the app life cycle methods you should log

  • application(didFinishLaunchingWithOptions:)
  • applicationWillResignActive
  • applicationDidEnterBackground
  • applicationWillEnterForeground
  • applicationDidBecomeActive
  • applicationWillTerminate

Crash report after logging app life cycle

Using the above image as an example, you can clearly tell that the crash happened when the app entered the background state.

App entry points

The iOS app ecosystem is getting more complex each year. There are a few ways to launch the app besides tapping the icon from the home screen:

  • Universal linking
  • Push notification
  • Home screen quick action or 3d touch
  • Siri Shortcut
  • WidgetKit
  • And many other ways

Here is the log when the crash happened when the user is in another app:
Crash happened in background state

Case study: Can't notify the dead

There was a crash that happened in a very specific condition:

  1. The app must be in a killed state
  2. The crash must be triggered only with a specific push notification payload.

By logging the app life cycle and app entry points, we can see how to reproduce this specific scenario. This would be a chore to find without the help of logging.

Crash happened when the app is terminated

Opening view controllers

Let's go back a bit to the UICollectionView crash I mentioned before.
A crash related to UICollectionView, main cause is unknown

If we have a list of previously visited view controllers, finding out where the crash happened would be easy.

One straightforward way to add the log is by using this code

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    Crashlytics.crashlytics.log("\(self)")
}
Enter fullscreen mode Exit fullscreen mode

And here is the result. You can tell immediately where the crash happened.
Crashlytics now shows visited view controllers in order before the crash

But I have 100 view controllers 😰

Adding the code snippet above for 5 view controllers is not a problem, but it can be tedious when you have 100 view controllers.

Fortunately there's a solution, you can use swizzling to log all the view controllers.

For those who never heard of swizzling, it is a way to swap a method implementation with ours. You can find more about it by searching the internet.

This code snippet below replaces every UIViewController's viewDidAppear method that is implemented by Apple with our implementation.
Note that I'm using a library called JRSwizzle in this example to make swizzling quick simple.

extension UIViewController {
    // This method that will replace Apple's viewDidAppear implementation
    @objc public func swizzled_viewDidAppear(_ animated: Bool) {
        Crashlytics.crashlytics().log("\(self)")

        // call original viewDidAppear
        self.swizzled_viewDidAppear(animated)
    }
}

// Do the swizzling at the start of the app
func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
    FirebaseApp.configure()

    // From now on, swizzled_viewDidAppear will be called time a view controller appears on screen
    try? UIViewController.jr_swizzleMethod(
        method: #selector(UIViewController.viewDidAppear(_:)),
        withMethod: #selector(UIViewController.swizzled_viewDidAppear(_:))
    )
}

Enter fullscreen mode Exit fullscreen mode

Now Crashlytics will automatically log every view controller that is visited by the app 🥳.

Conclusion

Debugging crashes can be difficult sometimes, but crash reporters have some tools that you can use to make them easier.

If you have been scratching your head figuring how to reproduce this crash, try the techniques described above. I hope it can solve your problems!

Top comments (0)