use std::path::PathBuf;
pub const ENV_LOG_DIR: &str = "DIG_LOG_DIR";
pub fn resolve_log_dir<G, C>(service: &str, get: G, can_create: C) -> PathBuf
where
G: Fn(&str) -> Option<String>,
C: Fn(&std::path::Path) -> bool,
{
let read = |key: &str| {
get(key)
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
};
if let Some(root) = read(ENV_LOG_DIR) {
return PathBuf::from(root).join(service);
}
let machine = machine_root(&read).join(service);
if can_create(&machine) {
return machine;
}
dev_root(&read).join(service)
}
pub fn log_dir(service: &str) -> PathBuf {
resolve_log_dir(
service,
|key| std::env::var(key).ok(),
|path| std::fs::create_dir_all(path).is_ok(),
)
}
#[cfg(windows)]
fn machine_root<R: Fn(&str) -> Option<String>>(read: &R) -> PathBuf {
let base = read("ProgramData").unwrap_or_else(|| r"C:\ProgramData".to_string());
PathBuf::from(base).join("DigNetwork").join("logs")
}
#[cfg(target_os = "macos")]
fn machine_root<R: Fn(&str) -> Option<String>>(_read: &R) -> PathBuf {
PathBuf::from("/Library/Logs/DigNetwork")
}
#[cfg(all(unix, not(target_os = "macos")))]
fn machine_root<R: Fn(&str) -> Option<String>>(_read: &R) -> PathBuf {
PathBuf::from("/var/log/dig")
}
#[cfg(windows)]
fn dev_root<R: Fn(&str) -> Option<String>>(read: &R) -> PathBuf {
let base = read("LOCALAPPDATA")
.or_else(|| read("ProgramData"))
.unwrap_or_else(|| r"C:\ProgramData".to_string());
PathBuf::from(base).join("DigNetwork").join("logs")
}
#[cfg(target_os = "macos")]
fn dev_root<R: Fn(&str) -> Option<String>>(read: &R) -> PathBuf {
let home = read("HOME").unwrap_or_else(|| "/tmp".to_string());
PathBuf::from(home)
.join("Library")
.join("Logs")
.join("DigNetwork")
}
#[cfg(all(unix, not(target_os = "macos")))]
fn dev_root<R: Fn(&str) -> Option<String>>(read: &R) -> PathBuf {
if let Some(state) = read("XDG_STATE_HOME") {
return PathBuf::from(state).join("dig").join("logs");
}
let home = read("HOME").unwrap_or_else(|| "/tmp".to_string());
PathBuf::from(home)
.join(".local")
.join("state")
.join("dig")
.join("logs")
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
use std::path::Path;
fn env(pairs: &[(&str, &str)]) -> impl Fn(&str) -> Option<String> {
let map: HashMap<String, String> = pairs
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect();
move |key| map.get(key).cloned()
}
#[test]
fn override_wins_and_joins_service() {
let dir = resolve_log_dir("dig-node", env(&[(ENV_LOG_DIR, "/custom/root")]), |_| true);
assert_eq!(dir, Path::new("/custom/root").join("dig-node"));
}
#[test]
fn blank_override_is_ignored() {
let dir = resolve_log_dir("dig-dns", env(&[(ENV_LOG_DIR, " ")]), |_| true);
assert!(dir.ends_with(Path::new("dig-dns")));
assert!(!dir.starts_with("/custom"));
}
#[test]
fn machine_root_used_when_creatable() {
let dir = resolve_log_dir("dig-updater", env(&[]), |_| true);
assert!(dir.ends_with(Path::new("dig-updater")));
#[cfg(all(unix, not(target_os = "macos")))]
assert_eq!(dir, Path::new("/var/log/dig/dig-updater"));
#[cfg(target_os = "macos")]
assert_eq!(dir, Path::new("/Library/Logs/DigNetwork/dig-updater"));
}
#[test]
fn dev_fallback_when_machine_root_not_creatable() {
let dir = resolve_log_dir(
"dig-node",
env(&[
("HOME", "/home/dev"),
("XDG_STATE_HOME", "/home/dev/.state"),
("LOCALAPPDATA", r"C:\Users\dev\AppData\Local"),
]),
|path: &Path| path.to_string_lossy().contains("dev"),
);
assert!(dir.ends_with(Path::new("dig-node")));
#[cfg(all(unix, not(target_os = "macos")))]
assert_eq!(dir, Path::new("/home/dev/.state/dig/logs/dig-node"));
}
#[cfg(all(unix, not(target_os = "macos")))]
#[test]
fn linux_dev_fallback_without_xdg_uses_local_state() {
let dir = resolve_log_dir("dig-dns", env(&[("HOME", "/home/dev")]), |_| false);
assert_eq!(dir, Path::new("/home/dev/.local/state/dig/logs/dig-dns"));
}
}