tracing_setup/
init_file_logging.rs1crate::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 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 let subscriber = create_file_logging_subscriber(&config);
33
34 let guard = tracing::subscriber::set_default(subscriber);
36 *GUARD.lock().unwrap() = Some(guard);
37 } 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 tracing::debug!("This is a test debug message");
66 });
67
68 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 let _ = writer.make_writer();
84
85 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 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 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 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 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}