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/// use nautilus_common::{enums::LogColor, log_trace};
23///
24/// // Automatic color (normal)
25/// log_trace!("Processing tick data");
26///
27/// // Custom color
28/// log_trace!("Processing tick data", color = LogColor::Cyan);
29///
30/// // Custom component
31/// log_trace!("Processing data", component = "DataEngine");
32///
33/// // Both color and component (flexible order)
34/// log_trace!(
35///     "Data processed",
36///     color = LogColor::Cyan,
37///     component = "DataEngine"
38/// );
39/// log_trace!(
40///     "Data processed",
41///     component = "DataEngine",
42///     color = LogColor::Cyan
43/// );
44/// ```
45#[macro_export]
46macro_rules! log_trace {
47    // Both color and component (color first)
48    ($msg:literal, color = $color:expr, component = $component:expr) => {
49        log::trace!(component = $component, color = $color as u8; $msg);
50    };
51    ($fmt:literal, $arg1:expr, color = $color:expr, component = $component:expr) => {
52        log::trace!(component = $component, color = $color as u8; $fmt, $arg1);
53    };
54    ($fmt:literal, $arg1:expr, $arg2:expr, color = $color:expr, component = $component:expr) => {
55        log::trace!(component = $component, color = $color as u8; $fmt, $arg1, $arg2);
56    };
57
58    // Both color and component (component first)
59    ($msg:literal, component = $component:expr, color = $color:expr) => {
60        log::trace!(component = $component, color = $color as u8; $msg);
61    };
62    ($fmt:literal, $arg1:expr, component = $component:expr, color = $color:expr) => {
63        log::trace!(component = $component, color = $color as u8; $fmt, $arg1);
64    };
65    ($fmt:literal, $arg1:expr, $arg2:expr, component = $component:expr, color = $color:expr) => {
66        log::trace!(component = $component, color = $color as u8; $fmt, $arg1, $arg2);
67    };
68
69    // Component only
70    ($msg:literal, component = $component:expr) => {
71        log::trace!(component = $component; $msg);
72    };
73    ($fmt:literal, $arg1:expr, component = $component:expr) => {
74        log::trace!(component = $component; $fmt, $arg1);
75    };
76    ($fmt:literal, $arg1:expr, $arg2:expr, component = $component:expr) => {
77        log::trace!(component = $component; $fmt, $arg1, $arg2);
78    };
79
80    // Color only
81    ($msg:literal, color = $color:expr) => {
82        log::trace!(color = $color as u8; $msg);
83    };
84    ($fmt:literal, $arg1:expr, color = $color:expr) => {
85        log::trace!(color = $color as u8; $fmt, $arg1);
86    };
87    ($fmt:literal, $arg1:expr, $arg2:expr, color = $color:expr) => {
88        log::trace!(color = $color as u8; $fmt, $arg1, $arg2);
89    };
90    ($fmt:literal, $arg1:expr, $arg2:expr, $arg3:expr, color = $color:expr) => {
91        log::trace!(color = $color as u8; $fmt, $arg1, $arg2, $arg3);
92    };
93
94    // Default (no color or component - auto-capture module path)
95    ($msg:literal) => {
96        log::trace!(component = module_path!(), color = $crate::enums::LogColor::Normal as u8; $msg);
97    };
98    ($fmt:literal, $($args:expr),+) => {
99        log::trace!(component = module_path!(), color = $crate::enums::LogColor::Normal as u8; $fmt, $($args),+);
100    };
101}
102
103/// Logs a debug message with automatic color mapping or custom color and component.
104///
105/// # Usage
106/// ```rust
107/// use nautilus_common::{enums::LogColor, log_debug};
108///
109/// let order_id = "O-19700101-000000-001-001-1";
110///
111/// // Automatic color (normal)
112/// log_debug!("Validating order: {}", order_id);
113///
114/// // Custom color
115/// log_debug!("Validating order: {}", order_id, color = LogColor::Blue);
116///
117/// // Custom component
118/// log_debug!("Validating order", component = "RiskEngine");
119///
120/// // Both color and component (flexible order)
121/// log_debug!(
122///     "Order validated",
123///     color = LogColor::Blue,
124///     component = "RiskEngine"
125/// );
126/// log_debug!(
127///     "Order validated",
128///     component = "RiskEngine",
129///     color = LogColor::Blue
130/// );
131/// ```
132#[macro_export]
133macro_rules! log_debug {
134    // Both color and component (color first)
135    ($msg:literal, color = $color:expr, component = $component:expr) => {
136        log::debug!(component = $component, color = $color as u8; $msg);
137    };
138    ($fmt:literal, $arg1:expr, color = $color:expr, component = $component:expr) => {
139        log::debug!(component = $component, color = $color as u8; $fmt, $arg1);
140    };
141    ($fmt:literal, $arg1:expr, $arg2:expr, color = $color:expr, component = $component:expr) => {
142        log::debug!(component = $component, color = $color as u8; $fmt, $arg1, $arg2);
143    };
144
145    // Both color and component (component first)
146    ($msg:literal, component = $component:expr, color = $color:expr) => {
147        log::debug!(component = $component, color = $color as u8; $msg);
148    };
149    ($fmt:literal, $arg1:expr, component = $component:expr, color = $color:expr) => {
150        log::debug!(component = $component, color = $color as u8; $fmt, $arg1);
151    };
152    ($fmt:literal, $arg1:expr, $arg2:expr, component = $component:expr, color = $color:expr) => {
153        log::debug!(component = $component, color = $color as u8; $fmt, $arg1, $arg2);
154    };
155
156    // Component only
157    ($msg:literal, component = $component:expr) => {
158        log::debug!(component = $component; $msg);
159    };
160    ($fmt:literal, $arg1:expr, component = $component:expr) => {
161        log::debug!(component = $component; $fmt, $arg1);
162    };
163    ($fmt:literal, $arg1:expr, $arg2:expr, component = $component:expr) => {
164        log::debug!(component = $component; $fmt, $arg1, $arg2);
165    };
166
167    // Color only
168    ($msg:literal, color = $color:expr) => {
169        log::debug!(color = $color as u8; $msg);
170    };
171    ($fmt:literal, $arg1:expr, color = $color:expr) => {
172        log::debug!(color = $color as u8; $fmt, $arg1);
173    };
174    ($fmt:literal, $arg1:expr, $arg2:expr, color = $color:expr) => {
175        log::debug!(color = $color as u8; $fmt, $arg1, $arg2);
176    };
177    ($fmt:literal, $arg1:expr, $arg2:expr, $arg3:expr, color = $color:expr) => {
178        log::debug!(color = $color as u8; $fmt, $arg1, $arg2, $arg3);
179    };
180
181    // Default (no color or component - auto-capture module path)
182    ($msg:literal) => {
183        log::debug!(component = module_path!(), color = $crate::enums::LogColor::Normal as u8; $msg);
184    };
185    ($fmt:literal, $($args:expr),+) => {
186        log::debug!(component = module_path!(), color = $crate::enums::LogColor::Normal as u8; $fmt, $($args),+);
187    };
188}
189
190/// Logs an info message with automatic color mapping or custom color and component.
191///
192/// # Usage
193/// ```rust
194/// use nautilus_common::{enums::LogColor, log_info};
195///
196/// let order_id = "O-19700101-000000-001-001-1";
197///
198/// // Automatic color (normal)
199/// log_info!("Order {} filled successfully", order_id);
200///
201/// // Custom color (e.g., green for success)
202/// log_info!(
203///     "Order {} filled successfully",
204///     order_id,
205///     color = LogColor::Green
206/// );
207///
208/// // Custom component
209/// log_info!("Processing order", component = "OrderManager");
210///
211/// // Both color and component (flexible order)
212/// log_info!(
213///     "Order filled",
214///     color = LogColor::Green,
215///     component = "OrderManager"
216/// );
217/// log_info!(
218///     "Order filled",
219///     component = "OrderManager",
220///     color = LogColor::Green
221/// );
222/// ```
223#[macro_export]
224macro_rules! log_info {
225    // Both color and component (color first)
226    ($msg:literal, color = $color:expr, component = $component:expr) => {
227        log::info!(component = $component, color = $color as u8; $msg);
228    };
229    ($fmt:literal, $arg1:expr, color = $color:expr, component = $component:expr) => {
230        log::info!(component = $component, color = $color as u8; $fmt, $arg1);
231    };
232    ($fmt:literal, $arg1:expr, $arg2:expr, color = $color:expr, component = $component:expr) => {
233        log::info!(component = $component, color = $color as u8; $fmt, $arg1, $arg2);
234    };
235
236    // Both color and component (component first)
237    ($msg:literal, component = $component:expr, color = $color:expr) => {
238        log::info!(component = $component, color = $color as u8; $msg);
239    };
240    ($fmt:literal, $arg1:expr, component = $component:expr, color = $color:expr) => {
241        log::info!(component = $component, color = $color as u8; $fmt, $arg1);
242    };
243    ($fmt:literal, $arg1:expr, $arg2:expr, component = $component:expr, color = $color:expr) => {
244        log::info!(component = $component, color = $color as u8; $fmt, $arg1, $arg2);
245    };
246
247    // Component only
248    ($msg:literal, component = $component:expr) => {
249        log::info!(component = $component; $msg);
250    };
251    ($fmt:literal, $arg1:expr, component = $component:expr) => {
252        log::info!(component = $component; $fmt, $arg1);
253    };
254    ($fmt:literal, $arg1:expr, $arg2:expr, component = $component:expr) => {
255        log::info!(component = $component; $fmt, $arg1, $arg2);
256    };
257
258    // Color only
259    ($msg:literal, color = $color:expr) => {
260        log::info!(color = $color as u8; $msg);
261    };
262    ($fmt:literal, $arg1:expr, color = $color:expr) => {
263        log::info!(color = $color as u8; $fmt, $arg1);
264    };
265    ($fmt:literal, $arg1:expr, $arg2:expr, color = $color:expr) => {
266        log::info!(color = $color as u8; $fmt, $arg1, $arg2);
267    };
268    ($fmt:literal, $arg1:expr, $arg2:expr, $arg3:expr, color = $color:expr) => {
269        log::info!(color = $color as u8; $fmt, $arg1, $arg2, $arg3);
270    };
271
272    // Default (no color or component - auto-capture module path)
273    ($msg:literal) => {
274        log::info!(component = module_path!(), color = $crate::enums::LogColor::Normal as u8; $msg);
275    };
276    ($fmt:literal, $($args:expr),+) => {
277        log::info!(component = module_path!(), color = $crate::enums::LogColor::Normal as u8; $fmt, $($args),+);
278    };
279}
280
281/// Logs a warning message with automatic yellow color or custom color and component.
282///
283/// # Usage
284/// ```rust
285/// use nautilus_common::{enums::LogColor, log_warn};
286///
287/// // Automatic color (yellow)
288/// log_warn!("Position size approaching limit");
289///
290/// // Custom color
291/// log_warn!("Custom warning message", color = LogColor::Magenta);
292///
293/// // Custom component
294/// log_warn!("Risk limit exceeded", component = "RiskEngine");
295///
296/// // Both color and component (flexible order)
297/// log_warn!(
298///     "Warning message",
299///     color = LogColor::Magenta,
300///     component = "RiskEngine"
301/// );
302/// log_warn!(
303///     "Warning message",
304///     component = "RiskEngine",
305///     color = LogColor::Magenta
306/// );
307/// ```
308#[macro_export]
309macro_rules! log_warn {
310    // Both color and component (color first)
311    ($msg:literal, color = $color:expr, component = $component:expr) => {
312        log::warn!(component = $component, color = $color as u8; $msg);
313    };
314    ($fmt:literal, $arg1:expr, color = $color:expr, component = $component:expr) => {
315        log::warn!(component = $component, color = $color as u8; $fmt, $arg1);
316    };
317    ($fmt:literal, $arg1:expr, $arg2:expr, color = $color:expr, component = $component:expr) => {
318        log::warn!(component = $component, color = $color as u8; $fmt, $arg1, $arg2);
319    };
320
321    // Both color and component (component first)
322    ($msg:literal, component = $component:expr, color = $color:expr) => {
323        log::warn!(component = $component, color = $color as u8; $msg);
324    };
325    ($fmt:literal, $arg1:expr, component = $component:expr, color = $color:expr) => {
326        log::warn!(component = $component, color = $color as u8; $fmt, $arg1);
327    };
328    ($fmt:literal, $arg1:expr, $arg2:expr, component = $component:expr, color = $color:expr) => {
329        log::warn!(component = $component, color = $color as u8; $fmt, $arg1, $arg2);
330    };
331
332    // Component only
333    ($msg:literal, component = $component:expr) => {
334        log::warn!(component = $component, color = $crate::enums::LogColor::Yellow as u8; $msg);
335    };
336    ($fmt:literal, $arg1:expr, component = $component:expr) => {
337        log::warn!(component = $component, color = $crate::enums::LogColor::Yellow as u8; $fmt, $arg1);
338    };
339    ($fmt:literal, $arg1:expr, $arg2:expr, component = $component:expr) => {
340        log::warn!(component = $component, color = $crate::enums::LogColor::Yellow as u8; $fmt, $arg1, $arg2);
341    };
342
343    // Color only
344    ($msg:literal, color = $color:expr) => {
345        log::warn!(color = $color as u8; $msg);
346    };
347    ($fmt:literal, $arg1:expr, color = $color:expr) => {
348        log::warn!(color = $color as u8; $fmt, $arg1);
349    };
350    ($fmt:literal, $arg1:expr, $arg2:expr, color = $color:expr) => {
351        log::warn!(color = $color as u8; $fmt, $arg1, $arg2);
352    };
353    ($fmt:literal, $arg1:expr, $arg2:expr, $arg3:expr, color = $color:expr) => {
354        log::warn!(color = $color as u8; $fmt, $arg1, $arg2, $arg3);
355    };
356
357    // Default (automatic yellow color, no component - auto-capture module path)
358    ($msg:literal) => {
359        log::warn!(component = module_path!(), color = $crate::enums::LogColor::Yellow as u8; $msg);
360    };
361    ($fmt:literal, $($args:expr),+) => {
362        log::warn!(component = module_path!(), color = $crate::enums::LogColor::Yellow as u8; $fmt, $($args),+);
363    };
364}
365
366/// Logs an error message with automatic red color or custom color and component.
367///
368/// # Usage
369/// ```rust
370/// use nautilus_common::{enums::LogColor, log_error};
371///
372/// let error = "connection refused";
373///
374/// // Automatic color (red)
375/// log_error!("Failed to connect to exchange: {}", error);
376///
377/// // Custom color
378/// log_error!("Custom error message", color = LogColor::Magenta);
379///
380/// // Custom component
381/// log_error!("Connection failed", component = "DataEngine");
382///
383/// // Both color and component (flexible order)
384/// log_error!(
385///     "Critical error",
386///     color = LogColor::Magenta,
387///     component = "DataEngine"
388/// );
389/// log_error!(
390///     "Critical error",
391///     component = "DataEngine",
392///     color = LogColor::Magenta
393/// );
394/// ```
395#[macro_export]
396macro_rules! log_error {
397    // Both color and component (color first)
398    ($msg:literal, color = $color:expr, component = $component:expr) => {
399        log::error!(component = $component, color = $color as u8; $msg);
400    };
401    ($fmt:literal, $arg1:expr, color = $color:expr, component = $component:expr) => {
402        log::error!(component = $component, color = $color as u8; $fmt, $arg1);
403    };
404    ($fmt:literal, $arg1:expr, $arg2:expr, color = $color:expr, component = $component:expr) => {
405        log::error!(component = $component, color = $color as u8; $fmt, $arg1, $arg2);
406    };
407
408    // Both color and component (component first)
409    ($msg:literal, component = $component:expr, color = $color:expr) => {
410        log::error!(component = $component, color = $color as u8; $msg);
411    };
412    ($fmt:literal, $arg1:expr, component = $component:expr, color = $color:expr) => {
413        log::error!(component = $component, color = $color as u8; $fmt, $arg1);
414    };
415    ($fmt:literal, $arg1:expr, $arg2:expr, component = $component:expr, color = $color:expr) => {
416        log::error!(component = $component, color = $color as u8; $fmt, $arg1, $arg2);
417    };
418
419    // Component only
420    ($msg:literal, component = $component:expr) => {
421        log::error!(component = $component, color = $crate::enums::LogColor::Red as u8; $msg);
422    };
423    ($fmt:literal, $arg1:expr, component = $component:expr) => {
424        log::error!(component = $component, color = $crate::enums::LogColor::Red as u8; $fmt, $arg1);
425    };
426    ($fmt:literal, $arg1:expr, $arg2:expr, component = $component:expr) => {
427        log::error!(component = $component, color = $crate::enums::LogColor::Red as u8; $fmt, $arg1, $arg2);
428    };
429
430    // Color only
431    ($msg:literal, color = $color:expr) => {
432        log::error!(color = $color as u8; $msg);
433    };
434    ($fmt:literal, $arg1:expr, color = $color:expr) => {
435        log::error!(color = $color as u8; $fmt, $arg1);
436    };
437    ($fmt:literal, $arg1:expr, $arg2:expr, color = $color:expr) => {
438        log::error!(color = $color as u8; $fmt, $arg1, $arg2);
439    };
440    ($fmt:literal, $arg1:expr, $arg2:expr, $arg3:expr, color = $color:expr) => {
441        log::error!(color = $color as u8; $fmt, $arg1, $arg2, $arg3);
442    };
443
444    // Default (automatic red color, no component - auto-capture module path)
445    ($msg:literal) => {
446        log::error!(component = module_path!(), color = $crate::enums::LogColor::Red as u8; $msg);
447    };
448    ($fmt:literal, $($args:expr),+) => {
449        log::error!(component = module_path!(), color = $crate::enums::LogColor::Red as u8; $fmt, $($args),+);
450    };
451}
452
453// Re-exports
454pub use log_debug;
455pub use log_error;
456pub use log_info;
457pub use log_trace;
458pub use log_warn;
459
460// Gated out under `cfg(madsim)`: both tests drive the file-logging writer thread,
461// which is itself gated out under simulation (see `Logger::init_with_config`), so log
462// events are dropped and these tests would hang on `wait_until` waiting for a log file
463// that is never written. Logging is outside the determinism contract.
464#[cfg(all(test, not(all(feature = "simulation", madsim))))]
465mod tests {
466    use std::{thread::sleep, time::Duration};
467
468    use nautilus_core::UUID4;
469    use nautilus_model::identifiers::TraderId;
470    use rstest::*;
471    use tempfile::tempdir;
472
473    use crate::{
474        enums::LogColor,
475        logging::{
476            logger::{Logger, LoggerConfig},
477            logging_clock_set_static_mode, logging_clock_set_static_time,
478            writer::FileWriterConfig,
479        },
480        testing::wait_until,
481    };
482
483    #[rstest]
484    fn test_colored_logging_macros() {
485        let config = LoggerConfig::from_spec("stdout=Trace;fileout=Trace;is_colored").unwrap();
486
487        let temp_dir = tempdir().expect("Failed to create temporary directory");
488        let file_config = FileWriterConfig {
489            directory: Some(temp_dir.path().to_str().unwrap().to_string()),
490            ..Default::default()
491        };
492
493        let log_guard = Logger::init_with_config(
494            TraderId::from("TRADER-001"),
495            UUID4::new(),
496            config,
497            file_config,
498        )
499        .expect("Failed to initialize logger");
500
501        logging_clock_set_static_mode();
502        logging_clock_set_static_time(1_650_000_000_000_000);
503
504        // Test automatic color mappings using explicit components to ensure they're written
505        log_trace!("This is a trace message", component = "TestComponent");
506        log_debug!("This is a debug message", component = "TestComponent");
507        log_info!("This is an info message", component = "TestComponent");
508        log_warn!("This is a warning message", component = "TestComponent");
509        log_error!("This is an error message", component = "TestComponent");
510
511        // Test custom colors
512        log_info!(
513            "Success message",
514            color = LogColor::Green,
515            component = "TestComponent"
516        );
517        log_info!(
518            "Information message",
519            color = LogColor::Blue,
520            component = "TestComponent"
521        );
522        log_warn!(
523            "Custom warning",
524            component = "TestComponent",
525            color = LogColor::Magenta
526        );
527
528        // Test component only
529        log_info!("Component test", component = "TestComponent");
530        log_warn!("Component warning", component = "TestComponent");
531
532        // Test both color and component (different orders)
533        log_info!(
534            "Color then component",
535            color = LogColor::Cyan,
536            component = "TestComponent"
537        );
538        log_trace!(
539            "Trace color then component",
540            color = LogColor::Cyan,
541            component = "TestComponent"
542        );
543        log_trace!(
544            "Trace component then color",
545            component = "TestComponent",
546            color = LogColor::Cyan
547        );
548
549        // Allow time for logs to be written
550        sleep(Duration::from_millis(200));
551
552        drop(log_guard);
553
554        // Wait until log file exists and has contents
555        let mut log_contents = String::new();
556        wait_until(
557            || {
558                if let Some(log_file) = std::fs::read_dir(&temp_dir)
559                    .expect("Failed to read directory")
560                    .filter_map(Result::ok)
561                    .find(|entry| entry.path().is_file())
562                {
563                    let log_file_path = log_file.path();
564                    log_contents =
565                        std::fs::read_to_string(log_file_path).expect("Failed to read log file");
566                    !log_contents.is_empty()
567                } else {
568                    false
569                }
570            },
571            Duration::from_secs(3),
572        );
573
574        // Debug: print file contents if test is failing
575        if !log_contents.contains("This is a trace message") {
576            println!("File contents:\n{log_contents}");
577        }
578
579        // Verify that all log levels are present
580        assert!(log_contents.contains("This is a trace message"));
581        assert!(log_contents.contains("This is a debug message"));
582        assert!(log_contents.contains("This is an info message"));
583        assert!(log_contents.contains("This is a warning message"));
584        assert!(log_contents.contains("This is an error message"));
585        assert!(log_contents.contains("Success message"));
586        assert!(log_contents.contains("Information message"));
587        assert!(log_contents.contains("Custom warning"));
588
589        // Verify component and color combinations
590        assert!(log_contents.contains("Component test"));
591        assert!(log_contents.contains("Component warning"));
592        assert!(log_contents.contains("Color then component"));
593        assert!(log_contents.contains("Trace color then component"));
594        assert!(log_contents.contains("Trace component then color"));
595    }
596
597    #[rstest]
598    fn test_default_macro_captures_module_path() {
599        // This test verifies that log macros without explicit component
600        // auto-capture module_path!() as the component.
601        //
602        // The module path for this test is: nautilus_common::logging::macros::tests
603        // We configure a module filter and verify the log is filtered/passed accordingly.
604
605        let config = LoggerConfig::from_spec(
606            "stdout=Off;fileout=Trace;nautilus_common::logging::macros=Debug",
607        )
608        .unwrap();
609
610        let temp_dir = tempdir().expect("Failed to create temporary directory");
611        let file_config = FileWriterConfig {
612            directory: Some(temp_dir.path().to_str().unwrap().to_string()),
613            ..Default::default()
614        };
615
616        let log_guard = Logger::init_with_config(
617            TraderId::from("TRADER-PATH"),
618            UUID4::new(),
619            config,
620            file_config,
621        )
622        .expect("Failed to initialize logger");
623
624        logging_clock_set_static_mode();
625        logging_clock_set_static_time(1_650_000_000_000_000);
626
627        // Call macros WITHOUT explicit component - should auto-capture module_path!()
628        log_info!("Auto-captured module path message");
629        log_debug!("Debug level auto-captured");
630
631        // This trace should be filtered (module filter is Debug, Trace > Debug)
632        log_trace!("Trace should be filtered SHOULD_NOT_APPEAR");
633
634        sleep(Duration::from_millis(200));
635        drop(log_guard);
636
637        let mut log_contents = String::new();
638        wait_until(
639            || {
640                if let Some(log_file) = std::fs::read_dir(&temp_dir)
641                    .expect("Failed to read directory")
642                    .filter_map(Result::ok)
643                    .find(|entry| entry.path().is_file())
644                {
645                    log_contents =
646                        std::fs::read_to_string(log_file.path()).expect("Failed to read log file");
647                    !log_contents.is_empty()
648                } else {
649                    false
650                }
651            },
652            Duration::from_secs(3),
653        );
654
655        assert!(
656            log_contents.contains("nautilus_common::logging::macros"),
657            "Component should contain module path, was:\n{log_contents}"
658        );
659        assert!(
660            log_contents.contains("Auto-captured module path message"),
661            "Info message should pass"
662        );
663        assert!(
664            log_contents.contains("Debug level auto-captured"),
665            "Debug message should pass"
666        );
667        assert!(
668            !log_contents.contains("SHOULD_NOT_APPEAR"),
669            "Trace should be filtered by module filter"
670        );
671    }
672}