MeterReporter: Lightweight MetricKit Diagnostics

MetricKit is an incredible source of diagnostic information for your app. Not only can it get you some information that is inaccesible otherwise, but it does so in a way that respects the user’s privacy decisions. Unfortunately, it’s actually quite hard to use in practice. At a minimum, you need to capture the information, send it out to some server, and then process it into a human-readable form. But, each of this steps turns out to be a real pain.

MeterReporter is a open source library that makes getting at MetricKit data easier.

Uncaught Exceptions

One of the first big shortcomings I discovered about MetricKit-based crash reporting was a lack of uncaught exception information. My suspicion is MetricKit has a strict policy against capturing application-generated data. And, I also believe this won’t change in the future, for two reasons. First, any system that sends back in-app data could leak potentially private user information. Second, exceptions are playing less and less of an important role in application development on Apple platforms, so the need to address this is decreasing.

While their exception use might be on the decline, they are still quite common and the extra information can be helpful. The exception name and reason can provide invaluable context for understanding why the exception occurred. And, the throw-site stack trace, which is not necessarily the same as the crash site, can be critical. In particular, because of generally poor exception handling behavior in AppKit (and some other macOS frameworks), it’s vital for macOS developers.

Meter includes a simple facility for including runtime exception information in a MetricKit payload. This is a fully automated process for iOS apps. MeterReporter also includes a way to capture uncaught exceptions in an AppKit app. It isn’t entirely automatic, but at least it’s possible.

Symbolication

File/line information for your apps’ frames are usually extremely useful, but even function names go a very long way. Without either, you probably aren’t going to get very far. The process of adding this information to a report is called symbolication. MetricKit leaves symbolication completely up to you. You’ll typically use a dSYM from your app to symbolicate your app’s frames. This is annoying and laborious, but at least it’s possible.

There will also be a bunch of OS frames in a report. Those are technically possible to symbolicate, but it’s much more involved. You can only symbolicate against the exact version of a binary that was loaded at the time of the crash. This changes for every OS release, and often also across devices. This means, even for just iOS 15 releases, you’re looking at thousands of possible variants. And, even if you do have these binaries, actually performing the symbolication is quite challenging. There’s a Swift library available that can do it, but it’s really not something most people are going to be willing to deal with.

This is another place where Meter can help. It includes a system for symbolicating both app and OS frames on the device before the data is submitted. This system makes use of the binaries on the affected device to look up symbol information. It is not perfect, and the current implementation doesn’t always produce a result on iOS. But, it is far simpler than the alternative.

Transmission

The last piece of the puzzle may seem like the simplest - just getting the data back to a server you control. A simple NSURLSession setup can certainly work here. But, MeterReporter uses a much more robust system, backed by a library called Wells. Wells supports background uploads and a robust retry mechanism. These are both surprisingly difficult because they have to work across application launches and cannot persist state in memory. But, the payoff is reliable and power-efficient diagnostics reporting.

By default, MeterReporter will issue an HTTP PUT with the data. You can easily configure the endpoint that it will point the request to. And, it will also include a few headers with some metadata about the report. You’ll get the platform, a unique report identifier, and your application’s bundle identifier. Everything your server should need to process the data.

Conclusion

I get that most developers are going to opt for a full-featured in-process diagnostics system. There are many out there, they are high-quality, group related crashes, and take care of lots of details for you. Or, perhaps you just want to go with Apple’s own diagnostics, which has gotten consistently better each year. You get Apple’s system for free, so you could easily just use both. But, there are still some compelling reasons for going the MetricKit route.

First and foremost, Apple’s system only works for App Store-delivered apps. So, if you have a macOS app that lives outside of the App Store (ahem), Apple’s system is just not an option. MetrickKit also has a strong focus on respecting the user’s privacy. It can capture data, including many types of crashes that are impossible to detect using an in-process system. And, it is an incredibly lightweight solution that imposes basically no overhead in your app.

Yes, you do need to run a server to accept the reports. But, it’s very doable, and at very low cost. And, if MetricKit sounds like something you might want for your application, MeterReporter might be just the ticket.

Tue, Mar 29, 2022 - Matt Massicotte

Previous: Supporting older SDK versions with Swift
Next: Hello Ruby!