use crate::config::LoggingConfig;
use std::fs;
use std::path::{Path, PathBuf};
use tracing::info;
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
static LOGGING_INITIALIZED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
pub fn init_logging(config: &LoggingConfig) -> Option<WorkerGuard> {
if LOGGING_INITIALIZED.swap(true, std::sync::atomic::Ordering::SeqCst) {
return None;
}
let target_level = config.level.to_lowercase();
let filter_directives = match target_level.as_str() {
"trace" => "brum=trace,tower_http=debug",
"debug" => "brum=debug,tower_http=info",
"warn" => "brum=warn,tower_http=warn",
"error" => "brum=error,tower_http=error",
_ => "brum=info,tower_http=info",
};
let env_filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new(filter_directives));
let (file_layer, guard) = if config.file_enabled {
let log_dir = config.resolve_log_directory();
if let Err(e) = fs::create_dir_all(&log_dir) {
eprintln!("Failed to create log directory '{}': {}", log_dir.display(), e);
(None, None)
} else {
if config.max_files > 0 {
cleanup_old_logs(&log_dir, config.max_files);
}
let file_appender = match config.rotation.to_lowercase().as_str() {
"hourly" => tracing_appender::rolling::hourly(&log_dir, "brum.log"),
"never" => tracing_appender::rolling::never(&log_dir, "brum.log"),
_ => tracing_appender::rolling::daily(&log_dir, "brum.log"),
};
let (non_blocking, worker_guard) = tracing_appender::non_blocking(file_appender);
let layer = tracing_subscriber::fmt::layer()
.with_writer(non_blocking)
.with_ansi(false)
.with_target(true)
.with_thread_ids(false);
(Some(layer), Some(worker_guard))
}
} else {
(None, None)
};
let console_layer = if config.console_enabled {
Some(
tracing_subscriber::fmt::layer()
.with_writer(std::io::stdout)
.with_ansi(true),
)
} else {
None
};
let _ = tracing_subscriber::registry()
.with(env_filter)
.with(console_layer)
.with(file_layer)
.try_init();
if config.file_enabled {
info!(
"Brum logging initialized. Log level: '{}', Log directory: '{}'",
config.level,
config.resolve_log_directory().display()
);
}
guard
}
pub fn cleanup_old_logs(log_dir: &Path, max_files: usize) {
if max_files == 0 {
return;
}
let entries = match fs::read_dir(log_dir) {
Ok(e) => e,
Err(_) => return,
};
let mut log_files: Vec<(PathBuf, std::time::SystemTime)> = Vec::new();
for entry in entries.flatten() {
let path = entry.path();
if path.is_file() {
if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) {
if file_name.starts_with("brum.log") && file_name != "brum.log" {
if let Ok(metadata) = entry.metadata() {
if let Ok(modified) = metadata.modified() {
log_files.push((path, modified));
}
}
}
}
}
}
log_files.sort_by_key(|(_, time)| *time);
if log_files.len() > max_files {
let remove_count = log_files.len() - max_files;
for (path, _) in log_files.iter().take(remove_count) {
let _ = fs::remove_file(path);
}
}
}
pub fn write_initial_admin_credentials(
log_dir: &Path,
username: &str,
password: &str,
) -> std::io::Result<PathBuf> {
fs::create_dir_all(log_dir)?;
let target = log_dir.join("initial_admin_credentials.txt");
let now = chrono::Utc::now().to_rfc3339();
let content = format!(
"================================================================================\n\
🛡️ BRUM INITIAL ADMINISTRATIVE CREDENTIALS\n\
================================================================================\n\
Generated : {}\n\
Username : {}\n\
Password : {}\n\n\
SECURITY NOTICE:\n\
- Please log in and change this password immediately via Settings -> Security.\n\
- Alternatively, log in with any local Windows / Linux Administrator account.\n\
- Delete or restrict this file once credentials have been saved.\n\
================================================================================\n",
now, username, password
);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
use std::io::Write;
let mut file = fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.mode(0o600)
.open(&target)?;
file.write_all(content.as_bytes())?;
}
#[cfg(not(unix))]
{
fs::write(&target, content)?;
}
Ok(target)
}
pub fn cleanup_initial_admin_credentials() {
let log_dir = crate::config::LoggingConfig::default().resolve_log_directory();
let target = log_dir.join("initial_admin_credentials.txt");
if target.is_file() {
if let Err(e) = fs::remove_file(&target) {
tracing::warn!("Failed to remove initial admin credentials file: {}", e);
} else {
tracing::info!("Initial administrative credentials file cleaned up successfully.");
}
}
}