firo_logger/lib.rs
1//! # firo_logger
2//!
3//! A high-performance, feature-rich logger for Rust applications with colored output,
4//! structured logging, file rotation, async logging, and advanced configuration.
5//!
6//! ## Features
7//!
8//! - **Colored console output** with customizable colors
9//! - **Structured logging** with JSON format support
10//! - **File logging** with rotation (size-based and time-based)
11//! - **Async logging** for high-performance applications
12//! - **Level filtering** with module-specific filters
13//! - **Thread-safe** with minimal overhead
14//! - **Caller information** (file, line, module)
15//! - **Custom metadata** support
16//! - **Environment configuration** support
17//! - **Builder pattern** for easy configuration
18//!
19//! ## Quick Start
20//!
21//! ```rust
22//! use firo_logger::{init_default, log_info, log_error, log_success};
23//!
24//! fn main() -> Result<(), Box<dyn std::error::Error>> {
25//! // Initialize the logger with default settings
26//! init_default()?;
27//!
28//! // Log some messages
29//! log_info!("Application started").unwrap();
30//! log_success!("Configuration loaded successfully").unwrap();
31//! log_error!("Failed to connect to database: {}", "Connection timeout").unwrap();
32//!
33//! Ok(())
34//! }
35//! ```
36//!
37//! ## Configuration
38//!
39//! ```rust
40//! use firo_logger::{LoggerConfig, LogLevel, OutputFormat, init, log_info};
41//!
42//! fn main() -> Result<(), Box<dyn std::error::Error>> {
43//! let config = LoggerConfig::builder()
44//! .level(LogLevel::Debug)
45//! .format(OutputFormat::Json)
46//! .console(true)
47//! .colors(true)
48//! .file("app.log")
49//! .rotate_by_size(10 * 1024 * 1024, 5) // 10MB, keep 5 files
50//! .async_logging(1000)
51//! .include_caller(true)
52//! .include_thread(true)
53//! .metadata("app", "my-app")
54//! .metadata("version", "1.0.0")
55//! .build();
56//!
57//! init(config)?;
58//!
59//! log_info!("Logger initialized with custom configuration").unwrap();
60//!
61//! Ok(())
62//! }
63//! ```
64//!
65//! ## Environment Configuration
66//!
67//! The logger can be configured using environment variables:
68//!
69//! - `FIRO_LOG_LEVEL`: Set log level (ERROR, WARNING, INFO, SUCCESS, DEBUG)
70//! - `FIRO_LOG_FILE`: Set log file path
71//! - `FIRO_LOG_FORMAT`: Set output format (text, json, plain)
72//! - `NO_COLOR`: Disable colored output
73//! - `FORCE_COLOR`: Force colored output even when not in a terminal
74//!
75//! ```rust
76//! use firo_logger::{init_from_env, log_info};
77//!
78//! fn main() -> Result<(), Box<dyn std::error::Error>> {
79//! init_from_env()?;
80//! log_info!("Logger configured from environment").unwrap();
81//! Ok(())
82//! }
83//! ```
84//!
85//! ## Advanced Usage
86//!
87//! ### Structured Logging with Metadata
88//!
89//! ```rust
90//! use firo_logger::{log_with_metadata, LogLevel};
91//!
92//! log_with_metadata!(
93//! LogLevel::Info,
94//! "User login",
95//! "user_id" => "12345",
96//! "ip_address" => "192.168.1.100",
97//! "user_agent" => "Mozilla/5.0...",
98//! );
99//! ```
100//!
101//! ### Conditional and Rate-Limited Logging
102//!
103//! ```rust
104//! use firo_logger::{log_if, log_rate_limited, LogLevel};
105//! use std::time::Duration;
106//!
107//! let debug_mode = std::env::var("DEBUG").is_ok();
108//! log_if!(debug_mode, LogLevel::Debug, "Debug mode is enabled");
109//!
110//! // Rate-limited logging (max once per second)
111//! for i in 0..1000 {
112//! log_rate_limited!(Duration::from_secs(1), LogLevel::Info, "Processing item {}", i);
113//! }
114//! ```
115//!
116//! ### Function Tracing
117//!
118//! ```rust
119//! use firo_logger::trace_function;
120//!
121//! fn process_data(data: &[u8]) -> Result<(), std::io::Error> {
122//! trace_function!("process_data", data.len());
123//! // Function implementation...
124//! Ok(())
125//! }
126//! ```
127//!
128//! ### Performance Timing
129//!
130//! ```rust
131//! use firo_logger::{time_block, LogLevel};
132//!
133//! let result = time_block!(LogLevel::Info, "Database query", {
134//! // Your expensive operation here
135//! std::thread::sleep(std::time::Duration::from_millis(100));
136//! "query result"
137//! });
138//! ```
139
140// Core modules
141pub mod config;
142pub mod error;
143pub mod formatters;
144pub mod logger;
145pub mod macros;
146pub mod writers;
147
148// Re-export commonly used types and functions
149pub use config::{
150 Colors, ConsoleConfig, FileConfig, LogLevel, LoggerConfig, LoggerConfigBuilder, OutputFormat,
151 RotationConfig, RotationFrequency,
152};
153pub use error::{LoggerError, Result};
154pub use formatters::{CallerInfo, Formatter, LogRecord, ThreadInfo};
155pub use logger::{
156 config, current_logger, flush, init, init_default, init_from_env, is_initialized, log_debug,
157 log_error, log_info, log_success, log_warning, log_with_caller, logger, stats,
158 with_scoped_logger, LoggerInstance, LoggerStats,
159};
160pub use macros::__FunctionTraceGuard;
161
162// Re-export macros - they are automatically available when the crate is used
163// due to the #[macro_export] attribute, but we can also make them available
164// through the crate root for documentation purposes.
165
166/// Legacy compatibility with the old simple API
167pub mod legacy {
168 //! Legacy compatibility module for the old firo_logger API.
169 //!
170 //! This module provides compatibility with the old logger API while
171 //! internally using the new improved implementation.
172
173 use crate::{init_default, is_initialized};
174 use std::fmt::Arguments;
175
176 /// Legacy Logger struct for compatibility.
177 #[deprecated(note = "Use the new firo_logger API instead")]
178 pub struct Logger;
179
180 #[allow(deprecated)]
181 impl Logger {
182 /// Logs a message (legacy compatibility).
183 pub fn log(args: Arguments) {
184 if !is_initialized() {
185 let _ = init_default();
186 }
187 let _ = crate::log_info!("{}", args);
188 }
189
190 /// Logs an error message (legacy compatibility).
191 pub fn error(args: Arguments) {
192 if !is_initialized() {
193 let _ = init_default();
194 }
195 let _ = crate::log_error!("{}", args);
196 }
197
198 /// Logs a warning message (legacy compatibility).
199 pub fn warning(args: Arguments) {
200 if !is_initialized() {
201 let _ = init_default();
202 }
203 let _ = crate::log_warning!("{}", args);
204 }
205
206 /// Logs a debug message (legacy compatibility).
207 pub fn debug(args: Arguments) {
208 if !is_initialized() {
209 let _ = init_default();
210 }
211 let _ = crate::log_debug!("{}", args);
212 }
213
214 /// Logs an info message (legacy compatibility).
215 pub fn info(args: Arguments) {
216 if !is_initialized() {
217 let _ = init_default();
218 }
219 let _ = crate::log_info!("{}", args);
220 }
221
222 /// Logs a success message (legacy compatibility).
223 pub fn success(args: Arguments) {
224 if !is_initialized() {
225 let _ = init_default();
226 }
227 let _ = crate::log_success!("{}", args);
228 }
229 }
230
231 /// Legacy Colors struct for compatibility.
232 #[deprecated(note = "Use firo_logger::Colors instead")]
233 pub struct Colours;
234
235 #[allow(deprecated)]
236 impl Colours {
237 pub const RED: &'static str = "\x1b[31m";
238 pub const GREEN: &'static str = "\x1b[32m";
239 pub const YELLOW: &'static str = "\x1b[33m";
240 pub const BLUE: &'static str = "\x1b[34m";
241 pub const CYAN: &'static str = "\x1b[36m";
242 pub const WHITE: &'static str = "\x1b[37m";
243 }
244
245 /// Legacy LogLevel enum for compatibility.
246 #[deprecated(note = "Use firo_logger::LogLevel instead")]
247 #[derive(Debug, PartialEq)]
248 pub enum LogLevel {
249 Error,
250 Warning,
251 Debug,
252 Success,
253 Info,
254 Log,
255 }
256
257 #[allow(deprecated)]
258 impl LogLevel {
259 #[allow(dead_code)]
260 fn as_str(&self) -> &'static str {
261 match self {
262 LogLevel::Error => "ERROR",
263 LogLevel::Warning => "WARNING",
264 LogLevel::Debug => "DEBUG",
265 LogLevel::Success => "SUCCESS",
266 LogLevel::Info => "INFO",
267 LogLevel::Log => "LOG",
268 }
269 }
270 }
271}
272
273// Integration with the standard `log` crate (optional feature)
274#[cfg(feature = "log")]
275pub mod log_integration {
276 //! Integration with the standard `log` crate.
277 //!
278 //! This module provides a bridge to use firo_logger as a backend
279 //! for the standard `log` crate.
280
281 use crate::{init_default, is_initialized, LogLevel};
282 use log::{Level, Metadata, Record};
283
284 /// A log implementation that forwards to firo_logger.
285 pub struct FiroLoggerAdapter;
286
287 impl log::Log for FiroLoggerAdapter {
288 fn enabled(&self, metadata: &Metadata) -> bool {
289 // Enable all log levels - firo_logger will handle filtering
290 true
291 }
292
293 fn log(&self, record: &Record) {
294 if !is_initialized() {
295 let _ = init_default();
296 }
297
298 let level = match record.level() {
299 Level::Error => LogLevel::Error,
300 Level::Warn => LogLevel::Warning,
301 Level::Info => LogLevel::Info,
302 Level::Debug => LogLevel::Debug,
303 Level::Trace => LogLevel::Debug,
304 };
305
306 let module = record.module_path();
307 let file = record.file().unwrap_or("<unknown>");
308 let line = record.line().unwrap_or(0);
309
310 let caller = crate::CallerInfo { file, line, module };
311
312 let _ = crate::log_with_caller(level, *record.args(), Some(caller), module);
313 }
314
315 fn flush(&self) {
316 let _ = crate::flush();
317 }
318 }
319
320 /// Initialize firo_logger as the global logger for the `log` crate.
321 pub fn init_with_log() -> Result<(), crate::LoggerError> {
322 init_default()?;
323 log::set_boxed_logger(Box::new(FiroLoggerAdapter))
324 .map_err(|_| crate::LoggerError::AlreadyInitialized)?;
325 log::set_max_level(log::LevelFilter::Trace);
326 Ok(())
327 }
328}
329
330/// Utility functions and helpers.
331pub mod utils {
332 //! Utility functions for common logging patterns.
333
334 use crate::LogLevel;
335 use std::fmt::Arguments;
336 use std::time::{Duration, Instant};
337
338 /// Logs execution time of a closure.
339 pub fn log_execution_time<F, R>(level: LogLevel, name: &str, f: F) -> R
340 where
341 F: FnOnce() -> R,
342 {
343 let start = Instant::now();
344 let result = f();
345 let duration = start.elapsed();
346
347 let _ = crate::log!(level, "{} completed in {:?}", name, duration);
348 result
349 }
350
351 /// Creates a scoped logger that adds a prefix to all log messages.
352 pub struct ScopedLogger {
353 prefix: String,
354 }
355
356 impl ScopedLogger {
357 /// Creates a new scoped logger with the given prefix.
358 pub fn new<S: Into<String>>(prefix: S) -> Self {
359 Self {
360 prefix: prefix.into(),
361 }
362 }
363
364 /// Logs a message with the scoped prefix.
365 pub fn log(&self, level: LogLevel, args: Arguments) {
366 let _ = crate::log!(level, "[{}] {}", self.prefix, args);
367 }
368
369 /// Logs an error message.
370 pub fn error(&self, args: Arguments) {
371 self.log(LogLevel::Error, args);
372 }
373
374 /// Logs a warning message.
375 pub fn warning(&self, args: Arguments) {
376 self.log(LogLevel::Warning, args);
377 }
378
379 /// Logs an info message.
380 pub fn info(&self, args: Arguments) {
381 self.log(LogLevel::Info, args);
382 }
383
384 /// Logs a success message.
385 pub fn success(&self, args: Arguments) {
386 self.log(LogLevel::Success, args);
387 }
388
389 /// Logs a debug message.
390 pub fn debug(&self, args: Arguments) {
391 self.log(LogLevel::Debug, args);
392 }
393 }
394
395 /// Helper for rate limiting log messages.
396 pub struct RateLimiter {
397 last_log: std::sync::Mutex<Option<Instant>>,
398 interval: Duration,
399 }
400
401 impl RateLimiter {
402 /// Creates a new rate limiter with the specified interval.
403 pub fn new(interval: Duration) -> Self {
404 Self {
405 last_log: std::sync::Mutex::new(None),
406 interval,
407 }
408 }
409
410 /// Attempts to log a message, respecting the rate limit.
411 pub fn log(&self, level: LogLevel, args: Arguments) -> bool {
412 let now = Instant::now();
413 let mut last_log = self.last_log.lock().unwrap();
414
415 let should_log = match *last_log {
416 Some(last) => now.duration_since(last) >= self.interval,
417 None => true,
418 };
419
420 if should_log {
421 *last_log = Some(now);
422 let _ = crate::log!(level, "{}", args);
423 true
424 } else {
425 false
426 }
427 }
428 }
429}
430
431// Tests for the main API
432#[cfg(test)]
433mod tests {
434 use super::*;
435 use std::sync::Once;
436
437 static INIT: Once = Once::new();
438
439 fn init_test_logger() {
440 INIT.call_once(|| {
441 let config = LoggerConfig::builder()
442 .console(true)
443 .colors(false)
444 .level(LogLevel::Debug)
445 .build();
446 let _ = init(config);
447 });
448 }
449
450 #[test]
451 fn test_basic_api() {
452 init_test_logger();
453
454 assert!(log_error!("Test error").is_ok());
455 assert!(log_warning!("Test warning").is_ok());
456 assert!(log_info!("Test info").is_ok());
457 assert!(log_success!("Test success").is_ok());
458 assert!(log_debug!("Test debug").is_ok());
459 }
460
461 #[test]
462 fn test_formatted_logging() {
463 init_test_logger();
464
465 let user = "alice";
466 let count = 42;
467
468 assert!(log_info!("User {} processed {} items", user, count).is_ok());
469 assert!(log_error!("Error code: {}", 500).is_ok());
470 }
471
472 #[test]
473 #[allow(deprecated)]
474 fn test_legacy_static_functions() {
475 // Test static functions (legacy API)
476 legacy::Logger::info(format_args!("Legacy info"));
477 legacy::Logger::error(format_args!("Legacy error"));
478 legacy::Logger::log(format_args!("Legacy log"));
479 legacy::Logger::warning(format_args!("Legacy warning"));
480 }
481
482 #[test]
483 fn test_config_builder() {
484 let config = LoggerConfig::builder()
485 .level(LogLevel::Debug)
486 .console(true)
487 .colors(false)
488 .file("test.log")
489 .format(OutputFormat::Json)
490 .include_caller(true)
491 .include_thread(true)
492 .metadata("test", "value")
493 .build();
494
495 assert_eq!(config.level, LogLevel::Debug);
496 assert!(config.console_enabled);
497 assert!(!config.console.colors);
498 assert!(config.file_enabled);
499 assert_eq!(config.format, OutputFormat::Json);
500 assert!(config.include_caller);
501 assert!(config.include_thread);
502 assert_eq!(config.metadata.get("test"), Some(&"value".to_string()));
503 }
504
505 #[test]
506 fn test_level_filtering() {
507 let config = LoggerConfig::builder()
508 .level(LogLevel::Warning)
509 .console(true)
510 .colors(false)
511 .build();
512
513 let logger = LoggerInstance::new(config).unwrap();
514
515 // These should succeed (but may not actually log due to level filtering)
516 assert!(logger.error(format_args!("Error")).is_ok());
517 assert!(logger.warning(format_args!("Warning")).is_ok());
518 assert!(logger.info(format_args!("Info")).is_ok());
519 assert!(logger.debug(format_args!("Debug")).is_ok());
520 }
521
522 #[test]
523 fn test_utils() {
524 use utils::*;
525
526 let result = log_execution_time(LogLevel::Info, "test operation", || {
527 std::thread::sleep(std::time::Duration::from_millis(1));
528 42
529 });
530
531 assert_eq!(result, 42);
532
533 let scoped = ScopedLogger::new("TEST");
534 scoped.info(format_args!("Scoped message"));
535
536 let rate_limiter = RateLimiter::new(std::time::Duration::from_millis(100));
537 assert!(rate_limiter.log(LogLevel::Info, format_args!("Rate limited")));
538 }
539}