Crash reporting
day-piece-break saves crash reports on the device. On the next launch, the app can show the report
and offer to send it by HTTP, GitHub issue, or email. Reports are sent only through an explicit user action. The steps below show how to capture
a crash, display the report after restarting, and let the user decide whether to send it.
Works on: Rust panics are captured on every native target. Native faults (SIGSEGV, SIGBUS,
SIGILL, SIGFPE, SIGABRT, SIGTRAP) are caught on the Unix targets (macOS, iOS, Linux, Android, and
HarmonyOS), and Android also records uncaught Java exceptions. Windows records panics but not native
faults yet, and on the web init is a no-op. The full matrix is in the break
reference.
1. Initialize reporting before launch
Add the crate and call init as early as possible: before day::launch, so a crash during
startup is still recorded:
[dependencies]
day-piece-break = { git = "https://github.com/daybrite/day-piece-break" }
The showcase wraps it in a helper called from every entry point:
/// Arm crash reporting. Idempotent (day-piece-break's `init` is single-shot); safe to call from
/// every entry point.
pub fn install_crash_reporting() {
let _ = day_piece_break::Config::new()
// "Send report" opens a prefilled email to the developer (no server needed).
.reporter(day_piece_break::EmailReporter::new("crashdemo@daybrite.dev"))
.init();
}
Crash capture is process-global, so init is single-shot: a second call returns
InitError::AlreadyInitialized. That’s why the helper ignores the result: calling it from
both main and a mobile entry point is safe.
The configuration also controls which reports are kept and how they are redacted: .max_reports(n) caps the
report rotation (5), .keep_contained(false) drops reports for panics day-core contained
(kept by default), .signals(false) turns off the native signal handlers (on by default), and
.redact(|msg| …) scrubs secrets from panic messages before they are persisted, displayed, or
uploaded. App identity (id, version, build) is baked in by day build from Day.toml;
.app_id(…), .app_version(…), and .app_build(…) override it.
2. What gets captured
Reports come from a Rust panic (the panic hook), a native fault or abort (the signal handlers), and, on Android, an uncaught Java exception. A panic that day-core contains at its trampoline boundaries (the app survives) is recorded too, as a distinct non-fatal report, so you can investigate failures that did not terminate the app.
A report is versioned JSON: app id, version, and build; the day version and backend; OS,
device model, and locale; the session id and uptime; the panic message and source location, or
the signal’s number and addresses; and a backtrace. The reference lists every field.
Panic messages may contain user data supplied by your app; use .redact(…) to remove sensitive
values before storage. The signal handlers chain to the previous disposition, so the platform’s crash
reporter (Android tombstones, HarmonyOS faultlogs) still runs alongside.
3. Show the report on the next launch
init reconciles the previous session before your UI mounts, so by the time it builds you can
ask what happened:
match day_piece_break::last_session() {
day_piece_break::SessionEnd::Crashed { .. } => show_crash_prompt(), // your UI, or consent_banner()
day_piece_break::SessionEnd::Unknown => {} // an OS kill — not a crash; usually ignore
day_piece_break::SessionEnd::Clean => {}
}
Use day_piece_break::consent_banner() from the ui feature (on by
default): a piece that appears while reports are pending, shows the full report text, and
offers send and discard. To build your own (the showcase’s Crash Reporting page does),
compose the queries: pending() is a reactive Signal<Vec<ReportMeta>>, newest first;
report_text(&meta) is the full text the transport sends;
reporter_description() is the transport’s one-line disclosure; send(&meta, |result| …)
uploads; discard(&meta) deletes. The showcase keeps its viewer current with one effect:
let report = Signal::new(String::new());
let pending = day_piece_break::pending();
Effect::new(move || {
pending.get(); // track
report.set(day_piece_break::latest_report_text().unwrap_or_default());
});
4. Pick a reporter
Three transports ship with the crate:
RestReporter::new(url)POSTs the report JSON viaday-part-http, off the UI thread;.named("our crash server")sets the name shown to the user. It’s also the shape a GitHub-issue proxy takes: a small server accepts the JSON and opens the issue with your repo token held server-side, never on the device.GithubIssueReporter::new(owner, repo)opens a prefilled new-issue page in the browser; the user reviews and submits it themselves, and GitHub is the only server involved.EmailReporter::new(to)opens a prefilledmailto:compose (.subject_prefix(…)tags the subject); the user sends the mail.
Or implement the trait yourself:
pub trait Reporter: Send + Sync {
fn name(&self) -> &str; // shown on the consent surface
fn describe(&self) -> String; // one-line disclosure: where the report goes
fn send(&self, report: &Report, done: Box<dyn FnOnce(Result<(), SendError>) + Send>);
}
The browser and email transports finish with SendError::HandedOff: they handed the report
to the platform and can’t confirm delivery. It is reported so your UI can say so, and it is not
a failure.
Pitfalls
- Arm first. The hook can’t record a crash that happens before
initruns, andinitalso reconciles the previous session, so call it at the top of the app entry, beforeday::launch. - Release backtraces carry symbols, not lines. The release profile ships no debug info by
default. For
file:linein release reports, add[profile.release] debug = "line-tables-only"in your own workspace; day-piece-break doesn’t change the global profile. For native faults,signal.pc - signal.slideis the module-relative address to symbolize offline. Unknownis not a crash. A leftover session with no crash artifact (an OS kill, power loss) reconciles asSessionEnd::Unknown, neverCrashed. Don’t show crash UI for it.- Not every crash class is caught everywhere. Windows native faults, the iOS
Objective-C exception handler, and HarmonyOS
errorManagerare deferred to a later version; on iOS an uncaught ObjC exception ends inabort(), which the SIGABRT handler does record.
Reference
break — the full design: the report schema, signal-handler discipline, the session sentinel, app identity and symbolication, transports, and how it’s tested.