1#[macro_export]
8macro_rules! log {
9 ($level:expr, $msg:expr) => {{
11 let message = format!($msg);
14 let record = $crate::interface::logging::Record::new(
15 $level,
16 module_path!(),
17 message,
18 )
19 .with_location(file!(), line!());
20 $crate::interface::logging::log(record);
21 }};
22
23 ($level:expr, $fmt:expr, $($arg:tt)*) => {{
25 let message = format!($fmt, $($arg)*);
26 let record = $crate::interface::logging::Record::new(
27 $level,
28 module_path!(),
29 message,
30 )
31 .with_location(file!(), line!());
32 $crate::interface::logging::log(record);
33 }};
34}
35
36#[macro_export]
38macro_rules! log_trace {
39 ($msg:expr) => {
41 $crate::log!($crate::interface::logging::LogLevel::Trace, $msg)
42 };
43
44 ($msg:expr, { $($key:expr => $value:expr),+ $(,)? }) => {{
46 let mut record = $crate::interface::logging::Record::new(
47 $crate::interface::logging::LogLevel::Trace,
48 module_path!(),
49 $msg,
50 )
51 .with_location(file!(), line!());
52 $(
53 record = record.with_field($key, $value);
54 )+
55 $crate::interface::logging::log(record);
56 }};
57
58 ($fmt:expr, $($arg:tt)*) => {
60 $crate::log!($crate::interface::logging::LogLevel::Trace, $fmt, $($arg)*)
61 };
62}
63
64#[macro_export]
66macro_rules! log_debug {
67 ($msg:expr) => {
69 $crate::log!($crate::interface::logging::LogLevel::Debug, $msg)
70 };
71
72 ($msg:expr, { $($key:expr => $value:expr),+ $(,)? }) => {{
74 let mut record = $crate::interface::logging::Record::new(
75 $crate::interface::logging::LogLevel::Debug,
76 module_path!(),
77 $msg,
78 )
79 .with_location(file!(), line!());
80 $(
81 record = record.with_field($key, $value);
82 )+
83 $crate::interface::logging::log(record);
84 }};
85
86 ($fmt:expr, $($arg:tt)*) => {
88 $crate::log!($crate::interface::logging::LogLevel::Debug, $fmt, $($arg)*)
89 };
90}
91
92#[macro_export]
94macro_rules! log_info {
95 ($msg:expr) => {
97 $crate::log!($crate::interface::logging::LogLevel::Info, $msg)
98 };
99
100 ($msg:expr, { $($key:expr => $value:expr),+ $(,)? }) => {{
102 let mut record = $crate::interface::logging::Record::new(
103 $crate::interface::logging::LogLevel::Info,
104 module_path!(),
105 $msg,
106 )
107 .with_location(file!(), line!());
108 $(
109 record = record.with_field($key, $value);
110 )+
111 $crate::interface::logging::log(record);
112 }};
113
114 ($fmt:expr, $($arg:tt)*) => {
116 $crate::log!($crate::interface::logging::LogLevel::Info, $fmt, $($arg)*)
117 };
118}
119
120#[macro_export]
122macro_rules! log_warn {
123 ($msg:expr) => {
125 $crate::log!($crate::interface::logging::LogLevel::Warn, $msg)
126 };
127
128 ($msg:expr, { $($key:expr => $value:expr),+ $(,)? }) => {{
130 let mut record = $crate::interface::logging::Record::new(
131 $crate::interface::logging::LogLevel::Warn,
132 module_path!(),
133 $msg,
134 )
135 .with_location(file!(), line!());
136 $(
137 record = record.with_field($key, $value);
138 )+
139 $crate::interface::logging::log(record);
140 }};
141
142 ($fmt:expr, $($arg:tt)*) => {
144 $crate::log!($crate::interface::logging::LogLevel::Warn, $fmt, $($arg)*)
145 };
146}
147
148#[macro_export]
150macro_rules! log_error {
151 ($msg:expr) => {
153 $crate::log!($crate::interface::logging::LogLevel::Error, $msg)
154 };
155
156 ($msg:expr, { $($key:expr => $value:expr),+ $(,)? }) => {{
158 let mut record = $crate::interface::logging::Record::new(
159 $crate::interface::logging::LogLevel::Error,
160 module_path!(),
161 $msg,
162 )
163 .with_location(file!(), line!());
164 $(
165 record = record.with_field($key, $value);
166 )+
167 $crate::interface::logging::log(record);
168 }};
169
170 ($fmt:expr, $($arg:tt)*) => {
172 $crate::log!($crate::interface::logging::LogLevel::Error, $fmt, $($arg)*)
173 };
174}
175
176#[macro_export]
178macro_rules! log_critical {
179 ($msg:expr) => {
181 $crate::log!($crate::interface::logging::LogLevel::Critical, $msg)
182 };
183
184 ($msg:expr, { $($key:expr => $value:expr),+ $(,)? }) => {{
186 let mut record = $crate::interface::logging::Record::new(
187 $crate::interface::logging::LogLevel::Critical,
188 module_path!(),
189 $msg,
190 )
191 .with_location(file!(), line!());
192 $(
193 record = record.with_field($key, $value);
194 )+
195 $crate::interface::logging::log(record);
196 }};
197
198 ($fmt:expr, $($arg:tt)*) => {
200 $crate::log!($crate::interface::logging::LogLevel::Critical, $fmt, $($arg)*)
201 };
202}
203
204#[cfg(test)]
205mod tests {
206 use crossbeam_channel::unbounded;
207
208 use super::super::{mock::with_mock_logger, *};
209 use crate::{log, log_critical, log_debug, log_error, log_info, log_trace, log_warn};
210
211 #[derive(Debug)]
212 #[allow(dead_code)]
213 struct TestStruct {
214 value: i32,
215 name: String,
216 }
217
218 #[test]
219 fn test_literal_string() {
220 let (sender, receiver) = unbounded();
221
222 with_mock_logger(sender, || {
223 log_debug!("simple message");
224 });
225
226 let record = receiver.try_recv().unwrap();
227 assert_eq!(record.message, "simple message");
228 assert_eq!(record.level, LogLevel::Debug);
229 }
230
231 #[test]
232 fn test_inline_variable_syntax() {
233 let (sender, receiver) = unbounded();
234
235 with_mock_logger(sender, || {
236 let change = TestStruct {
237 value: 42,
238 name: "test".to_string(),
239 };
240
241 log_debug!("{change:?}");
242 });
243
244 let record = receiver.try_recv().unwrap();
245 assert!(record.message.contains("TestStruct"));
246 assert!(record.message.contains("value: 42"));
247 assert!(record.message.contains("name: \"test\""));
248 }
249
250 #[test]
251 fn test_traditional_format_syntax() {
252 let (sender, receiver) = unbounded();
253
254 with_mock_logger(sender, || {
255 let change = TestStruct {
256 value: 99,
257 name: "traditional".to_string(),
258 };
259
260 log_debug!("{:?}", change);
261 });
262
263 let record = receiver.try_recv().unwrap();
264 assert!(record.message.contains("TestStruct"));
265 assert!(record.message.contains("value: 99"));
266 assert!(record.message.contains("name: \"traditional\""));
267 }
268
269 #[test]
270 fn test_traditional_with_display() {
271 let (sender, receiver) = unbounded();
272
273 with_mock_logger(sender, || {
274 let x = 123;
275 log_info!("Value: {}", x);
276 });
277
278 let record = receiver.try_recv().unwrap();
279 assert_eq!(record.message, "Value: 123");
280 assert_eq!(record.level, LogLevel::Info);
281 }
282
283 #[test]
284 fn test_multiple_inline_variables() {
285 let (sender, receiver) = unbounded();
286
287 with_mock_logger(sender, || {
288 let name = "Alice";
289 let age = 30;
290 let city = "New York";
291
292 log_info!("{name} is {age} years old and lives in {city}");
293 });
294
295 let record = receiver.try_recv().unwrap();
296 assert_eq!(record.message, "Alice is 30 years old and lives in New York");
297 }
298
299 #[test]
300 fn test_mixed_formatting() {
301 let (sender, receiver) = unbounded();
302
303 with_mock_logger(sender, || {
304 let count = 5;
305 let items = vec!["apple", "banana", "orange"];
306
307 log_warn!("Found {count} items: {items:?}");
308 });
309
310 let record = receiver.try_recv().unwrap();
311 assert!(record.message.contains("Found 5 items"));
312 assert!(record.message.contains("[\"apple\", \"banana\", \"orange\"]"));
313 assert_eq!(record.level, LogLevel::Warn);
314 }
315
316 #[test]
317 fn test_all_log_levels_with_inline_syntax() {
318 let (sender, receiver) = unbounded();
319
320 with_mock_logger(sender, || {
321 let value = 42;
322
323 log_trace!("Trace: {value}");
324 log_debug!("Debug: {value}");
325 log_info!("Info: {value}");
326 log_warn!("Warn: {value}");
327 log_error!("Error: {value}");
328 log_critical!("Critical: {value}");
329 });
330
331 let mut logs = Vec::new();
333 while let Ok(record) = receiver.try_recv() {
334 logs.push(record);
335 }
336
337 assert_eq!(logs.len(), 6);
338
339 assert_eq!(logs[0].message, "Trace: 42");
340 assert_eq!(logs[0].level, LogLevel::Trace);
341
342 assert_eq!(logs[1].message, "Debug: 42");
343 assert_eq!(logs[1].level, LogLevel::Debug);
344
345 assert_eq!(logs[2].message, "Info: 42");
346 assert_eq!(logs[2].level, LogLevel::Info);
347
348 assert_eq!(logs[3].message, "Warn: 42");
349 assert_eq!(logs[3].level, LogLevel::Warn);
350
351 assert_eq!(logs[4].message, "Error: 42");
352 assert_eq!(logs[4].level, LogLevel::Error);
353
354 assert_eq!(logs[5].message, "Critical: 42");
355 assert_eq!(logs[5].level, LogLevel::Critical);
356 }
357
358 #[test]
359 fn test_comptokenize_inline_expressions() {
360 let (sender, receiver) = unbounded();
361
362 with_mock_logger(sender, || {
363 let numbers = vec![1, 2, 3, 4, 5];
364
365 log_info!("Sum: {}", numbers.iter().sum::<i32>());
366 log_info!("Sum inline: {sum}", sum = numbers.iter().sum::<i32>());
367 });
368
369 let log1 = receiver.try_recv().unwrap();
370 let log2 = receiver.try_recv().unwrap();
371
372 assert_eq!(log1.message, "Sum: 15");
373 assert_eq!(log2.message, "Sum inline: 15");
374 }
375
376 #[test]
377 fn test_escaped_braces() {
378 let (sender, receiver) = unbounded();
379
380 with_mock_logger(sender, || {
381 let value = 10;
382 log_debug!("The value {{in braces}} is {value}");
383 });
384
385 let record = receiver.try_recv().unwrap();
386 assert_eq!(record.message, "The value {in braces} is 10");
387 }
388
389 #[test]
390 fn test_empty_format_string() {
391 let (sender, receiver) = unbounded();
392
393 with_mock_logger(sender, || {
394 log_info!("");
395 });
396
397 let record = receiver.try_recv().unwrap();
398 assert_eq!(record.message, "");
399 }
400
401 #[test]
402 fn test_format_specifiers() {
403 let (sender, receiver) = unbounded();
404
405 with_mock_logger(sender, || {
406 let pi = 3.1456789;
407 let hex = 255;
408
409 log_debug!("{:.2}", pi);
411 log_debug!("{:x}", hex);
412
413 log_debug!("{pi:.2}");
415 log_debug!("{hex:x}");
416 });
417
418 let log1 = receiver.try_recv().unwrap();
419 let log2 = receiver.try_recv().unwrap();
420 let log3 = receiver.try_recv().unwrap();
421 let log4 = receiver.try_recv().unwrap();
422
423 assert_eq!(log1.message, "3.15");
424 assert_eq!(log2.message, "ff");
425 assert_eq!(log3.message, "3.15");
426 assert_eq!(log4.message, "ff");
427 }
428
429 #[test]
430 fn test_multiline_strings() {
431 let (sender, receiver) = unbounded();
432
433 with_mock_logger(sender, || {
434 let error = "Connection failed";
435 log_error!("Error occurred:\n{error}\nPlease retry");
436 });
437
438 let record = receiver.try_recv().unwrap();
439 assert_eq!(record.message, "Error occurred:\nConnection failed\nPlease retry");
440 }
441
442 #[test]
443 fn test_raw_log_macro() {
444 let (sender, receiver) = unbounded();
445
446 with_mock_logger(sender, || {
447 let custom_value = "custom";
448
449 log!(LogLevel::Info, "Raw log message");
451 log!(LogLevel::Warn, "Raw with value: {}", 123);
452 log!(LogLevel::Error, "Raw inline: {custom_value}");
453 });
454
455 let log1 = receiver.try_recv().unwrap();
456 let log2 = receiver.try_recv().unwrap();
457 let log3 = receiver.try_recv().unwrap();
458
459 assert_eq!(log1.message, "Raw log message");
460 assert_eq!(log1.level, LogLevel::Info);
461
462 assert_eq!(log2.message, "Raw with value: 123");
463 assert_eq!(log2.level, LogLevel::Warn);
464
465 assert_eq!(log3.message, "Raw inline: custom");
466 assert_eq!(log3.level, LogLevel::Error);
467 }
468
469 #[test]
470 fn test_logging_macros_compile() {
471 log_trace!("This is a trace message");
477 log_debug!("This is a debug message");
478 log_info!("This is an info message");
479 log_warn!("This is a warning message");
480 log_error!("This is an error message");
481 log_critical!("This is a critical message");
482
483 let value = 42;
485 log_info!("Value is: {}", value);
486 log_debug!("Debug value: {} and string: {}", value, "test");
487
488 log_info!("User logged in", {
491 "user_id" => 123i32,
492 "username" => "alice",
493 "active" => true
494 });
495
496 log_debug!("Processing request", {
497 "request_id" => "abc-123",
498 "method" => "GET",
499 "path" => "/api/users",
500 "status_code" => 200u16
501 });
502
503 log_error!("Database connection failed", {
504 "error_code" => 500i32,
505 "retry_count" => 3u8,
506 "message" => "Connection timeout"
507 });
508
509 log_warn!("Performance warning", {
511 "duration_ms" => 1500i64,
512 "threshold_ms" => 1000i64,
513 "exceeded_by" => 500i64
514 });
515
516 log_debug!("Numeric keys test", {
518 1 => "first",
519 2 => "second",
520 3 => "third"
521 });
522
523 log_info!("Feature flags", {
525 "feature_a" => true,
526 "feature_b" => false,
527 "feature_c" => true
528 });
529
530 let optional_value: Option<i32> = Some(42);
532 let none_value: Option<&str> = None;
533 log_debug!("Optional values", {
534 "some_value" => optional_value,
535 "none_value" => none_value
536 });
537 }
538
539 #[test]
540 fn test_logging_with_different_types() {
541 log_info!("Integer types", {
546 "i8" => 127i8,
547 "i16" => 32767i16,
548 "i32" => 2147483647i32,
549 "i64" => 9223372036854775807i64,
550 "u8" => 255u8,
551 "u16" => 65535u16,
552 "u32" => 4294967295u32,
553 "u64" => 18446744073709551615u64
554 });
555
556 let owned_string = String::from("owned");
558 log_debug!("String types", {
559 "literal" => "string literal",
560 "owned" => owned_string.clone(),
561 "reference" => owned_string.as_str()
562 });
563
564 log_trace!("Mixed key types", {
566 "string_key" => "value1",
567 123 => "numeric_key",
568 true => "boolean_key"
569 });
570 }
571
572 #[test]
573 fn test_format_with_fields() {
574 let user = "alice";
575 let attempts = 3;
576
577 log_error!("Login failed", {
581 "user" => user,
582 "attempts" => attempts,
583 "max_attempts" => 5
584 });
585 }
586}