#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
use std::path::Path;
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
use tracing_subscriber::{
EnvFilter, Layer, fmt::format::FmtSpan, layer::SubscriberExt, util::SubscriberInitExt,
};
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
struct LogGuards {
_file: tracing_appender::non_blocking::WorkerGuard,
_chrome: Option<crate::chrome_trace::FlushGuard>,
}
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
static GUARDS: std::sync::Mutex<Option<LogGuards>> = std::sync::Mutex::new(None);
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
const QUIET_DEPS: &str = "symphonia=warn,wgpu_core=warn,wgpu_hal=warn,naga=warn,h2=warn,hyper=warn,reqwest=info,cpal=info,sctk=warn,calloop=warn,dioxus_signals=warn,dioxus_core=warn,dioxus_document=warn,zbus=warn,zbus_names=warn,tracing=warn";
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
fn default_filter() -> EnvFilter {
let base = if debug_mode() { "debug" } else { "info" };
EnvFilter::new(format!("{base},{QUIET_DEPS}"))
}
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
fn console_filter() -> EnvFilter {
user_directives()
.and_then(|s| EnvFilter::try_new(&s).ok())
.unwrap_or_else(default_filter)
}
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
fn file_filter() -> EnvFilter {
user_directives()
.and_then(|s| EnvFilter::try_new(&s).ok())
.unwrap_or_else(default_filter)
}
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
fn debug_mode() -> bool {
std::env::var("KOPUZ_DEBUG")
.map(|v| !v.is_empty() && v != "0" && v != "false")
.unwrap_or(false)
}
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
fn user_directives() -> Option<String> {
std::env::var("KOPUZ_LOG")
.or_else(|_| std::env::var("RUST_LOG"))
.ok()
.filter(|s| !s.is_empty())
}
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
pub fn init(log_dir: &Path, config_tracing_enabled: bool) {
utils::logs::set_log_dir(log_dir.to_path_buf());
utils::logs::rotate_session_log(log_dir);
let file_appender = tracing_appender::rolling::never(log_dir, "latest.log");
let (non_blocking, file_guard) = tracing_appender::non_blocking(file_appender);
let file_layer = tracing_subscriber::fmt::layer()
.with_ansi(false)
.with_span_events(FmtSpan::CLOSE)
.with_writer(non_blocking)
.with_filter(file_filter());
let console_layer = tracing_subscriber::fmt::layer()
.with_writer(std::io::stderr)
.with_filter(console_filter());
let trace_path = log_dir.join("kopuz-trace.json");
let chrome_guard = if config_tracing_enabled {
match crate::chrome_trace::ChromeTraceLayer::new(&trace_path) {
Ok((chrome_layer, guard)) => {
tracing_subscriber::registry()
.with(file_layer)
.with(console_layer)
.with(chrome_layer.with_filter(file_filter()))
.init();
tracing::info!(trace = %trace_path.display(), "chrome span trace enabled");
Some(guard)
}
Err(err) => {
tracing_subscriber::registry()
.with(file_layer)
.with(console_layer)
.init();
tracing::warn!(trace = %trace_path.display(), %err, "failed to open chrome trace file — tracing disabled this session");
None
}
}
} else {
tracing_subscriber::registry()
.with(file_layer)
.with(console_layer)
.init();
None
};
let trace_enabled = chrome_guard.is_some();
*GUARDS.lock().unwrap_or_else(|e| e.into_inner()) = Some(LogGuards {
_file: file_guard,
_chrome: chrome_guard,
});
let _ = ctrlc::set_handler(|| {
shutdown();
std::process::exit(130);
});
if trace_enabled {
std::thread::spawn(|| {
loop {
std::thread::sleep(std::time::Duration::from_millis(500));
match GUARDS.lock() {
Ok(g) => match g.as_ref().and_then(|guards| guards._chrome.as_ref()) {
Some(chrome) => chrome.flush(),
None => break,
},
Err(_) => break,
}
}
});
}
install_panic_hook();
tracing::info!(log_dir = %log_dir.display(), "logging initialized");
}
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
fn install_panic_hook() {
use std::sync::atomic::{AtomicBool, Ordering};
static CRASH_WRITTEN: AtomicBool = AtomicBool::new(false);
let default = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
if !CRASH_WRITTEN.swap(true, Ordering::SeqCst) {
let message = info
.payload()
.downcast_ref::<&str>()
.map(|s| s.to_string())
.or_else(|| info.payload().downcast_ref::<String>().cloned())
.unwrap_or_else(|| "<non-string panic payload>".to_string());
let location = info
.location()
.map(|l| format!("{}:{}:{}", l.file(), l.line(), l.column()))
.unwrap_or_else(|| "unknown".to_string());
let backtrace = std::backtrace::Backtrace::force_capture().to_string();
if let Some(dir) = utils::logs::log_dir()
&& let Some(path) = utils::logs::write_crash_report(
&dir,
env!("CARGO_PKG_VERSION"),
&message,
&location,
&backtrace,
)
{
tracing::error!(crash_report = %path.display(), panic = %message, %location, "panic — crash report written");
}
}
default(info);
}));
}
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
pub fn shutdown() {
if let Ok(mut g) = GUARDS.lock() {
g.take();
}
}