tracing_setup/
init_file_logging.rs

1crate::ix!();
2
3pub fn init_default_file_logging() {
4    let config = FileLoggingConfiguration::default();
5    init_file_logging(config);
6}
7
8pub fn create_file_logging_subscriber(config: &FileLoggingConfiguration) -> impl Subscriber + Send + Sync {
9    let writer = config.create_writer();
10
11    let env_filter = std::env::var("LOGFLAG").unwrap_or_else(|_| "info".to_string());
12
13    FmtSubscriber::builder()
14        .with_max_level(*config.log_level())
15        .with_env_filter(EnvFilter::new(env_filter))
16        .with_writer(writer)
17        .finish()
18}
19
20pub fn init_file_logging(config: FileLoggingConfiguration) {
21    static INIT: std::sync::Once = std::sync::Once::new();
22    static GUARD: Mutex<Option<tracing::subscriber::DefaultGuard>> = Mutex::new(None);
23
24    INIT.call_once(|| {
25        // Create the subscriber
26        let subscriber = create_file_logging_subscriber(&config);
27
28        if tracing::subscriber::set_global_default(subscriber).is_err() {
29            eprintln!("Global subscriber already set, proceeding without setting it again.");
30
31            // Re-create the subscriber since the previous one was moved
32            let subscriber = create_file_logging_subscriber(&config);
33
34            // Set the subscriber for the current thread
35            let guard = tracing::subscriber::set_default(subscriber);
36            *GUARD.lock().unwrap() = Some(guard);
37            // The guard is stored in a Mutex to keep it alive
38        } else {
39            tracing::info!("Logging initialized with file rotation");
40        }
41    });
42}
43
44#[cfg(test)]
45mod file_logging_tests {
46    use super::*;
47    use std::fs;
48    use std::path::PathBuf;
49    use tracing_subscriber::fmt::MakeWriter;
50
51    #[test]
52    #[serial]
53    fn test_create_writer_with_rotation() {
54        let log_file = PathBuf::from("test_rotation.log");
55        let config = FileLoggingConfiguration::new_temporary(
56            Some(log_file.clone()),
57            Level::DEBUG,
58            Some(Rotation::DAILY),
59        );
60
61        let subscriber = create_file_logging_subscriber(&config);
62
63        tracing::subscriber::with_default(subscriber, || {
64            // Log some messages
65            tracing::debug!("This is a test debug message");
66        });
67
68        // Clean up
69        config.remove_logs();
70    }
71
72    #[test]
73    #[serial]
74    fn test_create_writer_without_rotation() {
75        let log_file = PathBuf::from("test_no_rotation.log");
76        let config = FileLoggingConfiguration::new_temporary(
77            Some(log_file.clone()),
78            Level::DEBUG,
79            None,
80        );
81        let writer = config.create_writer();
82        // Verify the writer is created successfully
83        let _ = writer.make_writer();
84
85        // Clean up
86        config.remove_logs();
87    }
88
89    #[test]
90    #[serial]
91    fn test_default_logging_configuration() {
92        let config = FileLoggingConfiguration::default_temporary();
93        assert!(config.log_path_root_is("default.log"));
94        assert!(config.info_level());
95        assert!(config.rotates_daily());
96    }
97
98    #[test]
99    #[serial]
100    fn test_init_file_logging_with_defaults() {
101        use tracing::{trace, debug, info, warn, error};
102
103        info!("Starting test_init_file_logging_with_defaults");
104
105        let config = FileLoggingConfiguration::default();
106        let subscriber = create_file_logging_subscriber(&config);
107
108        tracing::subscriber::with_default(subscriber, || {
109            info!("This is a default log message.");
110            debug!("This debug message should not appear at INFO level.");
111
112            // Give the logging system a brief moment to write the file.
113            trace!("sleeping briefly to allow log writes to occur");
114            std::thread::sleep(std::time::Duration::from_millis(100));
115
116            info!("Searching for any log file that starts with 'default.log'");
117
118            // Because daily rotation may append date/time or other suffixes, we just look for any file
119            // whose name begins with "default.log" in the current directory.
120            let pattern = "default.log";
121            let log_files: Vec<String> = std::fs::read_dir(".")
122                .expect("Could not read current directory")
123                .filter_map(|entry| {
124                    let entry = match entry {
125                        Ok(e) => e,
126                        Err(e) => {
127                            warn!("Skipping DirEntry read error: {:?}", e);
128                            return None;
129                        }
130                    };
131                    let filename = entry.file_name().into_string().ok()?;
132                    if filename.starts_with(pattern) {
133                        Some(filename)
134                    } else {
135                        None
136                    }
137                })
138                .collect();
139
140            info!("Found candidate log files: {:?}", log_files);
141            assert!(
142                !log_files.is_empty(),
143                "Expected at least one file matching 'default.log*', but found none"
144            );
145
146            // Check each candidate file for our INFO log message.
147            let mut found_info_message = false;
148            for file in &log_files {
149                trace!("Checking file: {}", file);
150
151                let log_contents = match std::fs::read_to_string(file) {
152                    Ok(contents) => contents,
153                    Err(e) => {
154                        warn!("Failed to read file {}: {:?}", file, e);
155                        continue;
156                    }
157                };
158
159                if log_contents.contains("This is a default log message.") {
160                    found_info_message = true;
161                    break;
162                }
163            }
164
165            assert!(
166                found_info_message,
167                "Did not find the expected 'default log message' in any candidate file"
168            );
169
170            // Cleanup: remove any files we created (especially if daily rotation introduced many).
171            trace!("Removing created files");
172            for file in &log_files {
173                let _ = std::fs::remove_file(file);
174            }
175        });
176
177        info!("test_init_file_logging_with_defaults completed successfully");
178    }
179
180}