The Atatus iOS SDK reports crashes and errors as part of the RUM session they happened in, so you can see the screens, taps, and requests that led to a failure rather than a stack trace on its own.
This page covers the five kinds of failure the SDK reports: crashes, app hangs, watchdog terminations, memory warnings, and errors you report yourself.
Prerequisites
- RUM is set up in your application. See iOS Monitoring Setup.
- The
AtatusCrashReportingproduct is added to your target, for crash reporting.
Enable crash reporting
Crash reporting is a separate product. Enable it after RUM:
import AtatusCore
import AtatusRUM
import AtatusCrashReporting
Atatus.initialize(
with: Atatus.Configuration(
licenseKey: "<LICENSE_KEY>",
env: "<ENV_NAME>",
service: "<APP_NAME>"
),
trackingConsent: .granted
)
RUM.enable(
with: RUM.Configuration(
uiKitViewsPredicate: DefaultUIKitRUMViewsPredicate(),
uiKitActionsPredicate: DefaultUIKitRUMActionsPredicate()
)
)
CrashReporting.enable()
The order matters. A crash is delivered as a RUM error, so CrashReporting.enable() must come after RUM.enable(with:). Enable it before RUM and the pending crash from the previous run is dropped, with only a console warning to show for it.
There is nothing to configure. CrashReporting.enable() takes no options.
What is captured
The SDK installs handlers for the failures that kill a process:
- Fatal signals, such as
SIGSEGV,SIGABRT,SIGBUS,SIGILL,SIGFPE, andSIGTRAP. - Uncaught Objective-C and Swift exceptions.
- Uncaught C++ exceptions.
A crash cannot be uploaded from a dying process, so the report is written to disk and sent on the next launch. It is attributed to the session and view that were active when the application died, which is why a crash appears in the session that caused it rather than the one that reported it.
A crash is only reported if tracking consent was .granted during the run that crashed. One pending report is kept at a time, so two crashes in quick succession report the most recent one.
Read a crash report
Crash reports leave the device unsymbolicated: each frame is a memory address plus the binary image it belongs to. Atatus resolves those addresses into file names, functions, and line numbers using the debug symbols for the exact build that crashed.
That makes the dSYM files part of every release you ship. Keep them for each build, including App Store builds, where Xcode rebuilds the binary and generates new dSYMs that you download from the Organizer or from your CI artifacts. Without the matching dSYMs, a report shows addresses only.
Stack traces are capped at 200 frames. When a stack is longer, frames are dropped from the middle so the crash site and the entry point both survive, and the report is marked as truncated.
Report app hangs
An app hang is the main thread blocking long enough that the interface stops responding. Detection is off by default. Turn it on with a threshold in seconds:
RUM.enable(
with: RUM.Configuration(
appHangThreshold: 0.25
)
)
Hangs are reported as errors with the type AppHang, not as long tasks. A hang that the application never recovers from, because the user force-quit it or the system killed it, is reported on the next launch as a fatal hang.
| Detail | Behavior |
|---|---|
| Minimum threshold | 0.25 seconds is a reasonable floor. Values below 0.1 are raised to 0.1 with a console warning |
| Tolerance | Hangs within about 2.5 percent of your threshold may not be reported |
| Upper bound | Hangs longer than 30 seconds are discarded as false positives, since they usually mean the device was suspended |
| Stack trace | Requires crash reporting. Without it the error says so in place of the trace |
Pick a threshold you will act on. 0.25 seconds catches interface stalls a user notices, while a lower value reports work that is invisible in practice and produces volume you will learn to ignore. Hangs are only detected in the main application, not in app extensions or widgets.
Report watchdog terminations
iOS kills an application that uses too much memory, takes too long to launch, or stops responding to the system. These terminations never reach a crash handler, so the SDK infers them on the next launch:
RUM.enable(
with: RUM.Configuration(
trackWatchdogTerminations: true
)
)
Watchdog termination tracking needs CrashReporting.enable() as well. With crash reporting absent, the flag has no effect and the SDK emits no warning to tell you so.
The SDK reports a termination only when it can rule out every other explanation: a crash, a graceful exit, an app update, an OS update, a device reboot, a reinstall, a debugger session, and a run on the simulator. In practice this means terminations are reported from release builds on real devices.
The report carries the type WatchdogTermination and no stack trace, because none exists. One case it cannot distinguish: a user force-quitting your application from the app switcher while it is in the foreground is reported as a watchdog termination.
Report memory warnings
Memory warnings are reported as errors by default, which is how you find the screens that precede an out-of-memory termination:
RUM.enable(
with: RUM.Configuration(
trackMemoryWarnings: true
)
)
Each warning becomes an error with the type MemoryWarning and no stack trace, attributed to the view that was active.
Report errors yourself
A failure your code handles still matters, because a retry loop or an empty screen is invisible in crash data. Report it:
import AtatusRUM
do {
try placeOrder()
} catch {
RUMMonitor.shared().addError(error: error, source: .source)
}
Or report a message when you have no error value:
RUMMonitor.shared().addError(
message: "Payment declined",
type: "PaymentError",
source: .source,
attributes: ["order.id": "A-1234"]
)
The source tells you where the failure came from: .source for your own code, .network for a request, .webview for embedded web content, .console, .logger, and .custom. The default is .custom. When you omit the stack, the SDK records the file and line of the call.
Group errors with a fingerprint
Errors are grouped by their message and stack trace, so a message that embeds an identifier splits one problem into many. Set a fingerprint to group them yourself:
RUMMonitor.shared().addError(
message: "Checkout failed for order A-1234",
source: .source,
attributes: [RUM.Attributes.errorFingerprint: "checkout-failed"]
)
Every error with the same fingerprint is grouped together regardless of its message.
The fingerprint applies to errors you report and to failed resources. Crashes, fatal hangs, watchdog terminations, and memory warnings are grouped by the SDK and cannot be fingerprinted this way.
Redact or drop errors
An error event mapper runs on every error before it is uploaded. Use it to strip sensitive content, or return nil to drop the error:
var configuration = RUM.Configuration(
uiKitViewsPredicate: DefaultUIKitRUMViewsPredicate()
)
configuration.errorEventMapper = { event in
var event = event
if event.error.message.contains("internal-debug") {
return nil
}
event.error.message = event.error.message.replacingOccurrences(
of: #"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+"#,
with: "<email>",
options: .regularExpression
)
return event
}
RUM.enable(with: configuration)
The fields you can change are the message, the stack, the fingerprint, the causes, and the event context. The category, type, source, and threads are fixed.
Crashes cannot be dropped. Returning nil for a crash sends the unmodified event and logs a warning, because losing a crash is worse than keeping one you did not want. Redaction still works. Fatal hangs and watchdog terminations skip the mapper entirely, so plan your redaction around that.
Errors from logs
Logs written at error or critical are also reported as RUM errors, with the source logger. See Log Collection.
Objective-C
@import AtatusRUM;
@import AtatusCrashReporting;
ATRUMConfiguration *configuration = [ATRUMConfiguration new];
configuration.appHangThreshold = 0.25; // 0 disables hang reporting
configuration.trackWatchdogTerminations = YES;
configuration.trackMemoryWarnings = YES;
[ATRUM enableWith:configuration];
[ATCrashReporter enable];
[[ATRUMMonitor shared] addErrorWithMessage:@"Checkout failed"
stack:nil
source:ATRUMErrorSourceSource
attributes:@{}];
In Objective-C, appHangThreshold is a plain NSTimeInterval where 0 means disabled, and addError takes no error type.
Platform support
| Platform | Notes |
|---|---|
| iOS, iPadOS | Everything on this page is supported |
| tvOS | Supported, except that memory warnings follow the same rules as iOS and actions are press based |
| App extensions | Crashes are reported. App hangs and watchdog terminations are not |
Next steps
- Measure launch time, hangs, and frame rate with Mobile Vitals.
- Watch a failing session back with Session Replay.
- Forward your application logs with Log Collection.
+1-415-800-4104