Error Monitoring
Monitor crashes, ANRs, unhandled errors, and handled errors across your mobile app. See what broke, how often, and what the user was doing when it happened.
Measure tracks crashes, ANRs, and other errors in your app. Crashes and ANRs are reported with a session replay that shows the steps the user took before it happened. Similar errors are grouped into a single issue so you can tell how often something happens and what to fix first.
Error severity
Every error is classified by severity.
- Fatal errors are ones that crash the app.
- Unhandled errors are ones that escape your code without crashing the app.
- Handled errors are ones that you catch and report manually.
Crash reporting
Measure reports crashes automatically. What gets captured and how it's captured depends on your platform.
Android
When an uncaught exception terminates the app, it's recorded as a fatal error with the full stack trace and the session that led up to it.
If you obfuscate your code with R8, set up the Measure Android Gradle Plugin. It uploads the mapping.txt file so stack traces are readable.
iOS
A crash report is saved the moment a crash happens and is uploaded the next time the user reopens the app. Crashes are captured using KSCrash.
By default, the stack trace contains just memory addresses. Upload your dSYM files in your release pipeline or from an Xcode build phase using these scripts.
Flutter
Most Flutter errors are Dart errors that the framework catches before they can crash the app. Measure hooks into FlutterError.onError and PlatformDispatcher.onError to record them automatically as unhandled errors. When a crash happens on the native layer, it's captured automatically as a fatal error.
If you obfuscate your Flutter code with --obfuscate and --split-debug-info, upload the generated symbol files so stack traces are readable. See Upload symbols for each platform's steps.
React Native
A fatal JavaScript error is captured automatically as a crash. Non-fatal JavaScript errors and unhandled promise rejections are captured as unhandled errors. When a crash happens on the native layer, it's captured automatically as a fatal error.
Upload your JavaScript sourcemaps so stack traces are readable. See Upload symbols for the steps.
Kotlin Multiplatform
Crashes are captured on both the Android and iOS targets, including crashes in your shared Kotlin code.
On iOS, the stack trace includes the Kotlin frames, pointing straight to the shared code that failed:
Thread 0 Crashed:
0 FrankensteinApp kfun:com.frankenstein.shared.demos$6.$<bridge-DNN>invoke() (CmpScreen.kt:210)
1 FrankensteinApp kfun:com.frankenstein.shared.ActionCard$$inlined$cache$1.invoke() (CmpScreen.kt:150)
2 FrankensteinApp kfun:androidx.compose.foundation.ClickableNode#onPointerEvent() (Clickable.kt:940)
3 UIKitCore -[UIWindow sendEvent:] + 2996
...ANR reporting
An ANR (Application Not Responding) happens when the app's main thread is blocked for too long, for example when it can't respond to input within 5 seconds. ANRs are reported automatically. How an ANR is reported depends on the Android version.
On Android 11 and above, the ANR is read from ApplicationExitInfo. Below Android 11, the ANR is caught by intercepting the SIGQUIT signal and capturing a stack trace manually when it happens.
Native Crashes (Android)
Native crashes require Measure Android SDK 0.23.0 or later and Measure Android Gradle Plugin 0.15.0 or later.
Native crashes in C, C++, Rust, or any other native code, including third-party SDKs, are reported on Android 12 and above. Native crash reporting is not turned on by default. To turn it on, set enableNativeCrashReporting in the measure block of your app's build.gradle.kts:
measure {
enableNativeCrashReporting = true
}Memory errors
Native crashes caused by a memory error, like a use after free, a double free, or a buffer overflow, also show where the memory was allocated and where it was freed. These errors are marked with a GWP-ASan label on the Errors page, and you can filter the page by Memory error to see only them.
Memory errors are caught by GWP-ASan and by Arm Memory Tagging Extension (MTE) on devices and apps that have it turned on. GWP-ASan is built into Android and checks a small, random sample of your app's memory allocations. When it catches a memory error, what happens next depends on the mode it runs in.
- Recoverable mode. The app keeps running. Measure reports the memory error as an unhandled error the next time the app starts.
- Crashing mode. The app crashes as soon as the memory error happens. Measure reports it as a fatal crash, the same as any other native crash.
The mode depends on the Android version, and on whether you set android:gwpAsanMode="always" on the <application> tag in your AndroidManifest.xml.
<application
android:gwpAsanMode="always"
... >| Android version | gwpAsanMode not set | gwpAsanMode="always" |
|---|---|---|
| 12 and 13 | Off | Crashing mode |
| 14 | Recoverable mode | Crashing mode |
| 15 and above | Recoverable mode | Recoverable mode |
GWP-ASan doesn't run on every launch of your app by default. On Android 14 and above, when you don't set gwpAsanMode, Android turns GWP-ASan on for about 1% of app launches. When you set gwpAsanMode="always", GWP-ASan runs on every launch of your app, on all supported Android versions.
App hang reporting
App hangs require Measure iOS SDK 0.15.0 or later.
Measure detects hangs on iOS and reports how long the main thread was blocked, where it happened, and whether the app recovered or got killed. App hangs are visible in session replays, alongside the user actions and app state that led up to them.
App hangs are listed on the Errors page. Hangs blocked at the same place in your code are grouped into a single issue, whether the app recovered or got killed.
App hangs are monitored by the Measure SDK, iOS itself does not report them as an error. A background thread sends a small probe to the main thread at a predefined interval and waits for an answer. If no answer arrives within the configured threshold, the main thread is considered blocked, and the SDK captures its stack.
Every app hang has a status.
- Recovered means the main thread started responding again. The reported duration is how long it was blocked.
- Killed means the app got killed while in the hung state, usually because iOS terminated it for being unresponsive.
Track a handled error
Errors you catch and recover from can be manually reported. See Track errors for the full signature.
import sh.measure.android.Measure
try {
methodThatThrows()
} catch (e: Exception) {
Measure.trackHandledException(e)
}Add attributes
Attach attributes to a handled error to record additional context about the error. You can filter and group by these attributes on the dashboard. See Attribute limits for the allowed keys and values.
import sh.measure.android.attributes.AttributesBuilder
val attributes = AttributesBuilder().put("screen", "Login").build()
Measure.trackHandledException(e, attributes)Session replays
Measure collects a session replay with every fatal error by default, and you can turn it on for unhandled and handled errors through Adaptive Capture. It's an ordered list of what happened leading up to the error: the screens opened, taps, network calls, logs, app lifecycle events, network changes and more. Use it to retrace exactly what the user did before it broke.
By default the replay covers the 5 minutes before the error. A longer window gives more context but collects more data. Change it from the dashboard through Adaptive Capture.
Screenshots
Each crash includes a screenshot of what was on screen when it happened, and so does each ANR below Android 11. Sensitive content is masked on the device before the screenshot is uploaded, and the screenshot mask level controls how much is hidden. See Screenshot masking for how masking works on each platform and the APIs to mask specific views.
Screenshots for crashes aren't supported on iOS. ANRs on Android 11 and above don't have one either.
Alerts
A spike of crashes or ANRs triggers an alert over email and Slack. Handled errors never trigger alerts. See Alerts for how spikes are detected, how to tune the thresholds, and how to set up delivery.
Configuration options
Errors, ANRs and app hangs are configured remotely through Adaptive Capture, so you can change these without shipping a new build:
| Option | Default | Description |
|---|---|---|
| Screenshots | On | Whether a screenshot is captured for fatal errors and for ANRs below Android 11. |
| Session replay | Fatal errors only | Which error severities come with a replay. |
| Session replay duration | 5 minutes | How far back the replay reaches before the error. A longer window gives more context but collects more data. |
| Sampling | 100% fatal and unhandled, 0% handled | How many errors of each severity are reported. |
| Screenshot mask level | All text and media | How much of each screenshot is masked to keep sensitive content from leaking. |
See Errors for each setting.
App hangs have their own settings:
| Option | Default | Description |
|---|---|---|
| Hang threshold | 2 seconds | How long the main thread must be blocked before it counts as a hang. The SDK will not go below 1 second. |
| Sampling | 100% | How many app hangs are reported. |
| Session replay | On | Whether an app hang comes with a replay. |
| Session replay duration | 5 minutes | How far back the replay reaches before the hang. |
See App hangs for each setting.
Think this page can be better?
Open an issue