7.9 KiB
name, description, metadata
| name | description | metadata | ||||||
|---|---|---|---|---|---|---|---|---|
| sentry-crash-reporting-4914 | Sentry crash reporting across JS/Rust/Android/iOS (PR |
|
Sentry crash/error reporting added in PR #4914 (feat(sentry): add crash reporting for Android, iOS, desktop, and web). Four layers, one build-time SENTRY_DSN (empty => every layer no-ops):
- JS + Rust panics:
tauri-plugin-sentry0.5 +sentry0.42 registered inlib.rs::run()(guard held to end ofrun()); rustls transport (NOT native-tls, so it cross-compiles for mobile); browser SDK auto-injected. Minidump handler is desktop-only:#[cfg(not(any(target_os = "ios", target_os = "android")))]. - Android native:
io.sentry:sentry-android:8.47.0ingen/android/app/build.gradle.kts+ manifest auto-init<meta-data>(io.sentry.dsnfrom${sentryDsn}placeholder,io.sentry.environment). Crashes+errors only (traces=0, no PII, no replay). - iOS native:
sentry-cocoavia SPM inproject.yml.
Config: sentry_config.rs holds pure helpers (sentry_dsn, environment_for_version, app_version, release_name/sentry_release, corrected_os_name, android_version_from_uname, is_ignored_browser_error, parse_webview_info/set_webview_info/webview_info). Scope = crashes+errors only. Symbolication (source-map/ProGuard/dSYM upload) is STILL deferred — several 2026-07 crashes (READEST-2 render loop, READEST-9) could only be triaged to a function name, not a source line, for lack of source maps; upload them.
The Rust-client before_send (in lib.rs) now does three things, in order, for every event (Rust panics + browser events forwarded by tauri-plugin-sentry): (1) drop known-benign browser noise via is_ignored_browser_error (only the View-Transition-skipped-while-hidden message; a transition timeout is deliberately kept — real perf signal); (2) rewrite the Android OS name/version (see below); (3) tag webview.engine/webview.version. The webview tags come from a set_webview_info Tauri command the app calls once in NativeAppService.init() with navigator.userAgent; parse_webview_info extracts engine+major-version (Chromium Chrome/140 checked before WebKit Version/17, because Android WebViews also carry a legacy Version/4.0) into a global OnceLock read in before_send. Added because forwarded browser events carry os/rust/device context but NO browser context, so crashes couldn't be correlated with WebView version. (feat(sentry): tag events with the WebView engine and version, merged 2026-07.)
2026-07 production crash-fix batch (all merged). The recurring root cause was best-effort background work throwing UNHANDLED promise rejections (callers fire-and-forget, so a throw hits the global handler): READEST-1 concurrent-use (turso, see turso-concurrent-use-forbidden); READEST-5 cloud deleteFile threw (log+swallow); READEST-6 statistics DB writes on teardown (runBestEffort wrapper in ReadingStatsTracker; also covers READEST-4/8 network fails); READEST-A library save to a custom shared-storage folder failed EACCES because the save path never called the existing requestStoragePermission() (AppService.saveLibraryBooks now requests-once-per-session + retries). READEST-2 = zustand updateTransferProgress allocating new state on unchanged values → React update loop (equality guard). READEST-9 = useAppRouter wraps EVERY nav in a View Transition; opening a book is a heavy render that overruns the ~4s DOM-update budget → TimeoutError (fix: book-open navs use the plain useRouter, matching 8/10 into-reader paths; version-gating does NOT help — the 4s budget is version-independent).
Release + environment key off package.json, NOT the crate version. Originally release: sentry::release_name!() = CARGO_PKG_NAME@CARGO_PKG_VERSION = Readest@0.2.2 (stale crate version, never bumped) and environment read CARGO_PKG_VERSION (so it was ALWAYS "production" — nightly detection was dead). Fix: build.rs::propagate_app_version() reads the top-level "version" from ../package.json (line-based parse via read_json_string_field, no serde) and bakes cargo:rustc-env=READEST_APP_VERSION; app_version() reads it via option_env! (falls back to CARGO_PKG_VERSION), same bake mechanism as SENTRY_DSN. sentry_release() -> Readest@<pkg-version> (e.g. Readest@0.11.17), sentry_environment() now derives from app_version() so nightly (-YYYYMMDDHH) correctly reports environment=nightly. Android/iOS native SDK releases already came from versionName/bundle version (tauri derives those from package.json), so only the Rust client (which also handles JS/browser events via tauri-plugin-sentry) needed fixing.
OS name "Linux" -> "Android". On Android, sentry-contexts::os_context() builds the OS context from uname() (not-macos/not-windows branch): name = info.sysname = "Linux", version = info.release = kernel string like 6.1.162-android14-11-.... Fixed with a Rust-client before_send in lib.rs that, keyed on std::env::consts::OS == "android", rewrites Context::Os.name -> "Android" and pulls the Android version ("14") out of the androidNN token in os.version via android_version_from_uname. before_send runs AFTER ContextIntegration::process_event (which inserts the os context only if Entry::Vacant), so the "Linux" context is present to rewrite; applies to browser events too since tauri-plugin-sentry forwards them through the same Rust client. iOS-via-Rust would show "Darwin" but that path is minor (native sentry-cocoa reports iOS correctly) — not remapped.
Gotcha 1 — iOS generated files are gitignored + never tracked. gen/apple/Sources/Readest/main.mm and gen/apple/Readest_iOS/Info.plist are gitignored (.gitignore src-tauri/gen) and regenerated by tauri ios init — editing them does NOT persist. Only project.yml and force-added custom files (like ShareExtension/*, ReadestWidget/*) survive. So native iOS init lives in a force-tracked gen/apple/SentrySupport/SentryBootstrap.m (+[ReadestSentryBootstrap load], runs before main) wired via project.yml (SPM package + - path: SentrySupport source), reading the DSN from an iOS-gated Rust C-ABI readest_sentry_dsn() FFI (in sentry_config.rs) — no generated-file edits. See ios-widget-appgroup-stripped-appstore.
Gotcha 2 — sentry-android needs a lifecycle exclude. sentry-android-core (7.x AND 8.x) depends on androidx.lifecycle:lifecycle-common-java8, discontinued at 2.9.0+. The app's lifecycle-process:2.10.0 pin version-aligns it to a nonexistent 2.10.0 -> Gradle Could not resolve at :app:mergeUniversalReleaseNativeLibs. Fix = exclude(group="androidx.lifecycle", module="lifecycle-common-java8") (its Java8 APIs now live in lifecycle-common). Same class as dependabot-pnpm-overrides.
Gotcha 3 — no dotenv in the tauri build; wire SENTRY_DSN yourself. tauri-cli (2.10.1) has NO dotenv dependency, so .env.local / .env.* are NOT auto-loaded into the cargo/gradle build (Next.js .env.local only reaches Next.js). build.rs resolves SENTRY_DSN with precedence env -> .env.local -> .env and bakes it via cargo:rustc-env (covers Rust + iOS FFI; rerun-if-changed avoids stale bake); build.gradle.kts does the same for the Android manifest placeholder. CI: release.yml/nightly.yml append SENTRY_DSN=${{ secrets.SENTRY_DSN }} to the .env.local they already build next to the PostHog/Supabase secrets (cp .env.local apps/readest-app/.env.local). iOS App Store release is a local script -> uses the maintainer's local .env.local.
Env tag: sentry_environment() now reads app_version() (baked from package.json), so nightly stamps (-YYYYMMDDHH) resolve to environment=nightly; store-distributed mobile stays production. (Previously it read CARGO_PKG_VERSION — crate 0.2.2, never nightly-stamped — so it was always production.)