Getting the most out of your crash reporter
Tags: ios, swift, debugging
Published: 2021-11-10 04:13:19

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/umsgx1u3q4uluizyqgky.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fi7r1j7h1ah9n2aubn4k.png)
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.

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

// Call this when the user is logged out
Crashlytics.crashlytics().setUserID(“”)
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6ebxpgkzl4fonsvr742p.png)

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k13ihn3l0ov4u4z2ysoi.png)

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

```swift
crashlytics.setCustomValue("My value", forKey: "My key")
```

Once you're done, it will show up like below. 
![Debugging information provided by the code snippet above](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6kjqt4bsl8lyoc1wghbg.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/24f2ejk2gnthmn64httr.png)

### 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5uez86h2taes4x7t4dt8.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bikwma3jfzu6ouyhr9ef.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q5v05k35b3c7t0e8w99i.png)


#### 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jkqabsxfo7yqqmf8dhml.png)

### 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fi7r1j7h1ah9n2aubn4k.png)

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
```swift
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    Crashlytics.crashlytics.log("\(self)")
}
```

And here is the result. You can tell immediately where the crash happened.
![Crashlytics now shows visited view controllers in order before the crash](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qbmxz6v466dior4bwk5w.png)

#### 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.
```swift
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(_:))
    )
}

```

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!