1use std::path::PathBuf;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum LogRotation {
6 Hourly,
7 Daily,
8 Weekly,
9}
10
11impl LogRotation {
12 pub fn from_env(prefix: &str) -> Self {
13 let key = format!("{prefix}_LOG_ROTATION");
14 Self::parse(std::env::var(&key).ok().as_deref())
15 }
16
17 pub fn parse(value: Option<&str>) -> Self {
18 match value {
19 Some("hourly") => Self::Hourly,
20 Some("weekly") => Self::Weekly,
21 Some("daily") | None => Self::Daily,
22 _ => Self::Daily,
23 }
24 }
25}
26
27#[derive(Debug, Clone)]
29pub struct LoggingOptions {
30 pub app_name: &'static str,
31 pub logs_dir: PathBuf,
32 pub level: String,
33 pub rotation: LogRotation,
34 pub max_files: Option<usize>,
35 pub file_enabled: bool,
36 pub console_enabled: bool,
37}
38
39impl LoggingOptions {
40 pub fn level_from_env(prefix: &str) -> String {
41 let key = format!("{prefix}_LOG_LEVEL");
42 match std::env::var(&key) {
43 Ok(value) if matches!(value.as_str(), "trace" | "debug" | "info" | "warn" | "error") => value,
44 _ => "info".to_string(),
45 }
46 }
47
48 pub fn max_files_from_env(prefix: &str) -> Option<usize> {
49 let key = format!("{prefix}_LOG_MAX_FILES");
50 std::env::var(&key).ok().and_then(|value| value.parse().ok())
51 }
52
53 pub fn file_logging_enabled(prefix: &str) -> bool {
54 let key = format!("{prefix}_LOG_FILE");
55 std::env::var(&key).map(|value| value != "0").unwrap_or(true)
56 }
57
58 fn resolve(env_prefix: &str, app_name: &'static str, logs_dir: Option<PathBuf>, console_enabled: bool) -> Self {
59 let file_enabled = logs_dir.is_some() && Self::file_logging_enabled(env_prefix);
60 Self {
61 app_name,
62 logs_dir: logs_dir.unwrap_or_default(),
63 level: Self::level_from_env(env_prefix),
64 rotation: LogRotation::from_env(env_prefix),
65 max_files: Self::max_files_from_env(env_prefix),
66 file_enabled,
67 console_enabled,
68 }
69 }
70}
71
72#[derive(Debug, Clone)]
74pub struct AgentInit {
75 pub app_version: &'static str,
76 pub quiet_env: Option<&'static str>,
77 pub logging: LoggingOptions,
78}
79
80#[derive(Debug, Clone)]
82pub struct AgentBuilder {
83 app_version: &'static str,
84 env_prefix: &'static str,
85 app_name: &'static str,
86 quiet_env: Option<&'static str>,
87 logs_dir: Option<PathBuf>,
88 console_enabled: bool,
89}
90
91impl AgentBuilder {
92 pub fn new(app_version: &'static str) -> Self {
93 Self {
94 app_version,
95 env_prefix: "",
96 app_name: "",
97 quiet_env: None,
98 logs_dir: None,
99 console_enabled: true,
100 }
101 }
102
103 pub fn env_prefix(mut self, prefix: &'static str) -> Self {
104 self.env_prefix = prefix;
105 self
106 }
107
108 pub fn app_name(mut self, name: &'static str) -> Self {
109 self.app_name = name;
110 self
111 }
112
113 pub fn quiet_env(mut self, env: &'static str) -> Self {
114 self.quiet_env = Some(env);
115 self
116 }
117
118 pub fn logs_dir(mut self, dir: PathBuf) -> Self {
119 self.logs_dir = Some(dir);
120 self
121 }
122
123 pub fn console_enabled(mut self, enabled: bool) -> Self {
124 self.console_enabled = enabled;
125 self
126 }
127
128 pub fn build(self) -> AgentInit {
129 AgentInit {
130 app_version: self.app_version,
131 quiet_env: self.quiet_env,
132 logging: LoggingOptions::resolve(self.env_prefix, self.app_name, self.logs_dir, self.console_enabled),
133 }
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn defaults_to_daily_rotation() {
143 assert_eq!(LogRotation::parse(None), LogRotation::Daily);
144 assert_eq!(LogRotation::parse(Some("daily")), LogRotation::Daily);
145 }
146
147 #[test]
148 fn parses_rotation_values() {
149 assert_eq!(LogRotation::parse(Some("hourly")), LogRotation::Hourly);
150 assert_eq!(LogRotation::parse(Some("weekly")), LogRotation::Weekly);
151 assert_eq!(LogRotation::parse(Some("monthly")), LogRotation::Daily);
152 }
153
154 #[test]
155 fn builder_resolves_logging_without_logs_dir() {
156 let init = AgentBuilder::new("0.0.12-test")
157 .env_prefix("ELPH")
158 .app_name("elph")
159 .console_enabled(false)
160 .build();
161
162 assert_eq!(init.app_version, "0.0.12-test");
163 assert!(!init.logging.file_enabled);
164 assert!(!init.logging.console_enabled);
165 assert_eq!(init.logging.level, "info");
166 }
167}