pub mod config;
pub mod consent;
pub mod scrub;
use std::path::Path;
use std::sync::{Arc, OnceLock};
use std::time::Duration;
use sentry::{ClientInitGuard, ClientOptions};
use crate::privacy::PrivacyFilter;
use self::consent::{resolve, ConsentState};
const BAKED_DSN: &str = env!("OPENLATCH_SENTRY_DSN");
const BAKED_RELEASE_SHA: &str = env!("OPENLATCH_RELEASE_SHA");
static SCRUB_FILTER: OnceLock<PrivacyFilter> = OnceLock::new();
fn scrub_filter() -> &'static PrivacyFilter {
SCRUB_FILTER.get_or_init(|| PrivacyFilter::new(&[]))
}
fn resolve_dsn() -> Option<String> {
std::env::var("OPENLATCH_SENTRY_DSN")
.ok()
.filter(|s| !s.is_empty())
.or_else(|| {
if BAKED_DSN.is_empty() {
None
} else {
Some(BAKED_DSN.to_string())
}
})
}
pub fn init(openlatch_dir: &Path) -> Option<ClientInitGuard> {
let dsn = resolve_dsn();
let config_path = openlatch_dir.join("config.toml");
let decision = resolve(&config_path, dsn.is_some());
if decision.state != ConsentState::Enabled {
return None;
}
let parsed_dsn = dsn.as_deref().and_then(|s| s.parse().ok());
parsed_dsn.as_ref()?;
let release = format!("openlatch-client@{BAKED_RELEASE_SHA}");
let environment = if cfg!(debug_assertions) {
"development"
} else {
"production"
};
let options = ClientOptions {
dsn: parsed_dsn,
release: Some(release.into()),
environment: Some(environment.into()),
send_default_pii: false,
traces_sample_rate: 0.0,
before_send: Some(Arc::new(|event| scrub::scrub_event(event, scrub_filter()))),
attach_stacktrace: true,
..Default::default()
};
Some(sentry::init(options))
}
pub fn enrich_cli_scope(command: &str) {
sentry::configure_scope(|scope| {
scope.set_tag("process_type", "cli");
scope.set_tag("command", command);
});
}
pub fn enrich_daemon_scope(port: u16, pid: u32) {
sentry::configure_scope(|scope| {
scope.set_tag("process_type", "daemon");
scope.set_extra("port", u64::from(port).into());
scope.set_extra("pid", u64::from(pid).into());
});
}
pub fn flush(timeout: Duration) {
if let Some(client) = sentry::Hub::current().client() {
let _ = client.flush(Some(timeout));
}
}
pub const fn build_includes_crash_report() -> bool {
true
}
pub fn current_state(openlatch_dir: &Path) -> consent::Resolved {
let dsn = resolve_dsn();
resolve(&openlatch_dir.join("config.toml"), dsn.is_some())
}