ipmi_rs/
fmt.rs

1#[derive(Debug, Clone)]
2pub enum LogOutput {
3    Log(log::Level),
4    LogTarget(log::Level, String),
5    StdOut,
6    StdErr,
7    #[cfg(feature = "log-to-file")]
8    File(std::sync::Arc<parking_lot::Mutex<std::fs::File>>),
9}
10
11impl From<log::Level> for LogOutput {
12    fn from(value: log::Level) -> Self {
13        Self::Log(value)
14    }
15}
16
17impl LogOutput {
18    fn print(&self, msg: &str) {
19        match self {
20            LogOutput::Log(level) => log::log!(*level, "{}", msg),
21            LogOutput::LogTarget(level, target) => {
22                log::log!(target: target, *level, "{}", msg)
23            }
24            LogOutput::StdOut => println!("{}", msg),
25            LogOutput::StdErr => eprintln!("{}", msg),
26            #[cfg(feature = "log-to-file")]
27            LogOutput::File(file) => {
28                use std::io::Write;
29
30                let mut file = file.lock();
31                file.write_all(msg.as_bytes()).ok();
32                file.write_all(b"\n").ok();
33            }
34        }
35    }
36}
37
38#[derive(Debug)]
39pub struct LogItem {
40    level: usize,
41    title: String,
42    value: Option<String>,
43}
44
45impl LogItem {
46    pub fn new<T: Into<String>, V: Into<String>>(level: usize, title: T, value: Option<V>) -> Self {
47        Self {
48            level,
49            title: title.into(),
50            value: value.map(Into::into),
51        }
52    }
53}
54
55impl<T: ToString, V: ToString> From<(usize, T, V)> for LogItem {
56    fn from((level, title, value): (usize, T, V)) -> Self {
57        Self::new(level, title.to_string(), Some(value.to_string()))
58    }
59}
60
61impl<T: ToString> From<(usize, T)> for LogItem {
62    fn from((level, value): (usize, T)) -> Self {
63        Self::new::<_, String>(level, value.to_string(), None)
64    }
65}
66
67pub struct Logger;
68
69impl Logger {
70    pub fn log<T>(output: &LogOutput, loggable: &T)
71    where
72        T: Loggable,
73    {
74        Self::log_impl(output, &loggable.as_log())
75    }
76
77    fn log_impl(output: &LogOutput, items: &[LogItem]) {
78        // TODO: support log items with more than 2 steps of log levels
79        if let Some(v) = items.first() {
80            output.print(&v.title)
81        }
82
83        let right_align = items
84            .iter()
85            .skip(1)
86            .map(|v| v.title.len())
87            .max()
88            .unwrap_or(0);
89
90        items.iter().skip(1).for_each(|i| {
91            let LogItem {
92                level,
93                title,
94                value,
95            } = i;
96
97            let front_padding: String = (0..level * 2).map(|_| ' ').collect();
98
99            let (value, value_padding) = if let Some(value) = value {
100                let value_padding: String = (0..(right_align - title.len())).map(|_| ' ').collect();
101                (value.as_str(), value_padding)
102            } else {
103                ("", String::new())
104            };
105
106            let message = format!("{front_padding}{title}: {value_padding}{value}");
107            output.print(&message);
108        })
109    }
110}
111
112pub trait Loggable {
113    fn as_log(&self) -> Vec<LogItem>;
114}
115
116#[macro_export]
117macro_rules ! log_vec {
118    [$($msg:tt)*] => {
119        $crate::to_log!(vec: $($msg)*)
120    }
121}
122
123#[macro_export]
124macro_rules! to_log {
125    ([$($array:tt)*],) => {
126        vec![$($array)*]
127    };
128
129    ([$($array:tt)*], ($level:literal, $title:expr, $value:expr)) => {
130        $crate::to_log!([$($array)* ($level, $title, $value).into(),],)
131    };
132
133    ([$($array:tt)*], ($level:literal, $title:expr)) => {
134        $crate::to_log!([$($array)* ($level, $title, "").into(),],)
135    };
136
137    ([$($array:tt)*], ($level:literal, $title:expr, $value:expr), $($msg:tt)*) => {
138        $crate::to_log!([$($array)* ($level, $title, $value).into(),], $($msg)*)
139    };
140
141    ([$($array:tt)*], ($level:literal, $title:expr), $($msg:tt)*) => {
142        $crate::to_log!([$($array)* ($level, $title, "").into(),], $($msg)*)
143    };
144
145    (vec: $($msg:tt)*) => {
146        $crate::to_log!([], $($msg)*)
147    };
148}