nautilus_common/logging/
macros.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Posei Systems Pty Ltd. All rights reserved.
3//  https://poseitrader.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Colored logging macros for enhanced log output with automatic color mapping.
17
18/// Logs a trace message with automatic color mapping or custom color and component.
19///
20/// # Usage
21/// ```rust
22/// // Automatic color (normal)
23/// log_trace!("Processing tick data");
24///
25/// // Custom color
26/// log_trace!("Processing tick data", color = LogColor::Cyan);
27///
28/// // Custom component
29/// log_trace!("Processing data", component = "DataEngine");
30///
31/// // Both color and component (flexible order)
32/// log_trace!("Data processed", color = LogColor::Cyan, component = "DataEngine");
33/// log_trace!("Data processed", component = "DataEngine", color = LogColor::Cyan);
34/// ```
35#[macro_export]
36macro_rules! log_trace {
37    // Component only
38    ($msg:literal, component = $component:expr) => {
39        log::trace!(component = $component; $msg);
40    };
41    ($fmt:literal, $($args:expr),+, component = $component:expr) => {
42        log::trace!(component = $component; $fmt, $($args),+);
43    };
44
45    // Color only
46    ($msg:literal, color = $color:expr) => {
47        log::trace!(color = $color as u8; $msg);
48    };
49    ($fmt:literal, $($args:expr),+, color = $color:expr) => {
50        log::trace!(color = $color as u8; $fmt, $($args),+);
51    };
52
53    // Both color and component (color first)
54    ($msg:literal, color = $color:expr, component = $component:expr) => {
55        log::trace!(component = $component, color = $color as u8; $msg);
56    };
57    ($fmt:literal, $($args:expr),+, color = $color:expr, component = $component:expr) => {
58        log::trace!(component = $component, color = $color as u8; $fmt, $($args),+);
59    };
60
61    // Both color and component (component first)
62    ($msg:literal, component = $component:expr, color = $color:expr) => {
63        log::trace!(component = $component, color = $color as u8; $msg);
64    };
65    ($fmt:literal, $($args:expr),+, component = $component:expr, color = $color:expr) => {
66        log::trace!(component = $component, color = $color as u8; $fmt, $($args),+);
67    };
68
69    // Default (no color or component)
70    ($msg:literal) => {
71        log::trace!(color = $crate::enums::LogColor::Normal as u8; $msg);
72    };
73    ($fmt:literal, $($args:expr),+) => {
74        log::trace!(color = $crate::enums::LogColor::Normal as u8; $fmt, $($args),+);
75    };
76}
77
78/// Logs a debug message with automatic color mapping or custom color and component.
79///
80/// # Usage
81/// ```rust
82/// // Automatic color (normal)
83/// log_debug!("Validating order: {}", order_id);
84///
85/// // Custom color
86/// log_debug!("Validating order: {}", order_id, color = LogColor::Blue);
87///
88/// // Custom component
89/// log_debug!("Validating order", component = "RiskEngine");
90///
91/// // Both color and component (flexible order)
92/// log_debug!("Order validated", color = LogColor::Blue, component = "RiskEngine");
93/// log_debug!("Order validated", component = "RiskEngine", color = LogColor::Blue);
94/// ```
95#[macro_export]
96macro_rules! log_debug {
97    // Component only
98    ($msg:literal, component = $component:expr) => {
99        log::debug!(component = $component; $msg);
100    };
101    ($fmt:literal, $($args:expr),+, component = $component:expr) => {
102        log::debug!(component = $component; $fmt, $($args),+);
103    };
104
105    // Color only
106    ($msg:literal, color = $color:expr) => {
107        log::debug!(color = $color as u8; $msg);
108    };
109    ($fmt:literal, $($args:expr),+, color = $color:expr) => {
110        log::debug!(color = $color as u8; $fmt, $($args),+);
111    };
112
113    // Both color and component (color first)
114    ($msg:literal, color = $color:expr, component = $component:expr) => {
115        log::debug!(component = $component, color = $color as u8; $msg);
116    };
117    ($fmt:literal, $($args:expr),+, color = $color:expr, component = $component:expr) => {
118        log::debug!(component = $component, color = $color as u8; $fmt, $($args),+);
119    };
120
121    // Both color and component (component first)
122    ($msg:literal, component = $component:expr, color = $color:expr) => {
123        log::debug!(component = $component, color = $color as u8; $msg);
124    };
125    ($fmt:literal, $($args:expr),+, component = $component:expr, color = $color:expr) => {
126        log::debug!(component = $component, color = $color as u8; $fmt, $($args),+);
127    };
128
129    // Default (no color or component)
130    ($msg:literal) => {
131        log::debug!(color = $crate::enums::LogColor::Normal as u8; $msg);
132    };
133    ($fmt:literal, $($args:expr),+) => {
134        log::debug!(color = $crate::enums::LogColor::Normal as u8; $fmt, $($args),+);
135    };
136}
137
138/// Logs an info message with automatic color mapping or custom color and component.
139///
140/// # Usage
141/// ```rust
142/// // Automatic color (normal)
143/// log_info!("Order {} filled successfully", order_id);
144///
145/// // Custom color (e.g., green for success)
146/// log_info!("Order {} filled successfully", order_id, color = LogColor::Green);
147///
148/// // Custom component
149/// log_info!("Processing order", component = "OrderManager");
150///
151/// // Both color and component (flexible order)
152/// log_info!("Order filled", color = LogColor::Green, component = "OrderManager");
153/// log_info!("Order filled", component = "OrderManager", color = LogColor::Green);
154/// ```
155#[macro_export]
156macro_rules! log_info {
157    // Both color and component (color first)
158    ($msg:literal, color = $color:expr, component = $component:expr) => {
159        log::info!(component = $component, color = $color as u8; $msg);
160    };
161    ($fmt:literal, $arg1:expr, color = $color:expr, component = $component:expr) => {
162        log::info!(component = $component, color = $color as u8; $fmt, $arg1);
163    };
164    ($fmt:literal, $arg1:expr, $arg2:expr, color = $color:expr, component = $component:expr) => {
165        log::info!(component = $component, color = $color as u8; $fmt, $arg1, $arg2);
166    };
167
168    // Both color and component (component first)
169    ($msg:literal, component = $component:expr, color = $color:expr) => {
170        log::info!(component = $component, color = $color as u8; $msg);
171    };
172    ($fmt:literal, $arg1:expr, component = $component:expr, color = $color:expr) => {
173        log::info!(component = $component, color = $color as u8; $fmt, $arg1);
174    };
175    ($fmt:literal, $arg1:expr, $arg2:expr, component = $component:expr, color = $color:expr) => {
176        log::info!(component = $component, color = $color as u8; $fmt, $arg1, $arg2);
177    };
178
179    // Component only
180    ($msg:literal, component = $component:expr) => {
181        log::info!(component = $component; $msg);
182    };
183    ($fmt:literal, $arg1:expr, component = $component:expr) => {
184        log::info!(component = $component; $fmt, $arg1);
185    };
186    ($fmt:literal, $arg1:expr, $arg2:expr, component = $component:expr) => {
187        log::info!(component = $component; $fmt, $arg1, $arg2);
188    };
189
190    // Color only
191    ($msg:literal, color = $color:expr) => {
192        log::info!(color = $color as u8; $msg);
193    };
194    ($fmt:literal, $arg1:expr, color = $color:expr) => {
195        log::info!(color = $color as u8; $fmt, $arg1);
196    };
197    ($fmt:literal, $arg1:expr, $arg2:expr, color = $color:expr) => {
198        log::info!(color = $color as u8; $fmt, $arg1, $arg2);
199    };
200    ($fmt:literal, $arg1:expr, $arg2:expr, $arg3:expr, color = $color:expr) => {
201        log::info!(color = $color as u8; $fmt, $arg1, $arg2, $arg3);
202    };
203
204    // Default (no color or component)
205    ($msg:literal) => {
206        log::info!(color = $crate::enums::LogColor::Normal as u8; $msg);
207    };
208    ($fmt:literal, $($args:expr),+) => {
209        log::info!(color = $crate::enums::LogColor::Normal as u8; $fmt, $($args),+);
210    };
211}
212
213/// Logs a warning message with automatic yellow color or custom color and component.
214///
215/// # Usage
216/// ```rust
217/// // Automatic color (yellow)
218/// log_warn!("Position size approaching limit");
219///
220/// // Custom color
221/// log_warn!("Custom warning message", color = LogColor::Magenta);
222///
223/// // Custom component
224/// log_warn!("Risk limit exceeded", component = "RiskEngine");
225///
226/// // Both color and component (flexible order)
227/// log_warn!("Warning message", color = LogColor::Magenta, component = "RiskEngine");
228/// log_warn!("Warning message", component = "RiskEngine", color = LogColor::Magenta);
229/// ```
230#[macro_export]
231macro_rules! log_warn {
232    // Both color and component (color first)
233    ($msg:literal, color = $color:expr, component = $component:expr) => {
234        log::warn!(component = $component, color = $color as u8; $msg);
235    };
236    ($fmt:literal, $arg1:expr, color = $color:expr, component = $component:expr) => {
237        log::warn!(component = $component, color = $color as u8; $fmt, $arg1);
238    };
239    ($fmt:literal, $arg1:expr, $arg2:expr, color = $color:expr, component = $component:expr) => {
240        log::warn!(component = $component, color = $color as u8; $fmt, $arg1, $arg2);
241    };
242
243    // Both color and component (component first)
244    ($msg:literal, component = $component:expr, color = $color:expr) => {
245        log::warn!(component = $component, color = $color as u8; $msg);
246    };
247    ($fmt:literal, $arg1:expr, component = $component:expr, color = $color:expr) => {
248        log::warn!(component = $component, color = $color as u8; $fmt, $arg1);
249    };
250    ($fmt:literal, $arg1:expr, $arg2:expr, component = $component:expr, color = $color:expr) => {
251        log::warn!(component = $component, color = $color as u8; $fmt, $arg1, $arg2);
252    };
253
254    // Component only
255    ($msg:literal, component = $component:expr) => {
256        log::warn!(component = $component, color = $crate::enums::LogColor::Yellow as u8; $msg);
257    };
258    ($fmt:literal, $arg1:expr, component = $component:expr) => {
259        log::warn!(component = $component, color = $crate::enums::LogColor::Yellow as u8; $fmt, $arg1);
260    };
261    ($fmt:literal, $arg1:expr, $arg2:expr, component = $component:expr) => {
262        log::warn!(component = $component, color = $crate::enums::LogColor::Yellow as u8; $fmt, $arg1, $arg2);
263    };
264
265    // Color only
266    ($msg:literal, color = $color:expr) => {
267        log::warn!(color = $color as u8; $msg);
268    };
269    ($fmt:literal, $arg1:expr, color = $color:expr) => {
270        log::warn!(color = $color as u8; $fmt, $arg1);
271    };
272    ($fmt:literal, $arg1:expr, $arg2:expr, color = $color:expr) => {
273        log::warn!(color = $color as u8; $fmt, $arg1, $arg2);
274    };
275    ($fmt:literal, $arg1:expr, $arg2:expr, $arg3:expr, color = $color:expr) => {
276        log::warn!(color = $color as u8; $fmt, $arg1, $arg2, $arg3);
277    };
278
279    // Default (automatic yellow color, no component)
280    ($msg:literal) => {
281        log::warn!(color = $crate::enums::LogColor::Yellow as u8; $msg);
282    };
283    ($fmt:literal, $($args:expr),+) => {
284        log::warn!(color = $crate::enums::LogColor::Yellow as u8; $fmt, $($args),+);
285    };
286}
287
288/// Logs an error message with automatic red color or custom color and component.
289///
290/// # Usage
291/// ```rust
292/// // Automatic color (red)
293/// log_error!("Failed to connect to exchange: {}", error);
294///
295/// // Custom color
296/// log_error!("Custom error message", color = LogColor::Magenta);
297///
298/// // Custom component
299/// log_error!("Connection failed", component = "DataEngine");
300///
301/// // Both color and component (flexible order)
302/// log_error!("Critical error", color = LogColor::Magenta, component = "DataEngine");
303/// log_error!("Critical error", component = "DataEngine", color = LogColor::Magenta);
304/// ```
305#[macro_export]
306macro_rules! log_error {
307    // Both color and component (color first)
308    ($msg:literal, color = $color:expr, component = $component:expr) => {
309        log::error!(component = $component, color = $color as u8; $msg);
310    };
311    ($fmt:literal, $arg1:expr, color = $color:expr, component = $component:expr) => {
312        log::error!(component = $component, color = $color as u8; $fmt, $arg1);
313    };
314    ($fmt:literal, $arg1:expr, $arg2:expr, color = $color:expr, component = $component:expr) => {
315        log::error!(component = $component, color = $color as u8; $fmt, $arg1, $arg2);
316    };
317
318    // Both color and component (component first)
319    ($msg:literal, component = $component:expr, color = $color:expr) => {
320        log::error!(component = $component, color = $color as u8; $msg);
321    };
322    ($fmt:literal, $arg1:expr, component = $component:expr, color = $color:expr) => {
323        log::error!(component = $component, color = $color as u8; $fmt, $arg1);
324    };
325    ($fmt:literal, $arg1:expr, $arg2:expr, component = $component:expr, color = $color:expr) => {
326        log::error!(component = $component, color = $color as u8; $fmt, $arg1, $arg2);
327    };
328
329    // Component only
330    ($msg:literal, component = $component:expr) => {
331        log::error!(component = $component, color = $crate::enums::LogColor::Red as u8; $msg);
332    };
333    ($fmt:literal, $arg1:expr, component = $component:expr) => {
334        log::error!(component = $component, color = $crate::enums::LogColor::Red as u8; $fmt, $arg1);
335    };
336    ($fmt:literal, $arg1:expr, $arg2:expr, component = $component:expr) => {
337        log::error!(component = $component, color = $crate::enums::LogColor::Red as u8; $fmt, $arg1, $arg2);
338    };
339
340    // Color only
341    ($msg:literal, color = $color:expr) => {
342        log::error!(color = $color as u8; $msg);
343    };
344    ($fmt:literal, $arg1:expr, color = $color:expr) => {
345        log::error!(color = $color as u8; $fmt, $arg1);
346    };
347    ($fmt:literal, $arg1:expr, $arg2:expr, color = $color:expr) => {
348        log::error!(color = $color as u8; $fmt, $arg1, $arg2);
349    };
350    ($fmt:literal, $arg1:expr, $arg2:expr, $arg3:expr, color = $color:expr) => {
351        log::error!(color = $color as u8; $fmt, $arg1, $arg2, $arg3);
352    };
353
354    // Default (automatic red color, no component)
355    ($msg:literal) => {
356        log::error!(color = $crate::enums::LogColor::Red as u8; $msg);
357    };
358    ($fmt:literal, $($args:expr),+) => {
359        log::error!(color = $crate::enums::LogColor::Red as u8; $fmt, $($args),+);
360    };
361}
362
363// Re-exports
364pub use log_debug;
365pub use log_error;
366pub use log_info;
367pub use log_trace;
368pub use log_warn;
369
370////////////////////////////////////////////////////////////////////////////////
371// Tests
372////////////////////////////////////////////////////////////////////////////////
373#[cfg(test)]
374mod tests {
375    use std::{thread::sleep, time::Duration};
376
377    use nautilus_core::UUID4;
378    use nautilus_model::identifiers::TraderId;
379    use rstest::*;
380    use tempfile::tempdir;
381
382    use crate::{
383        enums::LogColor,
384        logging::{
385            logger::{Logger, LoggerConfig},
386            logging_clock_set_static_mode, logging_clock_set_static_time,
387            writer::FileWriterConfig,
388        },
389        testing::wait_until,
390    };
391
392    #[rstest]
393    fn test_colored_logging_macros() {
394        let config = LoggerConfig::from_spec("stdout=Trace;fileout=Trace;is_colored").unwrap();
395
396        let temp_dir = tempdir().expect("Failed to create temporary directory");
397        let file_config = FileWriterConfig {
398            directory: Some(temp_dir.path().to_str().unwrap().to_string()),
399            ..Default::default()
400        };
401
402        let log_guard = Logger::init_with_config(
403            TraderId::from("TRADER-001"),
404            UUID4::new(),
405            config,
406            file_config,
407        )
408        .expect("Failed to initialize logger");
409
410        logging_clock_set_static_mode();
411        logging_clock_set_static_time(1_650_000_000_000_000);
412
413        // Test automatic color mappings using explicit components to ensure they're written
414        log_trace!("This is a trace message", component = "TestComponent");
415        log_debug!("This is a debug message", component = "TestComponent");
416        log_info!("This is an info message", component = "TestComponent");
417        log_warn!("This is a warning message", component = "TestComponent");
418        log_error!("This is an error message", component = "TestComponent");
419
420        // Test custom colors
421        log_info!(
422            "Success message",
423            color = LogColor::Green,
424            component = "TestComponent"
425        );
426        log_info!(
427            "Information message",
428            color = LogColor::Blue,
429            component = "TestComponent"
430        );
431        log_warn!(
432            "Custom warning",
433            component = "TestComponent",
434            color = LogColor::Magenta
435        );
436
437        // Test component only
438        log_info!("Component test", component = "TestComponent");
439        log_warn!("Component warning", component = "TestComponent");
440
441        // Test both color and component (different orders)
442        log_info!(
443            "Color then component",
444            color = LogColor::Cyan,
445            component = "TestComponent"
446        );
447
448        // Allow time for logs to be written
449        sleep(Duration::from_millis(100));
450
451        drop(log_guard);
452
453        // Wait until log file exists and has contents
454        let mut log_contents = String::new();
455        wait_until(
456            || {
457                if let Some(log_file) = std::fs::read_dir(&temp_dir)
458                    .expect("Failed to read directory")
459                    .filter_map(Result::ok)
460                    .find(|entry| entry.path().is_file())
461                {
462                    let log_file_path = log_file.path();
463                    log_contents =
464                        std::fs::read_to_string(log_file_path).expect("Failed to read log file");
465                    !log_contents.is_empty()
466                } else {
467                    false
468                }
469            },
470            Duration::from_secs(3),
471        );
472
473        // Debug: print file contents if test is failing
474        if !log_contents.contains("This is a trace message") {
475            println!("File contents:\n{}", log_contents);
476        }
477
478        // Verify that all log levels are present
479        assert!(log_contents.contains("This is a trace message"));
480        assert!(log_contents.contains("This is a debug message"));
481        assert!(log_contents.contains("This is an info message"));
482        assert!(log_contents.contains("This is a warning message"));
483        assert!(log_contents.contains("This is an error message"));
484        assert!(log_contents.contains("Success message"));
485        assert!(log_contents.contains("Information message"));
486        assert!(log_contents.contains("Custom warning"));
487
488        // Verify component and color combinations
489        assert!(log_contents.contains("Component test"));
490        assert!(log_contents.contains("Component warning"));
491        assert!(log_contents.contains("Color then component"));
492    }
493}