Files
tolaria/src-tauri/src/telemetry.rs
Test 91854f8bae feat: integrate Sentry crash reporting + PostHog analytics
- JS: @sentry/browser + posthog-js with lazy init and path scrubbing
- Rust: sentry crate with beforeSend path scrubber
- useTelemetry hook reactively inits/tears down SDKs on settings change
- reinit_telemetry Tauri command lets frontend trigger Rust-side reinit
- Zero network requests without explicit user consent
- All file paths redacted from error payloads via regex scrubber

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 16:20:09 +01:00

95 lines
2.6 KiB
Rust

use crate::settings;
use regex::Regex;
use std::sync::Mutex;
/// Global Sentry guard — must live for the duration of the app.
static SENTRY_GUARD: Mutex<Option<sentry::ClientInitGuard>> = Mutex::new(None);
/// Scrub absolute file paths from a string to prevent vault content leakage.
fn scrub_paths(input: &str) -> String {
let re = Regex::new(r"(?:/[\w.-]+){2,}|[A-Z]:\\[\w\\.-]+").unwrap();
re.replace_all(input, "<redacted-path>").to_string()
}
/// Initialize Sentry if the user has opted in to crash reporting.
/// Returns `true` if Sentry was initialized, `false` if skipped.
pub fn init_sentry_from_settings() -> bool {
let settings = match settings::get_settings() {
Ok(s) => s,
Err(_) => return false,
};
if settings.crash_reporting_enabled != Some(true) {
return false;
}
let dsn = option_env!("SENTRY_DSN").unwrap_or_default();
if dsn.is_empty() {
return false;
}
let anonymous_id = settings.anonymous_id.unwrap_or_default();
let guard = sentry::init(sentry::ClientOptions {
dsn: dsn.parse().ok(),
release: Some(env!("CARGO_PKG_VERSION").into()),
send_default_pii: false,
before_send: Some(std::sync::Arc::new(|mut event| {
if let Some(ref mut msg) = event.message {
*msg = scrub_paths(msg);
}
Some(event)
})),
..Default::default()
});
sentry::configure_scope(|scope| {
scope.set_user(Some(sentry::User {
id: Some(anonymous_id),
..Default::default()
}));
});
*SENTRY_GUARD.lock().unwrap() = Some(guard);
true
}
/// Tear down and reinitialize Sentry (called when user changes settings).
pub fn reinit_sentry() {
// Drop old guard first
*SENTRY_GUARD.lock().unwrap() = None;
// Re-read settings and optionally re-init
init_sentry_from_settings();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scrub_paths_unix() {
assert_eq!(
scrub_paths("Error at /Users/luca/Laputa/note.md"),
"Error at <redacted-path>"
);
}
#[test]
fn test_scrub_paths_windows() {
assert_eq!(
scrub_paths("Error at C:\\Users\\test\\doc.md"),
"Error at <redacted-path>"
);
}
#[test]
fn test_scrub_paths_no_paths() {
assert_eq!(scrub_paths("Normal error message"), "Normal error message");
}
#[test]
fn test_init_sentry_returns_false_without_dsn() {
// Without SENTRY_DSN env var set at compile time, init should return false
assert!(!init_sentry_from_settings());
}
}