reifydb_core/interface/logging/
timed.rs

1// Copyright (c) reifydb.com 2025
2// This file is licensed under the AGPL-3.0-or-later, see license.md file
3
4//! Timed logging macros that automatically measure and append execution time
5
6/// Format a duration in nanoseconds for human-readable display
7pub fn format_duration_nanos(nanos: u128) -> String {
8	if nanos < 1_000 {
9		format!("{}ns", nanos)
10	} else if nanos < 1_000_000 {
11		format!("{:.2}μs", nanos as f64 / 1_000.0)
12	} else if nanos < 1_000_000_000 {
13		format!("{:.2}ms", nanos as f64 / 1_000_000.0)
14	} else {
15		format!("{:.2}s", nanos as f64 / 1_000_000_000.0)
16	}
17}
18
19/// Main timed logging macro with support for all format patterns
20#[macro_export]
21macro_rules! log_timed {
22    // Simple message with code block
23    ($level:expr, $msg:expr, $code:block) => {{
24        let __start = std::time::Instant::now();
25        let __result = $code;
26        let __elapsed = __start.elapsed().as_nanos();
27        let __duration_str = $crate::interface::logging::timed::format_duration_nanos(__elapsed);
28        let __message = format!("{} (took {})", format!($msg), __duration_str);
29
30        let __record = $crate::interface::logging::Record::new(
31            $level,
32            module_path!(),
33            __message,
34        )
35        .with_location(file!(), line!());
36        $crate::interface::logging::log(__record);
37        __result
38    }};
39
40    // Format string with arguments and code block
41    ($level:expr, $fmt:expr, $($arg:tt)*; $code:block) => {{
42        let __start = std::time::Instant::now();
43        let __result = $code;
44        let __elapsed = __start.elapsed().as_nanos();
45        let __duration_str = $crate::interface::logging::timed::format_duration_nanos(__elapsed);
46        let __message = format!("{} (took {})", format!($fmt, $($arg)*), __duration_str);
47
48        let __record = $crate::interface::logging::Record::new(
49            $level,
50            module_path!(),
51            __message,
52        )
53        .with_location(file!(), line!());
54        $crate::interface::logging::log(__record);
55        __result
56    }};
57}
58
59/// Trace level timed logging
60#[macro_export]
61macro_rules! log_timed_trace {
62    // Simple message
63    ($msg:expr, $code:block) => {
64        $crate::log_timed!($crate::interface::logging::LogLevel::Trace, $msg, $code)
65    };
66
67    // Format string with arguments - note the semicolon separator before the code block
68    ($fmt:expr, $($arg:tt)*; $code:block) => {
69        $crate::log_timed!($crate::interface::logging::LogLevel::Trace, $fmt, $($arg)*; $code)
70    };
71}
72
73/// Debug level timed logging
74#[macro_export]
75macro_rules! log_timed_debug {
76    // Simple message
77    ($msg:expr, $code:block) => {
78        $crate::log_timed!($crate::interface::logging::LogLevel::Debug, $msg, $code)
79    };
80
81    // Format string with arguments - note the semicolon separator before the code block
82    ($fmt:expr, $($arg:tt)*; $code:block) => {
83        $crate::log_timed!($crate::interface::logging::LogLevel::Debug, $fmt, $($arg)*; $code)
84    };
85}
86
87/// Info level timed logging
88#[macro_export]
89macro_rules! log_timed_info {
90    // Simple message
91    ($msg:expr, $code:block) => {
92        $crate::log_timed!($crate::interface::logging::LogLevel::Info, $msg, $code)
93    };
94
95    // Format string with arguments - note the semicolon separator before the code block
96    ($fmt:expr, $($arg:tt)*; $code:block) => {
97        $crate::log_timed!($crate::interface::logging::LogLevel::Info, $fmt, $($arg)*; $code)
98    };
99}
100
101/// Warning level timed logging
102#[macro_export]
103macro_rules! log_timed_warn {
104    // Simple message
105    ($msg:expr, $code:block) => {
106        $crate::log_timed!($crate::interface::logging::LogLevel::Warn, $msg, $code)
107    };
108
109    // Format string with arguments - note the semicolon separator before the code block
110    ($fmt:expr, $($arg:tt)*; $code:block) => {
111        $crate::log_timed!($crate::interface::logging::LogLevel::Warn, $fmt, $($arg)*; $code)
112    };
113}
114
115/// Error level timed logging
116#[macro_export]
117macro_rules! log_timed_error {
118    // Simple message
119    ($msg:expr, $code:block) => {
120        $crate::log_timed!($crate::interface::logging::LogLevel::Error, $msg, $code)
121    };
122
123    // Format string with arguments - note the semicolon separator before the code block
124    ($fmt:expr, $($arg:tt)*; $code:block) => {
125        $crate::log_timed!($crate::interface::logging::LogLevel::Error, $fmt, $($arg)*; $code)
126    };
127}
128
129/// Critical level timed logging
130#[macro_export]
131macro_rules! log_timed_critical {
132    // Simple message
133    ($msg:expr, $code:block) => {
134        $crate::log_timed!($crate::interface::logging::LogLevel::Critical, $msg, $code)
135    };
136
137    // Format string with arguments - note the semicolon separator before the code block
138    ($fmt:expr, $($arg:tt)*; $code:block) => {
139        $crate::log_timed!($crate::interface::logging::LogLevel::Critical, $fmt, $($arg)*; $code)
140    };
141}
142
143#[cfg(test)]
144mod tests {
145	use crossbeam_channel::unbounded;
146
147	use super::*;
148	use crate::{
149		interface::logging::{LogLevel, mock::with_mock_logger},
150		log_timed_debug, log_timed_info, log_timed_trace,
151	};
152
153	#[test]
154	fn test_format_duration_nanos() {
155		assert_eq!(format_duration_nanos(500), "500ns");
156		assert_eq!(format_duration_nanos(1_500), "1.50μs");
157		assert_eq!(format_duration_nanos(1_500_000), "1.50ms");
158		assert_eq!(format_duration_nanos(1_500_000_000), "1.50s");
159	}
160
161	#[test]
162	fn test_simple_timed_log() {
163		let (sender, receiver) = unbounded();
164
165		with_mock_logger(sender, || {
166			let result = log_timed_debug!("Test operation", {
167				std::thread::sleep(std::time::Duration::from_millis(10));
168				42
169			});
170			assert_eq!(result, 42);
171		});
172
173		let record = receiver.try_recv().unwrap();
174		assert_eq!(record.level, LogLevel::Debug);
175		assert!(record.message.starts_with("Test operation (took "));
176		assert!(record.message.ends_with(")"));
177	}
178
179	#[test]
180	fn test_timed_log_with_inline_variables() {
181		let (sender, receiver) = unbounded();
182
183		with_mock_logger(sender, || {
184			let operation = "database init";
185			let result = log_timed_info!("Performing {operation}", {
186				std::thread::sleep(std::time::Duration::from_millis(5));
187				"success"
188			});
189			assert_eq!(result, "success");
190		});
191
192		let record = receiver.try_recv().unwrap();
193		assert_eq!(record.level, LogLevel::Info);
194		assert!(record.message.starts_with("Performing database init (took "));
195	}
196
197	#[test]
198	fn test_timed_log_returns_result() {
199		let (sender, _receiver) = unbounded();
200
201		with_mock_logger(sender, || {
202			// Test that the macro properly returns the result of
203			// the code block
204			let value = log_timed_debug!("Computing value", { 100 + 200 });
205			assert_eq!(value, 300);
206
207			// Test with more comptokenize return type
208			let vec = log_timed_info!("Creating vector", { vec![1, 2, 3, 4, 5] });
209			assert_eq!(vec.len(), 5);
210		});
211	}
212
213	#[test]
214	fn test_all_timed_log_levels() {
215		let (sender, receiver) = unbounded();
216
217		with_mock_logger(sender, || {
218			log_timed_trace!("Trace operation", { 1 });
219			log_timed_debug!("Debug operation", { 2 });
220			log_timed_info!("Info operation", { 3 });
221			log_timed_warn!("Warn operation", { 4 });
222			log_timed_error!("Error operation", { 5 });
223			log_timed_critical!("Critical operation", { 6 });
224		});
225
226		let logs: Vec<_> = (0..6).map(|_| receiver.try_recv().unwrap()).collect();
227
228		assert_eq!(logs[0].level, LogLevel::Trace);
229		assert!(logs[0].message.starts_with("Trace operation (took "));
230
231		assert_eq!(logs[1].level, LogLevel::Debug);
232		assert!(logs[1].message.starts_with("Debug operation (took "));
233
234		assert_eq!(logs[2].level, LogLevel::Info);
235		assert!(logs[2].message.starts_with("Info operation (took "));
236
237		assert_eq!(logs[3].level, LogLevel::Warn);
238		assert!(logs[3].message.starts_with("Warn operation (took "));
239
240		assert_eq!(logs[4].level, LogLevel::Error);
241		assert!(logs[4].message.starts_with("Error operation (took "));
242
243		assert_eq!(logs[5].level, LogLevel::Critical);
244		assert!(logs[5].message.starts_with("Critical operation (took "));
245	}
246}