rust_loguru/
lib.rs

1//! A flexible and efficient logging library for Rust.
2//!
3//! This library provides a powerful logging system with the following features:
4//! - Multiple log levels (TRACE, DEBUG, INFO, WARNING, ERROR, CRITICAL)
5//! - Thread-safe global logger
6//! - Extensible handler system
7//! - Configurable log formatting
8//! - Support for metadata in log records
9//! - Convenient logging macros
10//! - Asynchronous logging with worker thread pool
11//! - Log crate compatibility
12//! - Compile-time filtering optimizations
13//!
14//! # Examples
15//!
16//! ```rust,no_run
17//! use rust_loguru::{Logger, LogLevel, Record};
18//! use rust_loguru::handler::NullHandler;
19//! use rust_loguru::{info, debug, error};
20//! use std::sync::Arc;
21//! use parking_lot::RwLock;
22//!
23//! // Create a logger with a handler
24//! let handler = Arc::new(RwLock::new(NullHandler::new(LogLevel::Info)));
25//! let mut logger = Logger::new(LogLevel::Debug);
26//! logger.add_handler(handler);
27//!
28//! // Log a message
29//! let record = Record::new(
30//!     LogLevel::Info,
31//!     "Hello, world!",
32//!     Some("my_module".to_string()),
33//!     Some("main.rs".to_string()),
34//!     Some(42),
35//! );
36//! logger.log(&record);
37//!
38//! // Or use the convenient macros
39//! info!("Hello, world!");
40//! debug!("Debug message: {}", 42);
41//! error!("Error occurred: {}", "something went wrong");
42//!
43//! // Asynchronous logging
44//! logger.set_async(true, Some(10000));
45//! info!("This will be logged asynchronously");
46//! ```
47
48pub mod config;
49pub mod context;
50pub mod error;
51pub mod formatter;
52pub mod formatters;
53pub mod handler;
54pub mod integration;
55pub mod level;
56pub mod logger;
57#[doc(hidden)]
58pub mod macros;
59pub mod record;
60pub mod record_pool;
61pub mod scope;
62pub mod test_utils;
63
64pub use config::{LoggerConfig, LoggerConfigBuilder};
65pub use error::{error_chain, install_panic_hook, ContextError, OptionExt, ResultExt};
66pub use formatters::json::JsonFormatter;
67pub use formatters::template::TemplateFormatter;
68pub use formatters::text::TextFormatter;
69pub use formatters::FormatterTrait;
70pub use handler::Handler;
71pub use level::LogLevel;
72pub use logger::{global, init, log, Logger};
73pub use record::Record;
74pub use record_pool::{PooledRecord, RecordPool};
75pub use scope::{ScopeError, ScopeGuard};
76
77// Re-export log crate types for compatibility
78pub use log::{LevelFilter, Log, Metadata, Record as LogRecord};
79
80// Asynchronous logging types
81use crossbeam_channel::{bounded, Receiver, Sender};
82use parking_lot::RwLock as PLRwLock;
83use std::sync::atomic::{AtomicBool, Ordering};
84use std::sync::Arc;
85use std::thread;
86use std::time::Duration;
87
88/// Compile-time static log level for filtering (can be overridden at build time)
89#[allow(dead_code)]
90pub static STATIC_LEVEL: level::LogLevel = level::LogLevel::Trace;
91
92/// Async logging command type
93#[doc(hidden)]
94pub enum AsyncLogCommand {
95    /// Log a record
96    Log(Record),
97    /// Shut down the async logger
98    Shutdown,
99}
100
101/// Handle to the async logger
102#[derive(Clone, Debug)]
103pub struct AsyncLoggerHandle {
104    /// Channel for sending commands to the async logger
105    sender: Sender<AsyncLogCommand>,
106    /// Flag indicating whether the async logger is running
107    running: Arc<AtomicBool>,
108}
109
110impl AsyncLoggerHandle {
111    /// Log a record
112    pub fn log(&self, record: Record) -> bool {
113        if !self.running.load(Ordering::Relaxed) {
114            return false;
115        }
116
117        self.sender.try_send(AsyncLogCommand::Log(record)).is_ok()
118    }
119
120    /// Shut down the async logger
121    pub fn shutdown(&self) {
122        if !self.running.load(Ordering::Relaxed) {
123            return;
124        }
125
126        // Send shutdown command and update running flag
127        let _ = self.sender.send(AsyncLogCommand::Shutdown);
128        self.running.store(false, Ordering::Relaxed);
129    }
130}
131
132impl Drop for AsyncLoggerHandle {
133    fn drop(&mut self) {
134        self.shutdown();
135    }
136}
137
138/// Builder for the async logger
139pub struct AsyncLoggerBuilder {
140    /// Queue size
141    queue_size: usize,
142    /// Handler registry
143    handlers: Vec<Arc<PLRwLock<dyn Handler>>>,
144    /// Log level
145    level: LogLevel,
146    /// Number of worker threads
147    workers: usize,
148}
149
150impl AsyncLoggerBuilder {
151    /// Create a new async logger builder
152    pub fn new() -> Self {
153        Self {
154            queue_size: 10000,
155            handlers: Vec::new(),
156            level: LogLevel::Info,
157            workers: 1,
158        }
159    }
160
161    /// Set the queue size
162    pub fn with_queue_size(mut self, queue_size: usize) -> Self {
163        self.queue_size = queue_size;
164        self
165    }
166
167    /// Set the handlers
168    pub fn with_handlers(mut self, handlers: Vec<Arc<PLRwLock<dyn Handler>>>) -> Self {
169        self.handlers = handlers;
170        self
171    }
172
173    /// Set the log level
174    pub fn with_level(mut self, level: LogLevel) -> Self {
175        self.level = level;
176        self
177    }
178
179    /// Set the number of worker threads
180    pub fn with_workers(mut self, workers: usize) -> Self {
181        self.workers = workers;
182        self
183    }
184
185    /// Build the async logger
186    pub fn build(self) -> AsyncLoggerHandle {
187        // Create a channel for sending commands to the worker thread
188        let (sender, receiver) = bounded(self.queue_size);
189
190        // Create a running flag
191        let running = Arc::new(AtomicBool::new(true));
192        let running_clone = running.clone();
193
194        // Spawn the worker threads
195        let handlers = self.handlers.clone();
196        let level = self.level;
197
198        // Create worker thread pool
199        for _ in 0..self.workers {
200            let receiver = receiver.clone();
201            let handlers = handlers.clone();
202            let running = running_clone.clone();
203
204            thread::spawn(move || {
205                Self::worker_thread(receiver, handlers, level, running);
206            });
207        }
208
209        // Create the async logger handle
210        AsyncLoggerHandle { sender, running }
211    }
212
213    /// Worker thread function
214    fn worker_thread(
215        receiver: Receiver<AsyncLogCommand>,
216        handlers: Vec<Arc<PLRwLock<dyn Handler>>>,
217        level: LogLevel,
218        running: Arc<AtomicBool>,
219    ) {
220        while running.load(Ordering::Relaxed) {
221            match receiver.recv_timeout(Duration::from_millis(100)) {
222                Ok(AsyncLogCommand::Log(record)) => {
223                    // Process the record
224                    if record.level() >= level {
225                        for handler in &handlers {
226                            let guard = handler.write();
227                            if guard.is_enabled() && record.level() >= guard.level() {
228                                let _ = guard.handle(&record);
229                            }
230                        }
231                    }
232                }
233                Ok(AsyncLogCommand::Shutdown) => {
234                    running.store(false, Ordering::Relaxed);
235                    break;
236                }
237                Err(crossbeam_channel::RecvTimeoutError::Timeout) => {
238                    // Timeout is normal, continue waiting
239                    continue;
240                }
241                Err(crossbeam_channel::RecvTimeoutError::Disconnected) => {
242                    // Channel closed, exit
243                    running.store(false, Ordering::Relaxed);
244                    break;
245                }
246            }
247        }
248    }
249}
250
251impl Default for AsyncLoggerBuilder {
252    fn default() -> Self {
253        Self::new()
254    }
255}
256
257/// Module for log crate compatibility
258pub mod log_adapter {
259    use crate::level::LogLevel;
260    use crate::logger::global;
261    use crate::record::Record as LoguruRecord;
262    use log::{Level, Log, Metadata, Record};
263
264    /// Adapter for the log crate
265    pub struct LogAdapter;
266
267    impl Log for LogAdapter {
268        fn enabled(&self, metadata: &Metadata) -> bool {
269            let level = match metadata.level() {
270                Level::Error => LogLevel::Error,
271                Level::Warn => LogLevel::Warning,
272                Level::Info => LogLevel::Info,
273                Level::Debug => LogLevel::Debug,
274                Level::Trace => LogLevel::Trace,
275            };
276
277            level >= global().read().level()
278        }
279
280        fn log(&self, record: &Record) {
281            if !self.enabled(record.metadata()) {
282                return;
283            }
284
285            let level = match record.level() {
286                Level::Error => LogLevel::Error,
287                Level::Warn => LogLevel::Warning,
288                Level::Info => LogLevel::Info,
289                Level::Debug => LogLevel::Debug,
290                Level::Trace => LogLevel::Trace,
291            };
292
293            let loguru_record = LoguruRecord::new(
294                level,
295                record.args().to_string(),
296                record.module_path().map(|s| s.to_string()),
297                record.file().map(|s| s.to_string()),
298                record.line(),
299            );
300
301            let _ = global().read().log(&loguru_record);
302        }
303
304        fn flush(&self) {
305            // Nothing to flush in our implementation
306        }
307    }
308
309    /// Initialize the log adapter
310    pub fn init() -> Result<(), log::SetLoggerError> {
311        static LOGGER: LogAdapter = LogAdapter;
312        log::set_logger(&LOGGER)?;
313        Ok(())
314    }
315
316    /// Set the maximum log level for the log crate
317    pub fn set_max_level(level: LogLevel) {
318        let max_level = match level {
319            LogLevel::Error => log::LevelFilter::Error,
320            LogLevel::Warning => log::LevelFilter::Warn,
321            LogLevel::Info => log::LevelFilter::Info,
322            LogLevel::Debug => log::LevelFilter::Debug,
323            LogLevel::Trace => log::LevelFilter::Trace,
324            _ => log::LevelFilter::Off,
325        };
326
327        log::set_max_level(max_level);
328    }
329}
330
331/// Module for compile-time filtering optimizations
332pub mod compile_time {
333    use crate::level::LogLevel;
334
335    /// Compile-time level check
336    #[macro_export]
337    macro_rules! compile_time_level_enabled {
338        ($level:expr) => {{
339            #[cfg(feature = "max_level_off")]
340            {
341                false
342            }
343            #[cfg(not(feature = "max_level_off"))]
344            {
345                #[cfg(feature = "max_level_error")]
346                {
347                    $level >= $crate::LogLevel::Error
348                }
349                #[cfg(feature = "max_level_warn")]
350                {
351                    $level >= $crate::LogLevel::Warning
352                }
353                #[cfg(feature = "max_level_info")]
354                {
355                    $level >= $crate::LogLevel::Info
356                }
357                #[cfg(feature = "max_level_debug")]
358                {
359                    $level >= $crate::LogLevel::Debug
360                }
361                #[cfg(feature = "max_level_trace")]
362                {
363                    $level >= $crate::LogLevel::Trace
364                }
365                #[cfg(not(any(
366                    feature = "max_level_error",
367                    feature = "max_level_warn",
368                    feature = "max_level_info",
369                    feature = "max_level_debug",
370                    feature = "max_level_trace"
371                )))]
372                {
373                    true
374                }
375            }
376        }};
377    }
378
379    /// Dynamic runtime level check (used when compile-time check passes)
380    #[inline]
381    pub fn runtime_level_enabled(level: LogLevel, current_level: LogLevel) -> bool {
382        level >= current_level
383    }
384}
385
386/// Module for benchmarking tools
387pub mod benchmark {
388    use crate::level::LogLevel;
389    use crate::logger::Logger;
390    use crate::record::Record;
391    use std::time::Instant;
392
393    /// Benchmark logger throughput
394    pub fn measure_throughput(logger: &Logger, iterations: usize, level: LogLevel) -> f64 {
395        let start = Instant::now();
396
397        for i in 0..iterations {
398            let record = Record::new(
399                level,
400                format!("Benchmark message {}", i),
401                Some("benchmark".to_string()),
402                Some("benchmark.rs".to_string()),
403                Some(1),
404            );
405            let _ = logger.log(&record);
406        }
407
408        let elapsed = start.elapsed();
409        iterations as f64 / elapsed.as_secs_f64()
410    }
411
412    /// Compare sync vs async performance
413    pub fn compare_sync_vs_async(
414        logger: &mut Logger,
415        iterations: usize,
416        level: LogLevel,
417    ) -> (f64, f64) {
418        // Measure sync throughput
419        let sync_throughput = measure_throughput(logger, iterations, level);
420
421        // Enable async logging
422        logger.set_async(true, Some(iterations));
423
424        // Measure async throughput
425        let async_throughput = measure_throughput(logger, iterations, level);
426
427        // Disable async logging
428        logger.set_async(false, None);
429
430        (sync_throughput, async_throughput)
431    }
432}