pub mod config;
pub mod error;
pub mod formatters;
pub mod logger;
pub mod macros;
pub mod writers;
pub use config::{
Colors, ConsoleConfig, FileConfig, LogLevel, LoggerConfig, LoggerConfigBuilder, OutputFormat,
RotationConfig, RotationFrequency,
};
pub use error::{LoggerError, Result};
pub use formatters::{CallerInfo, Formatter, LogRecord, ThreadInfo};
pub use logger::{
config, current_logger, flush, init, init_default, init_from_env, is_initialized, log_debug,
log_error, log_info, log_success, log_warning, log_with_caller, logger, stats,
with_scoped_logger, LoggerInstance, LoggerStats,
};
pub use macros::__FunctionTraceGuard;
pub mod legacy {
use crate::{init_default, is_initialized};
use std::fmt::Arguments;
#[deprecated(note = "Use the new firo_logger API instead")]
pub struct Logger;
#[allow(deprecated)]
impl Logger {
pub fn log(args: Arguments) {
if !is_initialized() {
let _ = init_default();
}
let _ = crate::log_info!("{}", args);
}
pub fn error(args: Arguments) {
if !is_initialized() {
let _ = init_default();
}
let _ = crate::log_error!("{}", args);
}
pub fn warning(args: Arguments) {
if !is_initialized() {
let _ = init_default();
}
let _ = crate::log_warning!("{}", args);
}
pub fn debug(args: Arguments) {
if !is_initialized() {
let _ = init_default();
}
let _ = crate::log_debug!("{}", args);
}
pub fn info(args: Arguments) {
if !is_initialized() {
let _ = init_default();
}
let _ = crate::log_info!("{}", args);
}
pub fn success(args: Arguments) {
if !is_initialized() {
let _ = init_default();
}
let _ = crate::log_success!("{}", args);
}
}
#[deprecated(note = "Use firo_logger::Colors instead")]
pub struct Colours;
#[allow(deprecated)]
impl Colours {
pub const RED: &'static str = "\x1b[31m";
pub const GREEN: &'static str = "\x1b[32m";
pub const YELLOW: &'static str = "\x1b[33m";
pub const BLUE: &'static str = "\x1b[34m";
pub const CYAN: &'static str = "\x1b[36m";
pub const WHITE: &'static str = "\x1b[37m";
}
#[deprecated(note = "Use firo_logger::LogLevel instead")]
#[derive(Debug, PartialEq)]
pub enum LogLevel {
Error,
Warning,
Debug,
Success,
Info,
Log,
}
#[allow(deprecated)]
impl LogLevel {
#[allow(dead_code)]
fn as_str(&self) -> &'static str {
match self {
LogLevel::Error => "ERROR",
LogLevel::Warning => "WARNING",
LogLevel::Debug => "DEBUG",
LogLevel::Success => "SUCCESS",
LogLevel::Info => "INFO",
LogLevel::Log => "LOG",
}
}
}
}
#[cfg(feature = "log")]
pub mod log_integration {
use crate::{init_default, is_initialized, LogLevel};
use log::{Level, Metadata, Record};
pub struct FiroLoggerAdapter;
impl log::Log for FiroLoggerAdapter {
fn enabled(&self, _metadata: &Metadata) -> bool {
true
}
fn log(&self, record: &Record) {
if !is_initialized() {
let _ = init_default();
}
let level = match record.level() {
Level::Error => LogLevel::Error,
Level::Warn => LogLevel::Warning,
Level::Info => LogLevel::Info,
Level::Debug => LogLevel::Debug,
Level::Trace => LogLevel::Debug,
};
let module = record.module_path();
let _ = crate::log_with_caller(level, *record.args(), None, module);
}
fn flush(&self) {
let _ = crate::flush();
}
}
pub fn init_with_log() -> Result<(), crate::LoggerError> {
init_default()?;
log::set_boxed_logger(Box::new(FiroLoggerAdapter))
.map_err(|_| crate::LoggerError::AlreadyInitialized)?;
log::set_max_level(log::LevelFilter::Trace);
Ok(())
}
}
pub mod utils {
use crate::LogLevel;
use std::fmt::Arguments;
use std::time::{Duration, Instant};
pub fn log_execution_time<F, R>(level: LogLevel, name: &str, f: F) -> R
where
F: FnOnce() -> R,
{
let start = Instant::now();
let result = f();
let duration = start.elapsed();
let _ = crate::log!(level, "{} completed in {:?}", name, duration);
result
}
pub struct ScopedLogger {
prefix: String,
}
impl ScopedLogger {
pub fn new<S: Into<String>>(prefix: S) -> Self {
Self {
prefix: prefix.into(),
}
}
pub fn log(&self, level: LogLevel, args: Arguments) {
let _ = crate::log!(level, "[{}] {}", self.prefix, args);
}
pub fn error(&self, args: Arguments) {
self.log(LogLevel::Error, args);
}
pub fn warning(&self, args: Arguments) {
self.log(LogLevel::Warning, args);
}
pub fn info(&self, args: Arguments) {
self.log(LogLevel::Info, args);
}
pub fn success(&self, args: Arguments) {
self.log(LogLevel::Success, args);
}
pub fn debug(&self, args: Arguments) {
self.log(LogLevel::Debug, args);
}
}
pub struct RateLimiter {
last_log: std::sync::Mutex<Option<Instant>>,
interval: Duration,
}
impl RateLimiter {
pub fn new(interval: Duration) -> Self {
Self {
last_log: std::sync::Mutex::new(None),
interval,
}
}
pub fn log(&self, level: LogLevel, args: Arguments) -> bool {
let now = Instant::now();
let mut last_log = self.last_log.lock().unwrap();
let should_log = match *last_log {
Some(last) => now.duration_since(last) >= self.interval,
None => true,
};
if should_log {
*last_log = Some(now);
let _ = crate::log!(level, "{}", args);
true
} else {
false
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Once;
static INIT: Once = Once::new();
fn init_test_logger() {
INIT.call_once(|| {
let config = LoggerConfig::builder()
.console(true)
.colors(false)
.level(LogLevel::Debug)
.build();
let _ = init(config);
});
}
#[test]
fn test_basic_api() {
init_test_logger();
assert!(log_error!("Test error").is_ok());
assert!(log_warning!("Test warning").is_ok());
assert!(log_info!("Test info").is_ok());
assert!(log_success!("Test success").is_ok());
assert!(log_debug!("Test debug").is_ok());
}
#[test]
fn test_formatted_logging() {
init_test_logger();
let user = "alice";
let count = 42;
assert!(log_info!("User {} processed {} items", user, count).is_ok());
assert!(log_error!("Error code: {}", 500).is_ok());
}
#[test]
#[allow(deprecated)]
fn test_legacy_static_functions() {
legacy::Logger::info(format_args!("Legacy info"));
legacy::Logger::error(format_args!("Legacy error"));
legacy::Logger::log(format_args!("Legacy log"));
legacy::Logger::warning(format_args!("Legacy warning"));
}
#[test]
fn test_config_builder() {
let config = LoggerConfig::builder()
.level(LogLevel::Debug)
.console(true)
.colors(false)
.file("test.log")
.format(OutputFormat::Json)
.include_caller(true)
.include_thread(true)
.metadata("test", "value")
.build();
assert_eq!(config.level, LogLevel::Debug);
assert!(config.console_enabled);
assert!(!config.console.colors);
assert!(config.file_enabled);
assert_eq!(config.format, OutputFormat::Json);
assert!(config.include_caller);
assert!(config.include_thread);
assert_eq!(config.metadata.get("test"), Some(&"value".to_string()));
}
#[test]
fn test_level_filtering() {
let config = LoggerConfig::builder()
.level(LogLevel::Warning)
.console(true)
.colors(false)
.build();
let logger = LoggerInstance::new(config).unwrap();
assert!(logger.error(format_args!("Error")).is_ok());
assert!(logger.warning(format_args!("Warning")).is_ok());
assert!(logger.info(format_args!("Info")).is_ok());
assert!(logger.debug(format_args!("Debug")).is_ok());
}
#[test]
fn test_utils() {
use utils::*;
let result = log_execution_time(LogLevel::Info, "test operation", || {
std::thread::sleep(std::time::Duration::from_millis(1));
42
});
assert_eq!(result, 42);
let scoped = ScopedLogger::new("TEST");
scoped.info(format_args!("Scoped message"));
let rate_limiter = RateLimiter::new(std::time::Duration::from_millis(100));
assert!(rate_limiter.log(LogLevel::Info, format_args!("Rate limited")));
}
}