#[cfg(not(target_os = "android"))]
use std::path::Path;
#[cfg(not(target_os = "android"))]
use tracing_subscriber::{
EnvFilter, Layer, fmt::format::FmtSpan, layer::SubscriberExt, util::SubscriberInitExt,
};
#[cfg(not(target_os = "android"))]
struct LogGuards {
_file: tracing_appender::non_blocking::WorkerGuard,
_chrome: Option<crate::chrome_trace::FlushGuard>,
_ui_profile: Option<crate::ui_profile::UiProfileGuard>,
}
#[cfg(not(target_os = "android"))]
static GUARDS: std::sync::Mutex<Option<LogGuards>> = std::sync::Mutex::new(None);
#[cfg(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(not(target_os = "android"))]
fn default_filter() -> EnvFilter {
let base = if debug_mode() { "debug" } else { "info" };
EnvFilter::new(format!("{base},{QUIET_DEPS}"))
}
#[cfg(not(target_os = "android"))]
fn console_filter() -> EnvFilter {
user_directives()
.and_then(|s| EnvFilter::try_new(&s).ok())
.unwrap_or_else(default_filter)
}
#[cfg(not(target_os = "android"))]
fn file_filter() -> EnvFilter {
user_directives()
.and_then(|s| EnvFilter::try_new(&s).ok())
.unwrap_or_else(default_filter)
}
#[cfg(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(not(target_os = "android"))]
fn ui_profile_enabled() -> bool {
std::env::var("KOPUZ_UI_PROFILE")
.map(|v| !v.is_empty() && v != "0" && v != "false")
.unwrap_or(false)
}
#[cfg(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(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 mut chrome_err: Option<String> = None;
let (chrome_layer, chrome_guard) = if config_tracing_enabled {
match crate::chrome_trace::ChromeTraceLayer::new(&trace_path) {
Ok((layer, guard)) => (Some(layer.with_filter(file_filter())), Some(guard)),
Err(err) => {
chrome_err = Some(err.to_string());
(None, None)
}
}
} else {
(None, None)
};
let ui_trace_path = log_dir.join("kopuz-ui-profile.json");
let mut ui_err: Option<String> = None;
let (ui_layer, ui_guard) = if ui_profile_enabled() {
match crate::ui_profile::UiProfileLayer::new(&ui_trace_path) {
Ok((layer, guard)) => (
Some(
layer.with_filter(EnvFilter::new("off,dioxus_core=trace,dioxus_signals=trace")),
),
Some(guard),
),
Err(err) => {
ui_err = Some(err.to_string());
(None, None)
}
}
} else {
(None, None)
};
tracing_subscriber::registry()
.with(file_layer)
.with(console_layer)
.with(chrome_layer)
.with(ui_layer)
.init();
if chrome_guard.is_some() {
tracing::info!(trace = %trace_path.display(), "chrome span trace enabled");
}
if let Some(err) = chrome_err {
tracing::warn!(trace = %trace_path.display(), %err, "failed to open chrome trace file, tracing disabled this session");
}
if ui_guard.is_some() {
tracing::info!(trace = %ui_trace_path.display(), "UI render profiler enabled (KOPUZ_UI_PROFILE)");
}
if let Some(err) = ui_err {
tracing::warn!(trace = %ui_trace_path.display(), %err, "failed to open UI profile trace file, profiler disabled this session");
}
let trace_enabled = chrome_guard.is_some();
*GUARDS.lock().unwrap_or_else(|e| e.into_inner()) = Some(LogGuards {
_file: file_guard,
_chrome: chrome_guard,
_ui_profile: ui_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(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(not(target_os = "android"))]
pub fn shutdown() {
if let Ok(mut g) = GUARDS.lock() {
g.take();
}
}