FreedomLogger/lib.rs
1/// FreedomLogger - A professional logging library for Rust
2///
3/// FreedomLogger provides clean, efficient logging with automatic rotation,
4/// multiple output formats, and error-proof operation. Designed for both
5/// development and production use.
6///
7/// Features:
8/// - Multiple log levels (ERROR, WARNING, INFO, DEBUG, TRACE) with filtering
9/// - Various output patterns (Basic, Detailed, Extended, JSON, Custom)
10/// - Automatic log rotation based on file size
11/// - Thread-safe concurrent logging
12/// - No external dependencies (except chrono for timestamps)
13/// - Error-proof operation (internal errors logged separately)
14/// - Easy single-initialization API
15/// - Flexible logging macros supporting formatted messages
16///
17/// Usage:
18/// 1. Initialize logger once in main(): logger::init(pattern, path, filename)
19/// 2. Log anywhere in your code:
20/// - Simple: logger::info("message")
21/// - Formatted: log_info!("User {} logged in", user_id)
22/// 3. All configuration is done at initialization time
23
24use std::sync::{Arc, Once};
25use std::path::Path;
26
27// Import all our modules
28pub mod error;
29pub mod core;
30pub mod format;
31pub mod rotation;
32
33// Re-export main types for user convenience
34pub use core::{LogLevel, Pattern, LoggerConfig, Logger};
35pub use error::LoggerError;
36
37/// Global logger instance - initialized once, used everywhere
38static mut GLOBAL_LOGGER: Option<Arc<Logger>> = None;
39static INIT_ONCE: Once = Once::new();
40
41/// Initialize the global logger with basic configuration
42///
43/// This is the simplest initialization - logs all levels with default settings.
44/// Uses 10MB max file size and keeps 5 backup files.
45///
46/// # Arguments
47/// * `pattern` - Log formatting pattern (Basic, Detailed, etc.)
48/// * `file_path` - Directory path where log files will be created
49/// * `file_name` - Base name for log files (without extension)
50///
51/// # Panics
52/// Panics if called more than once or if initialization fails
53pub fn log_init<P: AsRef<Path>>(pattern: Pattern, file_path: P, file_name: &str) {
54 let path_buf = file_path.as_ref().to_path_buf();
55 let config = LoggerConfig::basic(pattern, path_buf, file_name.to_string());
56 log_init_with_config(config);
57}
58
59/// Initialize the global logger with log level filtering
60///
61/// Logs only messages at or above the specified level.
62/// Uses default rotation settings (10MB, 5 backups).
63///
64/// # Arguments
65/// * `pattern` - Log formatting pattern
66/// * `file_path` - Directory path where log files will be created
67/// * `file_name` - Base name for log files (without extension)
68/// * `log_level` - Minimum log level to write
69///
70/// # Panics
71/// Panics if called more than once or if initialization fails
72pub fn log_init_with_level<P: AsRef<Path>>(
73 pattern: Pattern,
74 file_path: P,
75 file_name: &str,
76 log_level: LogLevel
77) {
78 let path_buf = file_path.as_ref().to_path_buf();
79 let config = LoggerConfig::with_level(pattern, path_buf, file_name.to_string(), log_level);
80 log_init_with_config(config);
81}
82
83/// Initialize the global logger with custom rotation settings
84///
85/// Full control over all logging parameters including rotation behavior.
86///
87/// # Arguments
88/// * `pattern` - Log formatting pattern
89/// * `file_path` - Directory path where log files will be created
90/// * `file_name` - Base name for log files (without extension)
91/// * `log_level` - Minimum log level to write
92/// * `max_file_size` - Maximum file size in bytes before rotation
93/// * `max_backup_files` - Number of backup files to keep
94///
95/// # Panics
96/// Panics if called more than once or if initialization fails
97pub fn log_init_with_rotation<P: AsRef<Path>>(
98 pattern: Pattern,
99 file_path: P,
100 file_name: &str,
101 log_level: LogLevel,
102 max_file_size: u64,
103 max_backup_files: u32,
104) {
105 let path_buf = file_path.as_ref().to_path_buf();
106 let config = LoggerConfig::with_rotation(
107 pattern,
108 path_buf,
109 file_name.to_string(),
110 log_level,
111 max_file_size,
112 max_backup_files
113 );
114 log_init_with_config(config);
115}
116
117/// Initialize with a complete configuration object
118///
119/// Internal method used by all public init functions.
120/// Ensures thread-safe single initialization.
121fn log_init_with_config(config: LoggerConfig) {
122 INIT_ONCE.call_once(|| {
123 let logger = Logger::new(config);
124 unsafe {
125 GLOBAL_LOGGER = Some(Arc::new(logger));
126 }
127 });
128}
129
130/// Get reference to the global logger instance
131///
132/// Returns the initialized logger or panics if not initialized.
133/// This is used internally by the logging functions.
134#[allow(static_mut_refs)]
135fn get_logger() -> &'static Arc<Logger> {
136 unsafe {
137 GLOBAL_LOGGER
138 .as_ref()
139 .expect("Logger not initialized - call logger::init() first")
140 }
141}
142
143/// Log an ERROR level message
144///
145/// Logs critical errors that indicate serious problems.
146/// These messages are always written regardless of log level filtering.
147///
148/// # Arguments
149/// * `message` - The error message to log
150pub fn log_error(message: &str) {
151 get_logger().error(message);
152}
153
154/// Log a WARNING level message
155///
156/// Logs warning messages that indicate potential issues.
157/// Written when log level is WARNING or higher.
158///
159/// # Arguments
160/// * `message` - The warning message to log
161pub fn log_warning(message: &str) {
162 get_logger().warning(message);
163}
164
165/// Log an INFO level message
166///
167/// Logs general information about application flow.
168/// Written when log level is INFO or higher.
169///
170/// # Arguments
171/// * `message` - The info message to log
172pub fn log_info(message: &str) {
173 get_logger().info(message);
174}
175
176/// Log a DEBUG level message
177///
178/// Logs detailed information useful for debugging.
179/// Written when log level is DEBUG or higher.
180///
181/// # Arguments
182/// * `message` - The debug message to log
183pub fn log_debug(message: &str) {
184 get_logger().debug(message);
185}
186
187/// Log a TRACE level message
188///
189/// Logs very detailed trace information.
190/// Written when log level is TRACE (logs everything).
191///
192/// # Arguments
193/// * `message` - The trace message to log
194pub fn log_trace(message: &str) {
195 get_logger().trace(message);
196}
197
198// ============================================================================
199// MACROS VOOR FORMATTED LOGGING
200// ============================================================================
201
202/// Macro for logging ERROR messages with formatting support
203///
204/// Supports both simple messages and formatted strings with arguments.
205/// Uses Rust's built-in format! macro for automatic type handling.
206///
207
208#[macro_export]
209macro_rules! log_error {
210 // Simple message zonder formatting
211 ($msg:expr) => {
212 $crate::log_error($msg);
213 };
214
215 // Formatted message met argumenten
216 ($fmt:expr, $($arg:expr),+ $(,)?) => {
217 $crate::log_error(&format!($fmt, $($arg),+));
218 };
219}
220
221/// Macro for logging WARNING messages with formatting support
222///
223/// Supports both simple messages and formatted strings with arguments.
224///
225
226/// ```
227#[macro_export]
228macro_rules! log_warning {
229 ($msg:expr) => {
230 $crate::log_warning($msg);
231 };
232
233 ($fmt:expr, $($arg:expr),+ $(,)?) => {
234 $crate::log_warning(&format!($fmt, $($arg),+));
235 };
236}
237
238/// Macro for logging INFO messages with formatting support
239///
240/// Supports both simple messages and formatted strings with arguments.
241///
242
243#[macro_export]
244macro_rules! log_info {
245 ($msg:expr) => {
246 $crate::log_info($msg);
247 };
248
249 ($fmt:expr, $($arg:expr),+ $(,)?) => {
250 $crate::log_info(&format!($fmt, $($arg),+));
251 };
252}
253
254/// Macro for logging DEBUG messages with formatting support
255///
256/// This macro solves the original problem! It supports both simple messages
257/// and formatted strings with arguments, automatically handling any type
258/// that implements Display or Debug.
259///
260
261#[macro_export]
262macro_rules! log_debug {
263 ($msg:expr) => {
264 $crate::log_debug($msg);
265 };
266
267 ($fmt:expr, $($arg:expr),+ $(,)?) => {
268 $crate::log_debug(&format!($fmt, $($arg),+));
269 };
270}
271
272/// Macro for logging TRACE messages with formatting support
273///
274/// Supports both simple messages and formatted strings with arguments.
275///
276
277#[macro_export]
278macro_rules! log_trace {
279 ($msg:expr) => {
280 $crate::log_trace($msg);
281 };
282
283 ($fmt:expr, $($arg:expr),+ $(,)?) => {
284 $crate::log_trace(&format!($fmt, $($arg),+));
285 };
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291 use tempfile::tempdir;
292 use std::fs;
293 use std::path::PathBuf;
294
295 #[test]
296 fn test_basic_logging_integration() {
297 let temp_dir = tempdir().unwrap();
298
299 // Initialize logger
300 log_init(Pattern::Basic, temp_dir.path(), "test");
301
302 // Test basic function logging
303 log_info("Test info message");
304 log_warning("Test warning message");
305 log_error("Test error message");
306
307 // Test the new macros with formatting
308 let database_path = PathBuf::from("/var/lib/myapp/database.db");
309 let user_id = 12345;
310 let status = "active";
311
312 // This should now work without any errors!
313 log_debug!("Database path: {:?}", database_path);
314 log_info!("User {} has status: {}", user_id, status);
315 log_warning!("Processing {} items", 42);
316 log_error!("Failed to connect to {}", "localhost:5432");
317
318 // Test simple messages still work
319 log_debug!("Simple debug message");
320 log_info!("Simple info message");
321
322 // Check that log file was created
323 let log_file = temp_dir.path().join("test.log");
324 assert!(log_file.exists());
325
326 // Check log content
327 let content = fs::read_to_string(&log_file).unwrap();
328
329 // Test basic function messages
330 assert!(content.contains("INFO: Test info message"));
331 assert!(content.contains("WARNING: Test warning message"));
332 assert!(content.contains("ERROR: Test error message"));
333
334 // Test macro messages
335 assert!(content.contains("Database path:"));
336 assert!(content.contains("User 12345 has status: active"));
337 assert!(content.contains("Processing 42 items"));
338 assert!(content.contains("Failed to connect to localhost:5432"));
339 assert!(content.contains("Simple debug message"));
340 }
341}