Skip to main content

rlg/
log_level.rs

1// log_level.rs
2// Copyright © 2024-2026 RustLogs (RLG). All rights reserved.
3// SPDX-License-Identifier: Apache-2.0
4// SPDX-License-Identifier: MIT
5
6// Import necessary traits and modules.
7use serde::{Deserialize, Serialize};
8use std::{convert::TryFrom, error::Error, fmt, str::FromStr};
9
10/// Custom error type for `LogLevel` parsing with context.
11#[derive(Debug, Clone)]
12pub struct ParseLogLevelError {
13    /// The invalid log level value.
14    pub invalid_value: String,
15}
16
17impl ParseLogLevelError {
18    /// Creates a new instance of `ParseLogLevelError` with the given invalid log level value.
19    ///
20    /// # Arguments
21    ///
22    /// * `invalid_value` - A reference to a string representing the invalid log level value.
23    ///
24    /// # Returns
25    ///
26    /// A new instance of `ParseLogLevelError` containing the provided invalid log level value.
27    #[must_use]
28    pub fn new(invalid_value: &str) -> Self {
29        Self {
30            invalid_value: invalid_value.to_string(),
31        }
32    }
33}
34
35impl fmt::Display for ParseLogLevelError {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        write!(f, "Invalid log level: {0}", self.invalid_value)
38    }
39}
40
41impl Error for ParseLogLevelError {}
42
43/// An enumeration of the different levels that a log message can have, ordered by severity.
44#[derive(
45    Clone,
46    Copy,
47    Debug,
48    Default,
49    Deserialize,
50    Eq,
51    Hash,
52    Ord,
53    PartialEq,
54    PartialOrd,
55    Serialize,
56)]
57pub enum LogLevel {
58    /// `ALL`: The log level includes all levels.
59    ALL,
60    /// `NONE`: No logging.
61    NONE,
62    /// `DISABLED`: Logging is disabled.
63    DISABLED,
64    /// `TRACE`: Finer-grained informational events than `DEBUG`.
65    TRACE,
66    /// `DEBUG`: Debugging information, typically useful for developers.
67    DEBUG,
68    /// `VERBOSE`: Detailed logging, often more detailed than `INFO`.
69    VERBOSE,
70    /// `INFO`: Informational messages that highlight the progress of the application.
71    #[default]
72    INFO,
73    /// `WARN`: Potentially harmful situations.
74    WARN,
75    /// `ERROR`: Error events that might still allow the application to continue running.
76    ERROR,
77    /// `FATAL`: Very severe error events that will presumably lead the application to abort.
78    FATAL,
79    /// `CRITICAL`: Critical conditions, often requiring immediate attention.
80    CRITICAL,
81}
82
83macro_rules! define_log_levels {
84    ( $( $variant:ident, $num:expr, $upper:expr, $lower:expr );+ $(;)? ) => {
85        impl LogLevel {
86            /// Converts the log level to its corresponding numeric value, similar to syslog severity levels.
87            ///
88            /// # Examples
89            ///
90            /// ```
91            /// use rlg::log_level::LogLevel;
92            /// assert_eq!(LogLevel::ERROR.to_numeric(), 8);
93            /// assert_eq!(LogLevel::DEBUG.to_numeric(), 4);
94            /// ```
95            #[must_use]
96            pub const fn to_numeric(self) -> u8 {
97                match self { $( Self::$variant => $num, )+ }
98            }
99
100            /// Returns the uppercase string representation of the log level.
101            #[must_use]
102            pub const fn as_str(&self) -> &'static str {
103                match self { $( Self::$variant => $upper, )+ }
104            }
105
106            /// Returns the lowercase string representation of the log level.
107            #[must_use]
108            pub const fn as_str_lowercase(&self) -> &'static str {
109                match self { $( Self::$variant => $lower, )+ }
110            }
111
112            /// Creates a `LogLevel` from a numeric value, similar to syslog severity levels.
113            ///
114            /// # Arguments
115            ///
116            /// * `value` - The numeric value to convert.
117            ///
118            /// # Examples
119            ///
120            /// ```
121            /// use rlg::log_level::LogLevel;
122            /// assert_eq!(LogLevel::from_numeric(8), Some(LogLevel::ERROR));
123            /// assert_eq!(LogLevel::from_numeric(5), Some(LogLevel::VERBOSE));
124            /// ```
125            #[must_use]
126            pub const fn from_numeric(value: u8) -> Option<Self> {
127                match value {
128                    $( $num => Some(Self::$variant), )+
129                    _ => None,
130                }
131            }
132        }
133
134        impl FromStr for LogLevel {
135            type Err = ParseLogLevelError;
136
137            fn from_str(s: &str) -> Result<Self, Self::Err> {
138                match s.to_uppercase().as_str() {
139                    $( $upper => Ok(Self::$variant), )+
140                    _ => Err(ParseLogLevelError::new(s)),
141                }
142            }
143        }
144    };
145}
146
147define_log_levels! {
148    ALL, 0, "ALL", "all";
149    NONE, 1, "NONE", "none";
150    DISABLED, 2, "DISABLED", "disabled";
151    TRACE, 3, "TRACE", "trace";
152    DEBUG, 4, "DEBUG", "debug";
153    VERBOSE, 5, "VERBOSE", "verbose";
154    INFO, 6, "INFO", "info";
155    WARN, 7, "WARN", "warn";
156    ERROR, 8, "ERROR", "error";
157    FATAL, 9, "FATAL", "fatal";
158    CRITICAL, 10, "CRITICAL", "critical";
159}
160
161impl LogLevel {
162    /// Checks if the current log level includes another log level.
163    ///
164    /// # Arguments
165    ///
166    /// * `other` - The log level to compare with.
167    ///
168    /// # Examples
169    ///
170    /// ```
171    /// use rlg::log_level::LogLevel;
172    /// assert!(LogLevel::ERROR.includes(LogLevel::DEBUG)); // ERROR includes DEBUG
173    /// assert!(!LogLevel::DEBUG.includes(LogLevel::WARN)); // DEBUG does not include WARN
174    /// assert!(LogLevel::WARN.includes(LogLevel::DEBUG)); // WARN includes DEBUG
175    /// ```
176    #[must_use]
177    pub const fn includes(self, other: Self) -> bool {
178        match self {
179            Self::ALL => true,   // ALL includes everything
180            Self::NONE => false, // NONE includes nothing
181            _ => self.to_numeric() >= other.to_numeric(), // Default behavior for other levels
182        }
183    }
184}
185
186impl TryFrom<String> for LogLevel {
187    type Error = ParseLogLevelError;
188
189    fn try_from(value: String) -> Result<Self, Self::Error> {
190        Self::from_str(&value)
191    }
192}
193
194impl fmt::Display for LogLevel {
195    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196        f.write_str(self.as_str())
197    }
198}