1use serde::de::{self, Deserializer};
7use serde::Deserialize;
8
9#[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 #[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#[derive(Debug, Clone, Copy)]
72pub enum To {
73 Off,
74 File,
75 Console,
76 Both,
77}
78
79impl To {
80 #[inline]
82 pub fn file(&self) -> bool {
83 matches!(self, To::Both | To::File)
84 }
85 #[inline]
87 pub fn console(&self) -> bool {
88 matches!(self, To::Both | To::Console)
89 }
90 #[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#[derive(Debug, Clone, Copy)]
119pub enum Level {
120 Trace,
121 Debug,
122 Info,
123 Warn,
124 Error,
125}
126
127impl Level {
128 #[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}