Crash Reporting
The mobile app uses Sentry for crash reporting, fatal exception capture, native crash diagnostics, Rust panic diagnostics, and a small curated breadcrumb trail. Selected non-production release builds can also opt into device logs and Sentry Logs for troubleshooting. The setup is intentionally split across Flutter, native Android/iOS, and Rust so each layer owns the failures it can diagnose best while using the same release, environment, and privacy rules.
Sentry is enabled only when a non-empty SENTRY_DSN is provided. The configured
environment labels events but does not decide whether logs are enabled. Builds
without a DSN run without Sentry telemetry.
Runtime Ownership
Each runtime owns one failure domain:
Flutter owns uncaught Dart exceptions and explicit Dart-side error events.
Android and iOS own native crashes, Android ANRs, iOS app hangs, watchdog terminations, tombstones, and native crash artifacts.
Rust owns Rust panics and categorized Rust error events emitted through
#[sentry_capture_error].
Native Sentry startup lives in the app shells:
Android initializes Sentry from
WalletApplication.iOS initializes Sentry from
AppDelegate.platform_supportstays limited to bridge/runtime support and does not own crash-reporting policy.
Flutter initializes Sentry around the app runner and disables implicit native SDK ownership:
autoInitializeNativeSdk = falseenableNativeCrashHandling = false
That keeps native crash capture under the explicit Android and iOS SDK configuration while still allowing Flutter to capture Dart events.
Rust initializes Sentry during WalletCore.init(). The panic integration is
installed before the wallet panic policy so the Rust SDK can capture and flush a
panic before the process exits.
Configuration
The same build inputs feed Flutter, native, and Rust:
Variable |
Purpose |
|---|---|
|
Enables Sentry and selects the Sentry project endpoint. |
|
Labels events with the build/runtime environment. |
|
Aligns Flutter, native, Rust, and uploaded debug files to one release. |
|
Enables device logs and Sentry Logs in profile/release builds. |
|
Enables release debug-symbol upload in CI/Fastlane. |
|
Sentry organization used by debug-symbol upload. |
|
Sentry project used by debug-symbol upload. |
|
Sentry base URL used by debug-symbol upload. |
Fastlane computes SENTRY_RELEASE from the app identifier, version, and build
number when no release is supplied explicitly. The computed release is passed to
Flutter as a Dart define, into Android BuildConfig, into iOS build settings,
and into the Rust compile environment. Rust prefers SENTRY_RELEASE and only
falls back to its crate release name when the app release is absent.
CI loads Sentry upload credentials from the environment-specific Sentry secret.
If SENTRY_AUTH_TOKEN is present, the release build must upload debug symbols
before publishing.
ALLOW_RELEASE_LOGS is a separate logging policy switch. Debug builds allow
logs by default. Profile and release builds allow logs only when
ALLOW_RELEASE_LOGS=true. CI defaults this flag to false; ont and demo
release builds opt in explicitly, while production builds keep it false.
Do not infer log allowance from SENTRY_ENVIRONMENT.
Event Sources
Fatal Dart failures are captured through one fatal path. The app installs a
PlatformDispatcher.onError integration inside Sentry, captures one fatal
Sentry event with the original Dart stacktrace, gives the transport a short time
to drain, closes Sentry, and exits the process. The same fatal handler is
installed without Sentry when no DSN is configured, so fatal Dart behavior is
consistent.
Explicit Dart captures remain explicit events. Repository and bridge code capture handled exceptions when the event is useful for diagnostics.
Android native Sentry captures native crashes, ANRs, NDK/tombstone artifacts,
and raw tombstone data where supported by the SDK and platform. Android 11 and
newer report ANRs through ApplicationExitInfo; a stall that recovers without
terminating the process is not reported as an ANR event.
iOS native Sentry captures native crashes, app hangs, watchdog terminations, and the app dSYM-based symbolication data for the final app binary.
Rust Sentry captures:
Rust panics through the Rust panic integration.
Categorized Rust errors emitted through
#[sentry_capture_error].A Rust breadcrumb for non-expected categorized errors.
Rust expected errors are dropped. Rust personal-data and uncategorized events are sent only after sensitive message values are removed. Rust unexpected events are sent scrubbed. Rust critical events keep their exception messages, but still use the shared request, user, and breadcrumb scrubbing rules.
Device Logs And Sentry Logs
Device logs and Sentry Logs are diagnostics for debug and explicitly opted-in non-production release builds. They are not part of production crash-reporting telemetry. We do not rely on log-message scrubbing for production privacy; the production control is that logs stay disabled.
In this document, logs means diagnostic log records from app-owned logging
paths, such as Flutter/Fimber messages, Rust tracing records, and wallet-owned
native platform logs emitted through Android android.util.Log or iOS NSLog.
These are separate from Sentry crash/error events.
The shared log gate is:
Debug builds: logs enabled.
Profile/release builds with
ALLOW_RELEASE_LOGS=true: logs enabled.Profile/release builds with
ALLOW_RELEASE_LOGS=false: logs disabled.
Production builds must use the default ALLOW_RELEASE_LOGS=false. This means
Flutter Fimber logs, Rust tracing logs, Rust Sentry Logs, and wallet-owned
native platform-support logs are not emitted there. These records are also not
forwarded to Sentry Logs in production.
When logs are enabled:
Flutter plants
DebugTreeand, if Sentry is enabled,SentryLogTree.Flutter
SentryLogTreeforwards Fimber logs to Sentry Logs.Rust tracing uses a
DEBUGmax level and forwardsdebug,info,warn, anderrortracing records to Sentry Logs when Sentry is enabled.Rust ignores
tracerecords for Sentry Logs.Android and iOS Rust tracing records are written to the platform log writers.
Wallet-owned
platform_supportnative code uses the same release-log gate.
When logs are disabled:
Flutter does not plant
DebugTreeorSentryLogTree.Flutter
beforeSendLogdrops Sentry Logs defensively.Rust tracing uses
LevelFilter::OFF.Rust Sentry
enable_logsis disabled.Wallet-owned platform-support logs are suppressed.
The platform-support gate applies only to wallet-owned native logging paths. It does not make a claim about third-party native libraries that write directly to Android Logcat or iOS system logging. Those logs are outside this wrapper and are not forwarded to Sentry Logs by our logging integration. Stripping such third-party logs from Android release builds would require a separate minification/ProGuard change and is not part of the Sentry logging design.
Annotated Rust Errors
Rust non-panic diagnostics are sent through functions and implementations
annotated with #[sentry_capture_error]. The annotation captures returned
errors from the annotated scope and sends them through the Rust
ErrorCategory policy. The error type must derive or implement
ErrorCategory, and every variant or field must resolve to one of the Sentry
categories.
Rust error categories map to Sentry behavior as follows:
Category |
Sentry behavior |
|---|---|
|
Drop the event. |
|
Send the event with exception messages intact. |
|
Send the event with sensitive exception messages removed. |
|
Log locally and send the event with sensitive exception messages removed. |
|
Resolve the category from the wrapped or nested error. |
The annotation is transitive across called functions: an unhandled categorized
error returned through an annotated function is classified at that boundary. For
non-expected categories, Rust also emits a curated wallet.native breadcrumb
with the message code rust.error.<category>.
Adding or changing a #[sentry_capture_error] boundary is a privacy-sensitive
change. The matching error types need category annotations before the boundary
is useful, and any variant whose display text can contain domain data, URLs,
tokens, identifiers, or user-controlled text belongs in #[category(pd)] or a
deferred wrapper that resolves to pd.
Native crash artifacts belong only to native failures. Dart exceptions and Rust categorized errors do not need separate minidump or coredump artifacts.
Privacy And PII Handling
Production Sentry telemetry must not become a broad log sink. Crash/error events
are filtered before sending, breadcrumbs are curated at both insertion time and
send time, and device/Sentry Logs are disabled in production by keeping
ALLOW_RELEASE_LOGS=false.
Shared privacy rules:
sendDefaultPiiis disabled in Flutter, Android, iOS, and Rust.SDK-side user IP address and geo fields are removed before sending.
Request and transaction data are removed from Rust events.
Breadcrumb payload data is removed; only category and message code remain.
Non-wallet breadcrumbs are dropped.
Flutter exception values are removed before sending. Stacktraces remain.
Rust exception values are removed for personal-data, unexpected, and uncategorized error events.
Rust expected errors are not sent.
Production device logs and Sentry Logs are not emitted.
The stable diagnostic signal is the event type, stacktrace, release, environment, platform context, curated breadcrumb codes, and Rust error category tag. Free-form user data and domain payloads are not part of crash reporting. Sentry-side IP/geolocation enrichment is disabled in account/project settings where possible, but the SDK cannot fully control metadata derived by Sentry from transport-level IP information. Treat any residual IP/geolocation as service-side metadata, not app-supplied event payload.
The current ont and demo Sentry projects are accepted as internal
non-production projects. Their service-level account setup, including data
storage region, must not be treated as production policy.
Releases And Debug Symbols
Release names must align across Flutter, native Android/iOS, Rust, and uploaded debug files. Symbol uploads are a release requirement whenever Sentry is enabled for a publishable build.
Fastlane runs the Sentry Dart plugin for Flutter and app-native debug symbols.
The plugin runs only when SENTRY_AUTH_TOKEN is present and requires
SENTRY_ORG, SENTRY_PROJECT, and SENTRY_URL.
Fastlane computes SENTRY_RELEASE once per app build from the app identifier,
version, and build number unless it is supplied explicitly. The same release
value is passed to Flutter, Android BuildConfig, iOS build settings, Rust
compile-time environment, and all Sentry upload commands.
Android additionally uploads Rust debug files explicitly from the unstripped Rust libraries copied into:
wallet_app/android/app/src/main/jniLibs/**/libwallet_core.so
For Android profile/release builds, Cargo keeps Rust line-table debug
information in those CI-side libraries with
CARGO_PROFILE_RELEASE_DEBUG=line-tables-only and
CARGO_PROFILE_RELEASE_STRIP=none. Fastlane uploads those files with
sentry-cli debug-files upload --include-sources --wait. The Android packaging
pipeline then strips the native libraries in the distributed app artifact. This
provides Rust source-line symbolication in Sentry without shipping debug symbols
in the installed app.
iOS Rust code is statically linked into the final app binary. Rust symbolication therefore uses the app dSYMs from:
wallet_app/build/ios/archive/Runner.xcarchive
For iOS profile/release builds, the Xcode build also sets
CARGO_PROFILE_RELEASE_DEBUG=line-tables-only and
CARGO_PROFILE_RELEASE_STRIP=none before compiling Rust. The iOS archive is
retained as a CI artifact and uploaded through the Sentry Dart plugin path, so
the dSYMs carry the final app and statically linked Rust symbolication data.