Skip to main content

nautilus_common/logging/
macros.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.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 - auto-capture module path)
70    ($msg:literal) => {
71        log::trace!(component = module_path!(), color = $crate::enums::LogColor::Normal as u8; $msg);
72    };
73    ($fmt:literal, $($args:expr),+) => {
74        log::trace!(component = module_path!(), 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 - auto-capture module path)
130    ($msg:literal) => {
131        log::debug!(component = module_path!(), color = $crate::enums::LogColor::Normal as u8; $msg);
132    };
133    ($fmt:literal, $($args:expr),+) => {
134        log::debug!(component = module_path!(), 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 - auto-capture module path)
205    ($msg:literal) => {
206        log::info!(component = module_path!(), color = $crate::enums::LogColor::Normal as u8; $msg);
207    };
208    ($fmt:literal, $($args:expr),+) => {
209        log::info!(component = module_path!(), 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 - auto-capture module path)
280    ($msg:literal) => {
281        log::warn!(component = module_path!(), color = $crate::enums::LogColor::Yellow as u8; $msg);
282    };
283    ($fmt:literal, $($args:expr),+) => {
284        log::warn!(component = module_path!(), 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 - auto-capture module path)
355    ($msg:literal) => {
356        log::error!(component = module_path!(), color = $crate::enums::LogColor::Red as u8; $msg);
357    };
358    ($fmt:literal, $($args:expr),+) => {
359        log::error!(component = module_path!(), 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// Gated out under `cfg(madsim)`: both tests drive the file-logging writer thread,
371// which is itself gated out under simulation (see `Logger::init_with_config`), so log
372// events are dropped and these tests would hang on `wait_until` waiting for a log file
373// that is never written. Logging is outside the determinism contract.
374#[cfg(all(test, not(all(feature = "simulation", madsim))))]
375mod tests {
376    use std::{thread::sleep, time::Duration};
377
378    use nautilus_core::UUID4;
379    use nautilus_model::identifiers::TraderId;
380    use rstest::*;
381    use tempfile::tempdir;
382
383    use crate::{
384        enums::LogColor,
385        logging::{
386            logger::{Logger, LoggerConfig},
387            logging_clock_set_static_mode, logging_clock_set_static_time,
388            writer::FileWriterConfig,
389        },
390        testing::wait_until,
391    };
392
393    #[rstest]
394    fn test_colored_logging_macros() {
395        let config = LoggerConfig::from_spec("stdout=Trace;fileout=Trace;is_colored").unwrap();
396
397        let temp_dir = tempdir().expect("Failed to create temporary directory");
398        let file_config = FileWriterConfig {
399            directory: Some(temp_dir.path().to_str().unwrap().to_string()),
400            ..Default::default()
401        };
402
403        let log_guard = Logger::init_with_config(
404            TraderId::from("TRADER-001"),
405            UUID4::new(),
406            config,
407            file_config,
408        )
409        .expect("Failed to initialize logger");
410
411        logging_clock_set_static_mode();
412        logging_clock_set_static_time(1_650_000_000_000_000);
413
414        // Test automatic color mappings using explicit components to ensure they're written
415        log_trace!("This is a trace message", component = "TestComponent");
416        log_debug!("This is a debug message", component = "TestComponent");
417        log_info!("This is an info message", component = "TestComponent");
418        log_warn!("This is a warning message", component = "TestComponent");
419        log_error!("This is an error message", component = "TestComponent");
420
421        // Test custom colors
422        log_info!(
423            "Success message",
424            color = LogColor::Green,
425            component = "TestComponent"
426        );
427        log_info!(
428            "Information message",
429            color = LogColor::Blue,
430            component = "TestComponent"
431        );
432        log_warn!(
433            "Custom warning",
434            component = "TestComponent",
435            color = LogColor::Magenta
436        );
437
438        // Test component only
439        log_info!("Component test", component = "TestComponent");
440        log_warn!("Component warning", component = "TestComponent");
441
442        // Test both color and component (different orders)
443        log_info!(
444            "Color then component",
445            color = LogColor::Cyan,
446            component = "TestComponent"
447        );
448
449        // Allow time for logs to be written
450        sleep(Duration::from_millis(200));
451
452        drop(log_guard);
453
454        // Wait until log file exists and has contents
455        let mut log_contents = String::new();
456        wait_until(
457            || {
458                if let Some(log_file) = std::fs::read_dir(&temp_dir)
459                    .expect("Failed to read directory")
460                    .filter_map(Result::ok)
461                    .find(|entry| entry.path().is_file())
462                {
463                    let log_file_path = log_file.path();
464                    log_contents =
465                        std::fs::read_to_string(log_file_path).expect("Failed to read log file");
466                    !log_contents.is_empty()
467                } else {
468                    false
469                }
470            },
471            Duration::from_secs(3),
472        );
473
474        // Debug: print file contents if test is failing
475        if !log_contents.contains("This is a trace message") {
476            println!("File contents:\n{log_contents}");
477        }
478
479        // Verify that all log levels are present
480        assert!(log_contents.contains("This is a trace message"));
481        assert!(log_contents.contains("This is a debug message"));
482        assert!(log_contents.contains("This is an info message"));
483        assert!(log_contents.contains("This is a warning message"));
484        assert!(log_contents.contains("This is an error message"));
485        assert!(log_contents.contains("Success message"));
486        assert!(log_contents.contains("Information message"));
487        assert!(log_contents.contains("Custom warning"));
488
489        // Verify component and color combinations
490        assert!(log_contents.contains("Component test"));
491        assert!(log_contents.contains("Component warning"));
492        assert!(log_contents.contains("Color then component"));
493    }
494
495    #[rstest]
496    fn test_default_macro_captures_module_path() {
497        // This test verifies that log macros without explicit component
498        // auto-capture module_path!() as the component.
499        //
500        // The module path for this test is: nautilus_common::logging::macros::tests
501        // We configure a module filter and verify the log is filtered/passed accordingly.
502
503        let config = LoggerConfig::from_spec(
504            "stdout=Off;fileout=Trace;nautilus_common::logging::macros=Debug",
505        )
506        .unwrap();
507
508        let temp_dir = tempdir().expect("Failed to create temporary directory");
509        let file_config = FileWriterConfig {
510            directory: Some(temp_dir.path().to_str().unwrap().to_string()),
511            ..Default::default()
512        };
513
514        let log_guard = Logger::init_with_config(
515            TraderId::from("TRADER-PATH"),
516            UUID4::new(),
517            config,
518            file_config,
519        )
520        .expect("Failed to initialize logger");
521
522        logging_clock_set_static_mode();
523        logging_clock_set_static_time(1_650_000_000_000_000);
524
525        // Call macros WITHOUT explicit component - should auto-capture module_path!()
526        log_info!("Auto-captured module path message");
527        log_debug!("Debug level auto-captured");
528
529        // This trace should be filtered (module filter is Debug, Trace > Debug)
530        log_trace!("Trace should be filtered SHOULD_NOT_APPEAR");
531
532        sleep(Duration::from_millis(200));
533        drop(log_guard);
534
535        let mut log_contents = String::new();
536        wait_until(
537            || {
538                if let Some(log_file) = std::fs::read_dir(&temp_dir)
539                    .expect("Failed to read directory")
540                    .filter_map(Result::ok)
541                    .find(|entry| entry.path().is_file())
542                {
543                    log_contents =
544                        std::fs::read_to_string(log_file.path()).expect("Failed to read log file");
545                    !log_contents.is_empty()
546                } else {
547                    false
548                }
549            },
550            Duration::from_secs(3),
551        );
552
553        assert!(
554            log_contents.contains("nautilus_common::logging::macros"),
555            "Component should contain module path, was:\n{log_contents}"
556        );
557        assert!(
558            log_contents.contains("Auto-captured module path message"),
559            "Info message should pass"
560        );
561        assert!(
562            log_contents.contains("Debug level auto-captured"),
563            "Debug message should pass"
564        );
565        assert!(
566            !log_contents.contains("SHOULD_NOT_APPEAR"),
567            "Trace should be filtered by module filter"
568        );
569    }
570}