use anyhow::Result;
use file_rotate::{ContentLimit, FileRotate, compression::Compression, suffix::AppendCount};
use std::io::Write;
use std::path::Path;
use std::sync::{Arc, Mutex};
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt};
struct RotatingWriter {
file_rotate: Arc<Mutex<FileRotate<AppendCount>>>,
}
impl RotatingWriter {
fn new(log_dir: &Path) -> Result<Self> {
let log_path = log_dir.join("torc-server.log");
let file_rotate = FileRotate::new(
log_path,
AppendCount::new(5), ContentLimit::Bytes(10 * 1024 * 1024), Compression::None,
#[cfg(unix)]
None,
);
Ok(Self {
file_rotate: Arc::new(Mutex::new(file_rotate)),
})
}
}
impl Write for RotatingWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let mut file = self.file_rotate.lock().unwrap_or_else(|poisoned| {
eprintln!("WARNING: RotatingWriter mutex was poisoned, recovering");
poisoned.into_inner()
});
file.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
let mut file = self.file_rotate.lock().unwrap_or_else(|poisoned| {
eprintln!("WARNING: RotatingWriter mutex was poisoned, recovering");
poisoned.into_inner()
});
file.flush()
}
}
impl Clone for RotatingWriter {
fn clone(&self) -> Self {
Self {
file_rotate: Arc::clone(&self.file_rotate),
}
}
}
pub fn init_logging(
log_dir: Option<&Path>,
log_level: &str,
json_format: bool,
) -> Result<Option<WorkerGuard>> {
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
EnvFilter::new(log_level)
});
if let Some(dir) = log_dir {
std::fs::create_dir_all(dir)?;
let file_writer = RotatingWriter::new(dir)?;
let (non_blocking, guard) = tracing_appender::non_blocking(file_writer);
if json_format {
tracing_subscriber::registry()
.with(env_filter)
.with(
fmt::layer()
.with_writer(std::io::stderr)
.with_target(true)
.with_thread_ids(true)
.with_line_number(true),
)
.with(
fmt::layer()
.json()
.with_writer(non_blocking)
.with_target(true)
.with_thread_ids(true)
.with_line_number(true),
)
.init();
} else {
tracing_subscriber::registry()
.with(env_filter)
.with(
fmt::layer()
.with_writer(std::io::stderr)
.with_target(true)
.with_thread_ids(true)
.with_line_number(true),
)
.with(
fmt::layer()
.with_writer(non_blocking)
.with_ansi(false) .with_target(true)
.with_thread_ids(true)
.with_line_number(true),
)
.init();
}
tracing::info!(
log_dir = %dir.display(),
log_level = %log_level,
json_format = %json_format,
max_file_size = "10 MiB",
max_files = 5,
"Logging initialized with file output and size-based rotation"
);
Ok(Some(guard))
} else {
tracing_subscriber::registry()
.with(env_filter)
.with(
fmt::layer()
.with_writer(std::io::stderr)
.with_target(true)
.with_thread_ids(true)
.with_line_number(true),
)
.init();
tracing::info!(
log_level = %log_level,
"Logging initialized (console only)"
);
Ok(None)
}
}
pub fn create_rotating_writer(log_dir: &Path) -> Result<impl Write + Clone + Send + 'static> {
std::fs::create_dir_all(log_dir)?;
RotatingWriter::new(log_dir)
}