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