Skip to main content

rmqtt_conf/
logging.rs

1//! Logging configuration for the RMQTT broker.
2//!
3//! Defines log output destination (`To`), severity level (`Level`), and
4//! file path settings.
5
6use serde::de::{self, Deserializer};
7use serde::Deserialize;
8
9/// Log output configuration, including destination, level, and file path.
10#[derive(Debug, Clone, Deserialize)]
11pub struct Log {
12    #[serde(default = "Log::to_default")]
13    pub to: To,
14    #[serde(default = "Log::level_default")]
15    pub level: Level,
16    #[serde(default = "Log::dir_default")]
17    pub dir: String,
18    #[serde(default = "Log::file_default")]
19    pub file: String,
20}
21
22impl Default for Log {
23    #[inline]
24    fn default() -> Self {
25        Self {
26            to: Self::to_default(),
27            level: Self::level_default(),
28            dir: Self::dir_default(),
29            file: Self::file_default(),
30        }
31    }
32}
33
34impl Log {
35    #[inline]
36    fn to_default() -> To {
37        To::Console
38    }
39    #[inline]
40    fn level_default() -> Level {
41        Level::Info
42    }
43    #[inline]
44    fn dir_default() -> String {
45        "/var/log/rmqtt".into()
46    }
47    #[inline]
48    fn file_default() -> String {
49        "rmqtt.log".into()
50    }
51    /// Returns the resolved log file path by joining directory and file name.
52    ///
53    /// Returns an empty string if the file name is empty.
54    #[inline]
55    pub fn filename(&self) -> String {
56        let file = &self.file;
57        if file.is_empty() {
58            return "".into();
59        }
60        if self.dir.is_empty() {
61            return file.to_owned();
62        }
63        let dir = self.dir.trim_end_matches(['/', '\\']);
64        format!("{dir}/{file}")
65    }
66}
67
68/// Log output destination type.
69///
70/// Controls whether logs are written to the console, a file, both, or disabled.
71#[derive(Debug, Clone, Copy)]
72pub enum To {
73    Off,
74    File,
75    Console,
76    Both,
77}
78
79impl To {
80    /// Returns `true` if the destination includes file output.
81    #[inline]
82    pub fn file(&self) -> bool {
83        matches!(self, To::Both | To::File)
84    }
85    /// Returns `true` if the destination includes console output.
86    #[inline]
87    pub fn console(&self) -> bool {
88        matches!(self, To::Both | To::Console)
89    }
90    /// Returns `true` if logging is disabled.
91    #[inline]
92    pub fn off(&self) -> bool {
93        matches!(self, To::Off)
94    }
95}
96
97impl<'de> Deserialize<'de> for To {
98    #[inline]
99    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
100    where
101        D: Deserializer<'de>,
102    {
103        let to = match (String::deserialize(deserializer)?).to_ascii_lowercase().as_str() {
104            "off" => To::Off,
105            "file" => To::File,
106            "console" => To::Console,
107            "both" => To::Both,
108            _ => To::Both,
109        };
110
111        Ok(to)
112    }
113}
114
115/// Log severity level.
116///
117/// Supports standard levels from Trace (most verbose) to Error (least verbose).
118#[derive(Debug, Clone, Copy)]
119pub enum Level {
120    Trace,
121    Debug,
122    Info,
123    Warn,
124    Error,
125}
126
127impl Level {
128    /// Returns the string representation of this log level (e.g. "info", "debug").
129    #[inline]
130    pub fn as_str(&self) -> &'static str {
131        match self {
132            Level::Trace => "trace",
133            Level::Debug => "debug",
134            Level::Info => "info",
135            Level::Warn => "warn",
136            Level::Error => "error",
137        }
138    }
139}
140
141impl std::fmt::Display for Level {
142    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143        f.write_str(self.as_str())
144    }
145}
146
147impl<'de> Deserialize<'de> for Level {
148    #[inline]
149    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
150    where
151        D: Deserializer<'de>,
152    {
153        let level = String::deserialize(deserializer)?;
154        let level = match level.to_ascii_lowercase().as_str() {
155            "trace" => Level::Trace,
156            "debug" => Level::Debug,
157            "info" => Level::Info,
158            "warn" => Level::Warn,
159            "error" => Level::Error,
160            _ => return Err(de::Error::missing_field("level")),
161        };
162        Ok(level)
163    }
164}