The Rust crate, with a disk-backed queue that survives being offline or killed.
Every call hands an event to a background worker and returns: nothing panics into your app, nothing blocks the UI thread, and if firstrun is unreachable your app is unaffected.
[dependencies]
firstrun-sdk = { path = "../../sdk/tauri" }Not published to crates.io yet, so it is a path or a git dependency.
use firstrun_sdk::{Analytics, Config};
let analytics = Analytics::start(Config {
source_key: "fr_xxxxxxxxxxxxxxxx".into(),
host: "https://app.firstrun.app".into(),
service_name: "YourApp".into(),
service_version: Some(env!("CARGO_PKG_VERSION").into()),
..Config::default()
});
tauri::Builder::default()
.manage(analytics)
.run(tauri::generate_context!())?;start returns an Analytics rather than a Result, so a bad key or an unwritable disk gives you a client that accepts every call and sends nothing instead of a failure on your startup path. app_install on the first run ever and app_launch on every run are queued for you. service_name names the directory holding the anonymous id and the queue, so set it and both survive a key rotation.
analytics.event("exported_project", attrs! { "format" => "pdf", "pages" => 12 });
analytics.error(&e, attrs! {});
analytics.log(Entry {
name: "render_stalled".into(),
severity: Severity::Warn,
attributes: attrs! { "frames_dropped" => 41 },
..Entry::now()
});
analytics.identify(Some("acct_8812")); // your own id, when they sign in
analytics.flush(Duration::from_secs(2)); // on exit. Optional: dropping it flushes tooevent writes at INFO and error at ERROR, both filling in the conventional attributes; log takes any name, any severity and any attributes. Values go on the wire as JSON, so a number stays a number, and an event is stamped with the time it happened rather than the time it is sent.
stats(). That is what makes a desktop number trustworthy: laptops are offline, processes get killed, and a launch on Friday that uploads on Monday is still a Friday launch. The anonymous id lives in per-user local application data (%LOCALAPPDATA% on Windows, not the roaming profile) because it names one installation.