use std::sync::Mutex;
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::EnvFilter;
static FILE_GUARD: Mutex<Option<WorkerGuard>> = Mutex::new(None);
pub fn init_tracing(log_level: &str, log_format: &str) {
let use_ansi = crate::terminal::should_use_ansi();
let to_file = crate::config::get_setting("log.to_file")
.ok()
.flatten()
.map(|v| matches!(v.to_ascii_lowercase().as_str(), "1" | "true" | "yes" | "on"))
.unwrap_or(false);
let file_writer = if to_file {
let dir = crate::paths::cache_dir()
.unwrap_or_else(|_| std::env::temp_dir().join("sqlite-graphrag"));
let log_dir = dir.join("logs");
let _ = std::fs::create_dir_all(&log_dir);
let rotation = crate::config::get_setting("log.rotation")
.ok()
.flatten()
.unwrap_or_else(|| "daily".into());
let appender = match rotation.as_str() {
"hourly" => tracing_appender::rolling::hourly(&log_dir, "sqlite-graphrag.log"),
"never" => tracing_appender::rolling::never(&log_dir, "sqlite-graphrag.log"),
_ => tracing_appender::rolling::daily(&log_dir, "sqlite-graphrag.log"),
};
let (non_blocking, guard) = tracing_appender::non_blocking(appender);
if let Ok(mut slot) = FILE_GUARD.lock() {
*slot = Some(guard);
}
Some(non_blocking)
} else {
None
};
if let Some(nb) = file_writer {
use tracing_subscriber::fmt::writer::MakeWriterExt;
let writer = std::io::stderr.and(nb);
if log_format == "json" {
tracing_subscriber::fmt()
.json()
.with_ansi(false)
.with_thread_ids(true)
.with_thread_names(true)
.with_env_filter(EnvFilter::new(log_level))
.with_writer(writer)
.init();
} else {
tracing_subscriber::fmt()
.with_ansi(use_ansi)
.with_env_filter(EnvFilter::new(log_level))
.with_writer(writer)
.init();
}
} else if log_format == "json" {
tracing_subscriber::fmt()
.json()
.with_ansi(false)
.with_thread_ids(true)
.with_thread_names(true)
.with_env_filter(EnvFilter::new(log_level))
.with_writer(std::io::stderr)
.init();
} else {
tracing_subscriber::fmt()
.with_ansi(use_ansi)
.with_env_filter(EnvFilter::new(log_level))
.with_writer(std::io::stderr)
.init();
}
tracing::debug!(
target: "logging",
filter = %log_level,
format = %log_format,
ansi = use_ansi,
to_file,
"tracing subscriber initialized (local only; no remote telemetry)"
);
std::panic::set_hook(Box::new(|info| {
let payload = info
.payload()
.downcast_ref::<&str>()
.copied()
.or_else(|| info.payload().downcast_ref::<String>().map(|s| s.as_str()))
.unwrap_or("<non-string panic>");
let location = info
.location()
.map(|l| format!("{}:{}:{}", l.file(), l.line(), l.column()));
tracing::error!(
target: "panic",
message = %payload,
location = location.as_deref().unwrap_or("unknown"),
"thread panicked"
);
}));
}
pub fn flush_tracing() {
if let Ok(mut slot) = FILE_GUARD.lock() {
*slot = None;
}
}