1use std::path::PathBuf;
17
18pub const ENV_LOG_DIR: &str = "DIG_LOG_DIR";
20
21pub fn resolve_log_dir<G, C>(service: &str, get: G, can_create: C) -> PathBuf
27where
28 G: Fn(&str) -> Option<String>,
29 C: Fn(&std::path::Path) -> bool,
30{
31 let read = |key: &str| {
32 get(key)
33 .map(|value| value.trim().to_string())
34 .filter(|value| !value.is_empty())
35 };
36
37 if let Some(root) = read(ENV_LOG_DIR) {
39 return PathBuf::from(root).join(service);
40 }
41
42 let machine = machine_root(&read).join(service);
44 if can_create(&machine) {
45 return machine;
46 }
47 dev_root(&read).join(service)
48}
49
50pub fn log_dir(service: &str) -> PathBuf {
53 resolve_log_dir(
54 service,
55 |key| std::env::var(key).ok(),
56 |path| std::fs::create_dir_all(path).is_ok(),
57 )
58}
59
60#[cfg(windows)]
63fn machine_root<R: Fn(&str) -> Option<String>>(read: &R) -> PathBuf {
64 let base = read("ProgramData").unwrap_or_else(|| r"C:\ProgramData".to_string());
65 PathBuf::from(base).join("DigNetwork").join("logs")
66}
67
68#[cfg(target_os = "macos")]
69fn machine_root<R: Fn(&str) -> Option<String>>(_read: &R) -> PathBuf {
70 PathBuf::from("/Library/Logs/DigNetwork")
71}
72
73#[cfg(all(unix, not(target_os = "macos")))]
74fn machine_root<R: Fn(&str) -> Option<String>>(_read: &R) -> PathBuf {
75 PathBuf::from("/var/log/dig")
76}
77
78#[cfg(windows)]
81fn dev_root<R: Fn(&str) -> Option<String>>(read: &R) -> PathBuf {
82 let base = read("LOCALAPPDATA")
83 .or_else(|| read("ProgramData"))
84 .unwrap_or_else(|| r"C:\ProgramData".to_string());
85 PathBuf::from(base).join("DigNetwork").join("logs")
86}
87
88#[cfg(target_os = "macos")]
89fn dev_root<R: Fn(&str) -> Option<String>>(read: &R) -> PathBuf {
90 let home = read("HOME").unwrap_or_else(|| "/tmp".to_string());
91 PathBuf::from(home)
92 .join("Library")
93 .join("Logs")
94 .join("DigNetwork")
95}
96
97#[cfg(all(unix, not(target_os = "macos")))]
98fn dev_root<R: Fn(&str) -> Option<String>>(read: &R) -> PathBuf {
99 if let Some(state) = read("XDG_STATE_HOME") {
100 return PathBuf::from(state).join("dig").join("logs");
101 }
102 let home = read("HOME").unwrap_or_else(|| "/tmp".to_string());
103 PathBuf::from(home)
104 .join(".local")
105 .join("state")
106 .join("dig")
107 .join("logs")
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113 use std::collections::HashMap;
114 use std::path::Path;
115
116 fn env(pairs: &[(&str, &str)]) -> impl Fn(&str) -> Option<String> {
118 let map: HashMap<String, String> = pairs
119 .iter()
120 .map(|(k, v)| (k.to_string(), v.to_string()))
121 .collect();
122 move |key| map.get(key).cloned()
123 }
124
125 #[test]
126 fn override_wins_and_joins_service() {
127 let dir = resolve_log_dir("dig-node", env(&[(ENV_LOG_DIR, "/custom/root")]), |_| true);
128 assert_eq!(dir, Path::new("/custom/root").join("dig-node"));
129 }
130
131 #[test]
132 fn blank_override_is_ignored() {
133 let dir = resolve_log_dir("dig-dns", env(&[(ENV_LOG_DIR, " ")]), |_| true);
135 assert!(dir.ends_with(Path::new("dig-dns")));
136 assert!(!dir.starts_with("/custom"));
137 }
138
139 #[test]
140 fn machine_root_used_when_creatable() {
141 let dir = resolve_log_dir("dig-updater", env(&[]), |_| true);
142 assert!(dir.ends_with(Path::new("dig-updater")));
143 #[cfg(all(unix, not(target_os = "macos")))]
144 assert_eq!(dir, Path::new("/var/log/dig/dig-updater"));
145 #[cfg(target_os = "macos")]
146 assert_eq!(dir, Path::new("/Library/Logs/DigNetwork/dig-updater"));
147 }
148
149 #[test]
150 fn dev_fallback_when_machine_root_not_creatable() {
151 let dir = resolve_log_dir(
153 "dig-node",
154 env(&[
155 ("HOME", "/home/dev"),
156 ("XDG_STATE_HOME", "/home/dev/.state"),
157 ("LOCALAPPDATA", r"C:\Users\dev\AppData\Local"),
158 ]),
159 |path: &Path| path.to_string_lossy().contains("dev"),
160 );
161 assert!(dir.ends_with(Path::new("dig-node")));
162 #[cfg(all(unix, not(target_os = "macos")))]
163 assert_eq!(dir, Path::new("/home/dev/.state/dig/logs/dig-node"));
164 }
165
166 #[cfg(all(unix, not(target_os = "macos")))]
167 #[test]
168 fn linux_dev_fallback_without_xdg_uses_local_state() {
169 let dir = resolve_log_dir("dig-dns", env(&[("HOME", "/home/dev")]), |_| false);
170 assert_eq!(dir, Path::new("/home/dev/.local/state/dig/logs/dig-dns"));
171 }
172}