In Swift, how do I capture every error and send it to a webservice?

I am a solo developer at a government agency. This presents some interesting challanges. First and foremost, I create apps that are not able to access internet resources, and thus I don't have access to some tools that would provide the easy answer to a lot of my problems.


My current problem is that I don't have the ability to use some of the very robust error handeling products out there. However, I need a way to capture crashes on devices that are not teathered to my workstation. I need robust reporting and the ability to see logs for anything that happened on devices that are deployed in places that are not accessable to me easily (think maximum security portion of a ****).


My current thought is capturing every error and sending it to an internal webservice. However, I don't know how to do this without surronding every single line in a try catch.


Does anyone have any experience with this, or with something else entirely that would get me the information I need to debug crashes?


Thanks in advance

Replies

You need to understand that there are three different types of errors in play here:

  • machine exceptions, like accessing unmapped memory

  • language exceptions, where Objective-C (or C++) code throws an exception

  • Swift errors

Swift’s try/catch mechanism will only catch Swift errors. Catching the others is quite a bit trickier.

In general I strongly recommend against implementing your own crash reporting mechanism. It is impossible to make one that’s as reliable as the mechanism built in to the system. However, if you’re working in a secure environment then the Apple crash reporter is not going to work for you.

It seems to me that MDM should provide a crash reporting facility for users like you. AFAIK it does not, but that would make a fine enhancement request. Please post your bug number, just for the record.

Given the above it seems that you’ll need your own crash reporter. I wouldn’t try to write one from scratch; it’s a lot of ugly low-level code. Instead I recommend that you adapt one of the existing solutions. I don’t have a specific recommendation for you because I don’t have any experience with them (as I mentioned above, I strongly recommend that normal developers stick with the Apple crash reporter), so you’ll have to dig into this yourself.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Any chance there is a way to write to a local (on device) log that can be accessed later?

Sure, if you don't mind connecting the devices to Xcode later, and are willing to compromise the results, meaning you'll mainly get what you trap...but you indicated above that those weren't an option, so...


As Eskimo states, the system you want isn't recommended.

Any chance there is a way to write to a local (on device) log that can be accessed later?

Crash reports will be written to disk by the built-in Apple crash reporter. They’ll then be uploaded to your Mac (or Windows machine) the next time you sync. The process for doing this is described in Technote 2151 Understanding and Analyzing iOS Application Crash Reports.

The gotcha here is that this is a very manual process. If your users are syncing their devices for other reasons, you could run code on their Mac to grab these crash reports and upload them to your internal system. However, if the user’s don’t sync their devices regularly, these crash reports will remain stuck on the device until they next sync.

You can’t access these crash report from code running on the device because they are ‘owned’ by the system, and thus walled off from your app by the sandbox.

The alternative is to build your own in-app crash reporter that writes its crash reports to some place in your app’s container, whereupon the app can upload the crash report to wherever it wants when it’s next launched. The key problem with this is that the first step, building your own crash reporter, which has two major drawbacks:

  • It’s a bunch of gnarly low-level code

  • It will reduce the quality of your crash reports, because it’s impossible to make a crash reporter that’s as reliable as the mechanism built in to the system

Regardless of what else you do here, please do take the time to file an enhancement request for MDM support for this.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

This may be an old question, and there may be recommendations against rolling your own solution, but for those who prefer to write their own solution, I wanted to share mine.

To write your own crash reporter you need to capture signals and exceptions. To capture signals, use a handler in this format:

signal(SIGINT) { _ in
  var threadStackTrace = ""
  _ = Thread.callStackSymbols.map({ threadStackTrace.append("\($0)\n") })
}

This will build a string representing the lines of a stack trace. SIGINT is just one of the signals that you need to capture. There are 14 other signals that I capture in my crash reporter. You'll need a separate handler for each signal. Here are a few other signals I capture: EXC_BREAKPOINT, EXC_CRASH, EXC_BAD_ACCESS, EXC_BAD_INSTRUCTION, SIGABRT, SIGKILL, SIGTRAP, SIGBUS, SIGSEGV, SIGHUP, SIGTERM, SIGILL, SIGFPE, SIGPIPE. There may be others that should be handled. In Xcode, 'Jump to Definition' to see other signals.

You also need to capture uncaught exceptions. You can do that with:

NSSetUncaughtExceptionHandler { (exception: NSException) in
  var exceptionStackTrace = ""
  exceptionStackTrace.append("\(exception.name)")
  exceptionStackTrace.append("\n\n")
  exceptionStackTrace.append("\(exception.reason ?? "")")
  exceptionStackTrace.append("\n\n")
  _ = exception.callStackSymbols.map({ exceptionStackTrace.append("\($0)\n") })
}

The exception.name will give you a name such as NSInvalidArgumentException. The exception.reason will explain the error. Then, exception.callStackSymbols is a collection of stack lines. Unlike with signals, you only need one uncaught exception handler.

Then, you can transport your signal or uncaught exception error string via HTTP to your web server:

let request = NSMutableURLRequest(url: ENDPOINT!)
request.httpMethod = "POST"
let body = parameters.data(using: String.Encoding.utf8)
request.httpBody = body
let task = URLSession.shared.dataTask(with: request as URLRequest)
task.resume()
RunLoop.current.run(until: Date.init(timeIntervalSinceNow: 3))

Finally, encapsulate all the signal handlers and uncaught exception handler in a class and call it once at didFinishLaunchingWithOptions.

The crash report you receive may contain lines that are unsymbolicated and unreadable. Other solutions in the market will upload the .dSYM file so that the crash report can be symbolicated on the server and method names will appear in the crash report. I don't currently have my own solution for this but often the crash report received can contain usual information, enough to identify the problem. I keep my solution as a Swift Package on GitHub at https://github.com/MerchV/iOSCrashReporter

  • Hey I loved your solution to this. Just one question, I noticed you don't log the error message when handling crashes caused by signals, only the stack trace. Do you know how I could go around to catching the actual error message when catching an exit signal? Thanks!

Add a Comment