multitool_hg/logger/
tracer_logger.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::fmt;
use serde::Serialize;

/// `LogLevel` defines the different levels of logging that can be used
/// within the application. These levels correspond to the common logging
/// levels found in Rust's logging libraries.
///
/// This enum supports deserialization via Serde and is compatible with
/// command-line arguments using Clap.
#[derive(clap::ValueEnum, Clone, Default, Debug, Serialize, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum LogLevel {
    /// Log level for informational messages.
    #[default]
    Info,
    /// Log level for detailed trace-level messages.
    Trace,
    /// Log level for debugging messages.
    Debug,
    /// Log level for warnings.
    Warn,
    /// Log level for errors.
    Error,
}

impl std::str::FromStr for LogLevel {
    type Err = String;

    /// Converts a string into a `LogLevel`. The string is case-insensitive and
    /// should match one of the log levels: info, trace, debug, warn, or error.
    ///
    /// # Errors
    /// Returns an error if the input string does not match a valid log level.
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "info" => Ok(LogLevel::Info),
            "trace" => Ok(LogLevel::Trace),
            "debug" => Ok(LogLevel::Debug),
            "warn" => Ok(LogLevel::Warn),
            "error" => Ok(LogLevel::Error),
            _ => Err(format!("Invalid log level: {}", s)),
        }
    }
}

impl std::fmt::Display for LogLevel {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            LogLevel::Info => write!(f, "info"),
            LogLevel::Trace => write!(f, "trace"),
            LogLevel::Debug => write!(f, "debug"),
            LogLevel::Warn => write!(f, "warn"),
            LogLevel::Error => write!(f, "error"),
        }
    }
}


/// Initializes a new tracing-based logger with the provided `LogLevel`.
///
/// This function configures a logger using the `tracing-subscriber` crate,
/// enabling structured logging with different log levels. The logger includes
/// thread information, target, and level in its output, using a compact format.
///
/// # Parameters
/// - `log_level`: The level of logging that should be used. This can be one of
///   `Info`, `Debug`, `Error`, `Warn`, or `Trace`.
///
/// # Example
/// ```rust
/// use multitool::logger::tracer_logger::{LogLevel, new_tracer_logger};
/// use log::info;
///
/// fn main() {
///     new_tracer_logger(LogLevel::Info);
///     info!("Hello, world!")
///     // Your application logic here...
/// }
/// ```
pub fn new_tracer_logger(log_level: LogLevel) {
    let log_level_filter = match log_level {
        LogLevel::Trace => LevelFilter::TRACE,
        LogLevel::Debug => LevelFilter::DEBUG,
        LogLevel::Info => LevelFilter::INFO,
        LogLevel::Warn => LevelFilter::WARN,
        LogLevel::Error => LevelFilter::ERROR,
    };

    let format = fmt::format()
        .with_level(true) // include levels in formatted output
        .with_target(true) // include targets
        .with_thread_ids(true) // include the thread ID of the current thread
        .with_thread_names(true) // include the name of the current thread
        .compact(); // use the `Compact` formatting style.

    tracing_subscriber::fmt()
        .event_format(format)
        .with_max_level(log_level_filter)
        .init();
}

#[cfg(test)]
mod tests {
    use std::str::FromStr;
    use super::*;

    /// Test parsing valid log levels from strings.
    #[test]
    fn test_from_str_valid_levels() {
        assert_eq!(LogLevel::from_str("info").unwrap(), LogLevel::Info);
        assert_eq!(LogLevel::from_str("trace").unwrap(), LogLevel::Trace);
        assert_eq!(LogLevel::from_str("debug").unwrap(), LogLevel::Debug);
        assert_eq!(LogLevel::from_str("warn").unwrap(), LogLevel::Warn);
        assert_eq!(LogLevel::from_str("error").unwrap(), LogLevel::Error);
    }

    /// Test converting log levels to string format.
    #[test]
    fn test_display() {
        assert_eq!(LogLevel::Info.to_string(), "info");
        assert_eq!(LogLevel::Trace.to_string(), "trace");
        assert_eq!(LogLevel::Debug.to_string(), "debug");
        assert_eq!(LogLevel::Warn.to_string(), "warn");
        assert_eq!(LogLevel::Error.to_string(), "error");
    }

    /// Test parsing invalid log levels from strings.
    #[test]
    fn test_from_str_invalid_level() {
        assert!(LogLevel::from_str("invalid").is_err());
    }
}