1use std::{
4 borrow::Cow,
5 collections::{
6 HashMap,
7 HashSet,
8 },
9 result,
10 str::FromStr,
11};
12
13#[cfg(feature = "schemars")]
14use schemars::JsonSchema;
15#[cfg(feature = "serde")]
16use serde::{
17 Deserialize,
18 Deserializer,
19 Serialize,
20 Serializer,
21};
22use smart_default::SmartDefault;
23
24use crate::error::{
25 Error,
26 Result,
27};
28
29#[derive(PartialEq, Eq, Clone, Debug)]
32#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
33#[cfg_attr(feature = "schemars", derive(JsonSchema))]
34pub struct Config {
35 #[cfg_attr(feature = "serde", serde(rename = "root", alias = "default"))]
37 pub default: Logger,
38 #[cfg_attr(
41 feature = "in-order-serialization",
42 serde(serialize_with = "ordered_map")
43 )]
44 pub appenders: HashMap<AppenderId, Appender>,
45 #[cfg_attr(
48 feature = "in-order-serialization",
49 serde(serialize_with = "ordered_map")
50 )]
51 pub loggers: HashMap<Target, Logger>,
52}
53
54#[cfg(feature = "in-order-serialization")]
57pub fn ordered_map<K, V, S>(
58 value: &HashMap<K, V>,
59 serializer: S,
60) -> std::result::Result<S::Ok, S::Error>
61where
62 K: Ord + Serialize,
63 V: Serialize,
64 S: Serializer,
65{
66 let ordered: std::collections::BTreeMap<_, _> = value.iter().collect();
67 ordered.serialize(serializer)
68}
69
70#[cfg(feature = "in-order-serialization")]
73pub fn ordered_set<K, S>(value: &HashSet<K>, serializer: S) -> std::result::Result<S::Ok, S::Error>
74where
75 K: Ord + Serialize,
76 S: Serializer,
77{
78 let ordered: std::collections::BTreeSet<_> = value.iter().collect();
79 ordered.serialize(serializer)
80}
81
82impl Default for Config {
83 fn default() -> Self {
84 Self::console_config()
85 }
86}
87
88impl Config {
89 fn console_config() -> Config {
91 use literally::{
92 hmap,
93 hset,
94 };
95
96 Config {
97 default: Logger {
98 level: LevelFilter::INFO,
99 appenders: hset! { "stdout" },
100 format: Format::default(),
101 },
102 loggers: hmap! {},
103 appenders: hmap! {
104 "stdout" => Appender::Console
105 },
106 }
107 }
108}
109#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
112#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
113#[cfg_attr(feature = "schemars", derive(JsonSchema))]
114pub struct Target(pub String);
115impl Target {
116 #[must_use]
117 pub fn as_str(&self) -> &str {
118 &self.0
119 }
120}
121impl From<&str> for Target {
122 fn from(s: &str) -> Self {
123 Target(s.to_string())
124 }
125}
126impl ToString for Target {
127 fn to_string(&self) -> String {
128 self.0.clone()
129 }
130}
131
132#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
135#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
136#[cfg_attr(feature = "schemars", derive(JsonSchema))]
137pub struct AppenderId(pub String);
138
139#[derive(PartialEq, Eq, Clone, Debug)]
141#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
142#[cfg_attr(feature = "schemars", derive(JsonSchema))]
143pub struct Logger {
144 #[cfg_attr(
145 feature = "in-order-serialization",
146 serde(serialize_with = "ordered_set")
147 )]
148 pub appenders: HashSet<AppenderId>,
149 pub level: LevelFilter,
150 #[cfg_attr(
151 feature = "serde",
152 serde(default = "Format::default", skip_serializing_if = "Format::is_normal")
153 )]
154 pub format: Format,
155}
156
157#[cfg(feature = "serde")]
158macro_rules! named_unit_variant {
159 ($variant:ident) => {
160 pub mod $variant {
161 pub fn serialize<S>(serializer: S) -> Result<S::Ok, S::Error>
162 where
163 S: serde::Serializer,
164 {
165 serializer.serialize_str(stringify!($variant))
166 }
167
168 pub fn deserialize<'de, D>(deserializer: D) -> Result<(), D::Error>
169 where
170 D: serde::Deserializer<'de>,
171 {
172 struct V;
173 impl<'de> serde::de::Visitor<'de> for V {
174 type Value = ();
175
176 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
177 f.write_str(concat!("\"", stringify!($variant), "\""))
178 }
179
180 fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
181 if value == stringify!($variant) {
182 Ok(())
183 } else {
184 Err(E::invalid_value(serde::de::Unexpected::Str(value), &self))
185 }
186 }
187 }
188 deserializer.deserialize_str(V)
189 }
190 }
191 };
192}
193
194#[cfg(feature = "serde")]
195mod format {
196 named_unit_variant!(normal);
197 named_unit_variant!(messageonly);
198
199 pub mod custom {
200 pub fn serialize<S>(value: &str, serializer: S) -> Result<S::Ok, S::Error>
201 where
202 S: serde::Serializer,
203 {
204 serializer.serialize_str(value)
205 }
206
207 pub fn deserialize<'de, D>(deserializer: D) -> Result<String, D::Error>
208 where
209 D: serde::Deserializer<'de>,
210 {
211 struct V;
212 impl<'de> serde::de::Visitor<'de> for V {
213 type Value = String;
214
215 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
216 f.write_str(concat!(r#"{ "custom": "<format string>" }"#))
217 }
218
219 fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
220 if value != "messageonly" && value != "normal" {
221 Ok(value.to_string())
222 } else {
223 Err(E::invalid_value(serde::de::Unexpected::Str(value), &self))
224 }
225 }
226 }
227 deserializer.deserialize_str(V)
228 }
229 }
230}
231
232#[derive(PartialEq, Eq, Clone, Debug, SmartDefault)]
233#[cfg_attr(
234 feature = "serde",
235 derive(Serialize, Deserialize),
236 serde(untagged, rename_all = "lowercase")
237)]
238#[cfg_attr(feature = "schemars", derive(JsonSchema))]
239pub enum Format {
240 #[default]
241 #[cfg_attr(feature = "serde", serde(with = "format::normal"))]
242 #[cfg_attr(feature = "schemars", schemars(with = "String"))]
243 Normal,
244 #[cfg_attr(feature = "serde", serde(with = "format::messageonly"))]
245 #[cfg_attr(feature = "schemars", schemars(with = "String"))]
246 MessageOnly,
247 #[cfg_attr(feature = "serde", serde(with = "format::custom"))]
248 #[cfg_attr(feature = "schemars", schemars(with = "String"))]
249 Custom(String),
250}
251impl Format {
252 #[cfg(feature = "serde")]
253 #[allow(clippy::trivially_copy_pass_by_ref)]
254 fn is_normal(&self) -> bool {
255 matches!(self, Self::Normal)
256 }
257}
258
259#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
262#[cfg_attr(feature = "schemars", derive(JsonSchema), schemars(transparent))]
263pub struct LevelFilter(
264 #[cfg_attr(feature = "schemars", schemars(with = "String"))]
265 tracing::level_filters::LevelFilter,
266);
267impl From<LevelFilter> for tracing::level_filters::LevelFilter {
268 fn from(l: LevelFilter) -> Self {
269 l.0
270 }
271}
272
273#[rustfmt::skip] impl LevelFilter {
275 pub const TRACE: Self = LevelFilter(tracing::level_filters::LevelFilter::TRACE);
276 pub const DEBUG: Self = LevelFilter(tracing::level_filters::LevelFilter::DEBUG);
277 pub const INFO: Self = LevelFilter(tracing::level_filters::LevelFilter::INFO);
278 pub const WARN: Self = LevelFilter(tracing::level_filters::LevelFilter::WARN);
279 pub const ERROR: Self = LevelFilter(tracing::level_filters::LevelFilter::ERROR);
280 pub const OFF: Self = LevelFilter(tracing::level_filters::LevelFilter::OFF);
281 #[must_use] pub const fn maximum() -> Self {
282 Self::TRACE
283 }
284}
285
286#[cfg(feature = "serde")]
287impl Serialize for LevelFilter {
288 fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
289 where
290 S: Serializer,
291 {
292 serializer.serialize_str(&self.0.to_string().to_ascii_uppercase())
293 }
294}
295#[cfg(feature = "serde")]
296impl<'de> Deserialize<'de> for LevelFilter {
297 fn deserialize<D>(deserializer: D) -> result::Result<Self, D::Error>
298 where
299 D: Deserializer<'de>,
300 {
301 let s = String::deserialize(deserializer)?;
302 FromStr::from_str(&s)
303 .map(Self)
304 .map_err(serde::de::Error::custom)
305 }
306}
307impl FromStr for LevelFilter {
308 type Err = <tracing::level_filters::LevelFilter as FromStr>::Err;
309
310 fn from_str(s: &str) -> result::Result<Self, Self::Err> {
311 Ok(Self(FromStr::from_str(s)?))
312 }
313}
314
315#[derive(Clone, Debug, PartialEq, Eq)]
317#[cfg_attr(feature = "schemars", derive(JsonSchema))]
318#[cfg_attr(
319 feature = "serde",
320 derive(Serialize, Deserialize),
321 serde(tag = "kind", rename_all = "lowercase")
322)]
323pub enum Appender {
324 Null,
325 Console,
326 File {
327 path: String,
328 },
329 RollingFile {
330 path: String,
331 #[cfg_attr(feature = "serde", serde(rename = "rolloverPolicy"))]
332 policy: Policy,
333 },
334}
335
336impl Appender {
337 pub fn file(path: impl Into<String>) -> Self {
338 Self::File { path: path.into() }
339 }
340
341 pub fn console() -> Self {
342 Self::Console
343 }
344}
345impl From<&str> for AppenderId {
346 fn from(s: &str) -> Self {
347 AppenderId(s.to_string())
348 }
349}
350
351#[derive(Clone, Debug, PartialEq, Eq)]
353#[cfg_attr(feature = "schemars", derive(JsonSchema))]
354#[cfg_attr(
355 feature = "serde",
356 derive(Serialize, Deserialize),
357 serde(rename_all = "camelCase")
358)]
359pub struct Policy {
360 pub maximum_file_size: String,
361 pub max_size_roll_backups: u32,
362 #[cfg_attr(
363 feature = "serde",
364 serde(default, skip_serializing_if = "Option::is_none")
365 )]
366 pub pattern: Option<String>,
367}
368
369impl Policy {
370 pub fn calculate_maximum_file_size(size: &str) -> Result<u64> {
385 const KB: u64 = 1024;
386 const MB: u64 = KB * 1024;
387 const GB: u64 = MB * 1024;
388 const TB: u64 = GB * 1024;
389
390 let (number, unit) = match size.find(|c: char| !c.is_ascii_digit()) {
392 Some(n) => {
393 let mut chars = size.chars();
394 let (first, rest) = (
395 chars.by_ref().take(n).collect::<String>(),
396 chars.collect::<String>(),
397 );
398 (
399 Cow::Owned(first.trim().to_string()),
400 Some(rest.trim().to_string()),
401 )
402 },
403 None => (Cow::Borrowed(size.trim()), None),
404 };
405
406 let number = match number.parse::<u64>() {
407 Ok(n) => n,
408 Err(e) => return Err(e.into()),
409 };
410
411 let unit = match unit {
412 Some(u) => u,
413 None => return Ok(number),
414 };
415
416 let bytes_number = if unit.eq_ignore_ascii_case("b") {
417 Some(number)
418 } else if unit.eq_ignore_ascii_case("kb") || unit.eq_ignore_ascii_case("kib") {
419 number.checked_mul(KB)
420 } else if unit.eq_ignore_ascii_case("mb") || unit.eq_ignore_ascii_case("mib") {
421 number.checked_mul(MB)
422 } else if unit.eq_ignore_ascii_case("gb") || unit.eq_ignore_ascii_case("gib") {
423 number.checked_mul(GB)
424 } else if unit.eq_ignore_ascii_case("tb") || unit.eq_ignore_ascii_case("tib") {
425 number.checked_mul(TB)
426 } else {
427 return Err(Error::UnexpectedUnit(unit));
428 };
429
430 match bytes_number {
431 Some(n) => Ok(n),
432 None => Err(Error::Overflow { number, unit }),
433 }
434 }
435}
436
437#[cfg(all(test, feature = "serde"))]
438mod test {
439 use literally::hset;
440
441 use super::{
442 LevelFilter,
443 Logger,
444 };
445 use crate::config::Format;
446
447 #[test]
448 fn test_format_serde() {
449 let lgr = Logger {
450 appenders: hset! {},
451 level: LevelFilter::OFF,
452 format: Format::Normal,
453 };
454 let lgr_value = dbg!(serde_json::to_value(&lgr).unwrap());
455 assert!(lgr_value.get("format").is_none());
456 let lgr_parsed: Logger = serde_json::from_value(lgr_value).unwrap();
457 assert_eq!(lgr_parsed.format, Format::Normal);
458
459 let lgr = Logger {
460 appenders: hset! {},
461 level: LevelFilter::OFF,
462 format: Format::MessageOnly,
463 };
464 let lgr_value = dbg!(serde_json::to_value(&lgr).unwrap());
465 let fmt = lgr_value.get("format").unwrap().as_str().unwrap();
466 assert_eq!(fmt, "messageonly");
467 let lgr_parsed: Logger = serde_json::from_value(lgr_value).unwrap();
468 assert_eq!(lgr_parsed.format, Format::MessageOnly);
469
470 let lgr = Logger {
471 appenders: hset! {},
472 level: LevelFilter::OFF,
473 format: Format::Custom("foobar".to_string()),
474 };
475 let lgr_value = dbg!(serde_json::to_value(&lgr).unwrap());
476 let fmt = lgr_value.get("format").unwrap().as_str().unwrap();
477 assert_eq!(fmt, "foobar");
478 let lgr_parsed: Logger = serde_json::from_value(lgr_value).unwrap();
479 assert_eq!(lgr_parsed.format, Format::Custom("foobar".to_string()));
480 }
481}