1use std::env;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum Level {
11 Debug,
13 #[default]
15 Info,
16 Warn,
18 Error,
20}
21
22impl Level {
23 #[must_use]
25 pub fn from_env(var: &str) -> Self {
26 match var.to_lowercase().as_str() {
27 "debug" => Level::Debug,
28 "info" => Level::Info,
29 "warn" | "warning" => Level::Warn,
30 "error" | "err" => Level::Error,
31 _ => Level::Info,
32 }
33 }
34
35 #[must_use]
37 pub const fn env_var() -> &'static str {
38 "NETSPEED_LOG"
39 }
40
41 #[must_use]
43 pub fn should_log(self, threshold: Level) -> bool {
44 self as u8 >= threshold as u8
45 }
46}
47
48#[must_use]
50pub fn current_level() -> Level {
51 env::var(Level::env_var())
52 .ok()
53 .map(|v| Level::from_env(&v))
54 .unwrap_or_default()
55}
56
57#[must_use]
59pub fn is_verbose() -> bool {
60 current_level() == Level::Debug
61}
62
63pub fn log(level: Level, message: &str, fields: &[(&str, &str)]) {
65 if level.should_log(current_level()) {
66 eprint!("[{}] {}", format!("{:?}", level).to_uppercase(), message);
67 for (key, value) in fields {
68 eprint!(" {}=\"{}\"", key, value);
69 }
70 eprintln!();
71 }
72}
73
74pub fn debug(message: &str) {
76 log(Level::Debug, message, &[]);
77}
78
79pub fn info(message: &str) {
81 log(Level::Info, message, &[]);
82}
83
84pub fn warn(message: &str) {
86 log(Level::Warn, message, &[]);
87}
88
89pub fn error(message: &str) {
91 log(Level::Error, message, &[]);
92}
93
94#[must_use]
96pub fn format_json_entry(level: Level, message: &str, fields: &[(&str, &str)]) -> String {
97 use serde_json::json;
98 let mut map = serde_json::Map::new();
99 map.insert(
100 "level".to_string(),
101 json!(format!("{:?}", level).to_lowercase()),
102 );
103 map.insert("message".to_string(), json!(message));
104 map.insert(
105 "timestamp".to_string(),
106 json!(chrono::Utc::now().to_rfc3339()),
107 );
108 for (key, value) in fields {
109 map.insert(key.to_string(), json!(value));
110 }
111 serde_json::to_string(&map).unwrap_or_else(|_| "{\"error\": \"log format failed\"}".to_string())
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
121 fn test_level_from_env_debug() {
122 assert_eq!(Level::from_env("debug"), Level::Debug);
123 assert_eq!(Level::from_env("DEBUG"), Level::Debug);
124 assert_eq!(Level::from_env("Debug"), Level::Debug);
125 }
126
127 #[test]
128 fn test_level_from_env_info() {
129 assert_eq!(Level::from_env("info"), Level::Info);
130 assert_eq!(Level::from_env("INFO"), Level::Info);
131 assert_eq!(Level::from_env("Info"), Level::Info);
132 }
133
134 #[test]
135 fn test_level_from_env_warn() {
136 assert_eq!(Level::from_env("warn"), Level::Warn);
137 assert_eq!(Level::from_env("warning"), Level::Warn);
138 assert_eq!(Level::from_env("WARN"), Level::Warn);
139 assert_eq!(Level::from_env("WARNING"), Level::Warn);
140 assert_eq!(Level::from_env("Warn"), Level::Warn);
141 }
142
143 #[test]
144 fn test_level_from_env_error() {
145 assert_eq!(Level::from_env("error"), Level::Error);
146 assert_eq!(Level::from_env("err"), Level::Error);
147 assert_eq!(Level::from_env("ERROR"), Level::Error);
148 assert_eq!(Level::from_env("Err"), Level::Error);
149 }
150
151 #[test]
152 fn test_level_from_env_invalid() {
153 assert_eq!(Level::from_env("invalid"), Level::Info);
154 assert_eq!(Level::from_env("trash"), Level::Info);
155 assert_eq!(Level::from_env(""), Level::Info);
156 assert_eq!(Level::from_env("123"), Level::Info);
157 }
158
159 #[test]
160 fn test_level_should_log() {
161 assert!(Level::Debug.should_log(Level::Debug));
163 assert!(Level::Info.should_log(Level::Debug)); assert!(Level::Warn.should_log(Level::Debug));
165 assert!(Level::Error.should_log(Level::Debug));
166
167 assert!(!Level::Debug.should_log(Level::Info)); assert!(Level::Info.should_log(Level::Info));
170 assert!(Level::Warn.should_log(Level::Info));
171 assert!(Level::Error.should_log(Level::Info));
172
173 assert!(!Level::Debug.should_log(Level::Warn));
175 assert!(!Level::Info.should_log(Level::Warn));
176 assert!(Level::Warn.should_log(Level::Warn));
177 assert!(Level::Error.should_log(Level::Warn));
178
179 assert!(!Level::Debug.should_log(Level::Error));
181 assert!(!Level::Info.should_log(Level::Error));
182 assert!(!Level::Warn.should_log(Level::Error));
183 assert!(Level::Error.should_log(Level::Error));
184 }
185
186 #[test]
187 fn test_level_default() {
188 assert_eq!(Level::default(), Level::Info);
189 }
190
191 #[test]
192 fn test_level_debug_trait() {
193 let debug_str = format!("{:?}", Level::Debug);
194 assert!(debug_str.contains("Debug"));
195
196 let debug_str = format!("{:?}", Level::Info);
197 assert!(debug_str.contains("Info"));
198
199 let debug_str = format!("{:?}", Level::Warn);
200 assert!(debug_str.contains("Warn"));
201
202 let debug_str = format!("{:?}", Level::Error);
203 assert!(debug_str.contains("Error"));
204 }
205
206 #[test]
207 fn test_level_copy() {
208 let level = Level::Debug;
209 let _copied = level; assert_eq!(_copied, level);
211 }
212
213 #[test]
214 fn test_level_eq() {
215 assert_eq!(Level::Debug, Level::Debug);
216 assert_eq!(Level::Info, Level::Info);
217 assert_eq!(Level::Warn, Level::Warn);
218 assert_eq!(Level::Error, Level::Error);
219 assert_ne!(Level::Debug, Level::Info);
220 assert_ne!(Level::Info, Level::Error);
221 }
222
223 #[test]
224 fn test_level_partial_eq() {
225 assert!(Level::Debug == Level::Debug);
226 assert!(Level::Debug != Level::Error);
227 }
228
229 #[test]
230 fn test_level_env_var() {
231 assert_eq!(Level::env_var(), "NETSPEED_LOG");
232 }
233
234 #[test]
237 fn test_current_level_returns_info_by_default() {
238 let level = current_level();
240 assert_eq!(level, Level::Info);
241 }
242
243 #[test]
246 fn test_is_verbose_by_default() {
247 let verbose = is_verbose();
249 assert!(!verbose);
250 }
251
252 #[test]
255 fn test_log_empty_fields() {
256 log(Level::Info, "test message", &[]);
258 }
259
260 #[test]
261 fn test_log_with_fields() {
262 log(
263 Level::Debug,
264 "test",
265 &[("key1", "value1"), ("key2", "value2")],
266 );
267 }
268
269 #[test]
270 fn test_log_special_characters_in_fields() {
271 log(Level::Info, "test", &[("key", "value with spaces")]);
272 log(Level::Info, "test", &[("key", "value\"with\"quotes")]);
273 log(Level::Info, "test", &[("key", "")]);
274 }
275
276 #[test]
279 fn test_debug_function() {
280 debug("debug message");
281 }
282
283 #[test]
284 fn test_info_function() {
285 info("info message");
286 }
287
288 #[test]
289 fn test_warn_function() {
290 warn("warning message");
291 }
292
293 #[test]
294 fn test_error_function() {
295 error("error message");
296 }
297
298 #[test]
301 fn test_format_json_entry_debug() {
302 let entry = format_json_entry(Level::Debug, "debug message", &[("key", "value")]);
303 assert!(entry.contains("debug"));
304 assert!(entry.contains("debug message"));
305 assert!(entry.contains("timestamp"));
306 assert!(entry.contains("key"));
307 assert!(entry.contains("value"));
308 }
309
310 #[test]
311 fn test_format_json_entry_info() {
312 let entry = format_json_entry(Level::Info, "test message", &[("key", "value")]);
313 assert!(entry.contains("info"));
314 assert!(entry.contains("test message"));
315 assert!(entry.contains("timestamp"));
316 assert!(entry.contains("key"));
317 assert!(entry.contains("value"));
318 }
319
320 #[test]
321 fn test_format_json_entry_warn() {
322 let entry = format_json_entry(Level::Warn, "warning message", &[]);
323 assert!(entry.contains("warn"));
324 assert!(entry.contains("warning message"));
325 }
326
327 #[test]
328 fn test_format_json_entry_error() {
329 let entry = format_json_entry(Level::Error, "error occurred", &[]);
330 assert!(entry.contains("error"));
331 assert!(entry.contains("error occurred"));
332 }
333
334 #[test]
335 fn test_format_json_entry_empty_fields() {
336 let entry = format_json_entry(Level::Error, "error occurred", &[]);
337 assert!(entry.contains("error"));
338 assert!(entry.contains("error occurred"));
339 assert!(entry.contains("timestamp"));
341 }
342
343 #[test]
344 fn test_format_json_entry_multiple_fields() {
345 let entry = format_json_entry(
346 Level::Info,
347 "multi-field message",
348 &[
349 ("field1", "value1"),
350 ("field2", "value2"),
351 ("field3", "value3"),
352 ],
353 );
354 assert!(entry.contains("field1"));
355 assert!(entry.contains("value1"));
356 assert!(entry.contains("field2"));
357 assert!(entry.contains("value2"));
358 assert!(entry.contains("field3"));
359 assert!(entry.contains("value3"));
360 }
361
362 #[test]
363 fn test_format_json_entry_is_valid_json() {
364 let entry = format_json_entry(Level::Info, "test", &[("key", "value")]);
365 let parsed: serde_json::Value = serde_json::from_str(&entry).unwrap();
367 assert_eq!(parsed["level"], "info");
368 assert_eq!(parsed["message"], "test");
369 assert!(parsed.get("timestamp").is_some());
370 assert_eq!(parsed["key"], "value");
371 }
372
373 #[test]
374 fn test_format_json_entry_timestamp_format() {
375 let entry = format_json_entry(Level::Info, "test", &[]);
376 let parsed: serde_json::Value = serde_json::from_str(&entry).unwrap();
377 let timestamp = parsed["timestamp"].as_str().unwrap();
378 assert!(!timestamp.is_empty());
381 assert!(timestamp.contains("-"));
383 }
384}