1use std::env;
39use std::fmt;
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum LogLevel {
44 Trace,
46 Debug,
48 Info,
50 Warn,
52 Error,
54}
55
56impl LogLevel {
57 pub fn from_env() -> Self {
59 env::var("RUST_LOG")
60 .ok()
61 .and_then(|s| s.parse().ok())
62 .unwrap_or(LogLevel::Info)
63 }
64}
65
66impl fmt::Display for LogLevel {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 match self {
69 LogLevel::Trace => write!(f, "trace"),
70 LogLevel::Debug => write!(f, "debug"),
71 LogLevel::Info => write!(f, "info"),
72 LogLevel::Warn => write!(f, "warn"),
73 LogLevel::Error => write!(f, "error"),
74 }
75 }
76}
77
78impl std::str::FromStr for LogLevel {
79 type Err = String;
80
81 fn from_str(s: &str) -> Result<Self, Self::Err> {
82 match s.to_lowercase().as_str() {
83 "trace" => Ok(LogLevel::Trace),
84 "debug" => Ok(LogLevel::Debug),
85 "info" => Ok(LogLevel::Info),
86 "warn" | "warning" => Ok(LogLevel::Warn),
87 "error" => Ok(LogLevel::Error),
88 _ => Err(format!("Invalid log level: {}", s)),
89 }
90 }
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub enum LogFormat {
96 Text,
98 Json,
100 Compact,
102}
103
104impl LogFormat {
105 pub fn from_env() -> Self {
107 env::var("LOG_FORMAT")
108 .ok()
109 .and_then(|s| s.parse().ok())
110 .unwrap_or(LogFormat::Text)
111 }
112}
113
114impl fmt::Display for LogFormat {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 match self {
117 LogFormat::Text => write!(f, "text"),
118 LogFormat::Json => write!(f, "json"),
119 LogFormat::Compact => write!(f, "compact"),
120 }
121 }
122}
123
124impl std::str::FromStr for LogFormat {
125 type Err = String;
126
127 fn from_str(s: &str) -> Result<Self, Self::Err> {
128 match s.to_lowercase().as_str() {
129 "text" | "pretty" | "human" => Ok(LogFormat::Text),
130 "json" => Ok(LogFormat::Json),
131 "compact" => Ok(LogFormat::Compact),
132 _ => Err(format!("Invalid log format: {}", s)),
133 }
134 }
135}
136
137#[derive(Debug, Clone)]
139pub struct LogConfig {
140 pub level: LogLevel,
142 pub format: LogFormat,
144 pub timestamps: bool,
146 pub source_location: bool,
148 pub thread_ids: bool,
150}
151
152impl Default for LogConfig {
153 fn default() -> Self {
154 Self {
155 level: LogLevel::Info,
156 format: LogFormat::Text,
157 timestamps: true,
158 source_location: false,
159 thread_ids: false,
160 }
161 }
162}
163
164impl LogConfig {
165 pub fn from_env() -> Self {
167 Self {
168 level: LogLevel::from_env(),
169 format: LogFormat::from_env(),
170 timestamps: env::var("LOG_TIMESTAMPS")
171 .ok()
172 .and_then(|v| v.parse().ok())
173 .unwrap_or(true),
174 source_location: env::var("LOG_SOURCE_LOCATION")
175 .ok()
176 .and_then(|v| v.parse().ok())
177 .unwrap_or(false),
178 thread_ids: env::var("LOG_THREAD_IDS")
179 .ok()
180 .and_then(|v| v.parse().ok())
181 .unwrap_or(false),
182 }
183 }
184}
185
186pub fn init_logging(config: LogConfig) -> Result<(), LoggingError> {
204 if env::var("RUST_LOG").is_err() {
209 unsafe {
210 env::set_var("RUST_LOG", format!("thread_flow={}", config.level));
211 }
212 }
213
214 let mut builder = env_logger::builder();
216 builder.parse_env("RUST_LOG");
217
218 if let Some(precision) = if config.timestamps {
219 Some(env_logger::fmt::TimestampPrecision::Millis)
220 } else {
221 None
222 } {
223 builder.format_timestamp(Some(precision));
224 } else {
225 builder.format_timestamp(None);
226 }
227
228 builder.format_module_path(config.source_location);
229
230 builder
231 .try_init()
232 .map_err(|e| LoggingError::InitializationFailed(e.to_string()))?;
233
234 Ok(())
235}
236
237pub fn init_cli_logging() -> Result<(), LoggingError> {
241 init_logging(LogConfig {
242 level: LogLevel::from_env(),
243 format: LogFormat::Text,
244 timestamps: true,
245 source_location: false,
246 thread_ids: false,
247 })
248}
249
250pub fn init_production_logging() -> Result<(), LoggingError> {
254 init_logging(LogConfig {
255 level: LogLevel::Info,
256 format: LogFormat::Json,
257 timestamps: true,
258 source_location: true,
259 thread_ids: true,
260 })
261}
262
263#[derive(Debug, thiserror::Error)]
265pub enum LoggingError {
266 #[error("Failed to initialize logging: {0}")]
267 InitializationFailed(String),
268
269 #[error("Invalid log configuration: {0}")]
270 InvalidConfiguration(String),
271}
272
273#[macro_export]
287macro_rules! timed_operation {
288 ($name:expr, $($key:ident = $value:expr),*, $block:block) => {{
289 let _start = std::time::Instant::now();
290 $(
291 println!("[DEBUG] {}: {} = {:?}", $name, stringify!($key), $value);
292 )*
293 let result = $block;
294 let _duration = _start.elapsed();
295 println!("[INFO] {} completed in {:?}", $name, _duration);
296 result
297 }};
298}
299
300pub mod structured {
302 use thread_utilities::RapidMap;
303
304 pub struct LogContext {
306 fields: RapidMap<String, String>,
307 }
308
309 impl LogContext {
310 pub fn new() -> Self {
312 Self {
313 fields: thread_utilities::get_map(),
314 }
315 }
316
317 pub fn field(mut self, key: impl Into<String>, value: impl ToString) -> Self {
319 self.fields.insert(key.into(), value.to_string());
320 self
321 }
322
323 pub fn info(self, message: &str) {
325 println!("[INFO] {} {:?}", message, self.fields);
327 }
328
329 pub fn warn(self, message: &str) {
331 eprintln!("[WARN] {} {:?}", message, self.fields);
332 }
333
334 pub fn error(self, message: &str) {
336 eprintln!("[ERROR] {} {:?}", message, self.fields);
337 }
338 }
339
340 impl Default for LogContext {
341 fn default() -> Self {
342 Self::new()
343 }
344 }
345}
346
347#[cfg(test)]
348mod tests {
349 use super::*;
350
351 #[test]
352 fn test_log_level_parsing() {
353 assert_eq!("trace".parse::<LogLevel>().unwrap(), LogLevel::Trace);
354 assert_eq!("debug".parse::<LogLevel>().unwrap(), LogLevel::Debug);
355 assert_eq!("info".parse::<LogLevel>().unwrap(), LogLevel::Info);
356 assert_eq!("warn".parse::<LogLevel>().unwrap(), LogLevel::Warn);
357 assert_eq!("error".parse::<LogLevel>().unwrap(), LogLevel::Error);
358 }
359
360 #[test]
361 fn test_log_format_parsing() {
362 assert_eq!("text".parse::<LogFormat>().unwrap(), LogFormat::Text);
363 assert_eq!("json".parse::<LogFormat>().unwrap(), LogFormat::Json);
364 assert_eq!("compact".parse::<LogFormat>().unwrap(), LogFormat::Compact);
365 }
366
367 #[test]
368 fn test_log_config_default() {
369 let config = LogConfig::default();
370 assert_eq!(config.level, LogLevel::Info);
371 assert_eq!(config.format, LogFormat::Text);
372 assert!(config.timestamps);
373 assert!(!config.source_location);
374 assert!(!config.thread_ids);
375 }
376}