spotflow_logger/
logline.rs

1use std::collections::HashMap;
2
3#[derive(Default)]
4pub struct LogLine {
5    pub(crate) message: String,
6    pub(crate) severity: Option<Severity>,
7    pub(crate) target: Option<String>,
8    pub(crate) file: Option<String>,
9    pub(crate) line: Option<u32>,
10    pub(crate) labels: HashMap<LabelName, LabelValue>,
11}
12
13impl LogLine {
14    pub fn new<S>(
15        severity: Option<Severity>,
16        message: S,
17        target: Option<String>,
18        file: Option<String>,
19        line: Option<u32>,
20    ) -> Self
21    where
22        S: Into<String>,
23    {
24        Self {
25            message: message.into(),
26            severity,
27            target,
28            file,
29            line,
30            labels: HashMap::new(),
31        }
32    }
33
34    pub fn add_label<K, V>(&mut self, name: K, value: V)
35    where
36        K: Into<LabelName>,
37        V: Into<LabelValue>,
38    {
39        _ = self.labels.insert(name.into(), value.into());
40    }
41
42    // TODO: comment that message is process differently than other props
43    #[cfg(feature = "tracing")]
44    fn add_property<K, V>(&mut self, key: K, value: V)
45    where
46        K: Into<LabelName>,
47        V: Into<LabelValue>,
48    {
49        let key = key.into();
50        let value = value.into();
51
52        if key == "message" {
53            self.message = if let LabelValue::String(message) = value {
54                message
55            } else {
56                "".into()
57            };
58        } else {
59            self.add_label(key, value);
60        }
61    }
62}
63
64#[cfg(feature = "tracing")]
65impl From<&tracing_core::Event<'_>> for LogLine {
66    fn from(event: &tracing_core::Event) -> Self {
67        let metadata = event.metadata();
68        let log_level = metadata.level().into();
69
70        let mut this = Self {
71            severity: Some(log_level),
72            target: Some(metadata.target().into()),
73            file: metadata.file().map(String::from),
74            line: metadata.line(),
75            ..Default::default()
76        };
77        event.record(&mut this);
78
79        this
80    }
81}
82
83#[cfg(feature = "tracing")]
84impl tracing::field::Visit for LogLine {
85    fn record_f64(&mut self, field: &tracing_core::Field, value: f64) {
86        self.add_property(field, value);
87    }
88
89    fn record_i64(&mut self, field: &tracing_core::Field, value: i64) {
90        self.add_property(field, value);
91    }
92
93    fn record_u64(&mut self, field: &tracing_core::Field, value: u64) {
94        self.add_property(field, value);
95    }
96
97    fn record_str(&mut self, field: &tracing_core::Field, value: &str) {
98        self.add_property(field, value);
99    }
100
101    fn record_i128(&mut self, field: &tracing_core::Field, value: i128) {
102        self.add_property(field, value);
103    }
104
105    fn record_u128(&mut self, field: &tracing_core::Field, value: u128) {
106        self.add_property(field, value);
107    }
108
109    fn record_bool(&mut self, field: &tracing_core::Field, value: bool) {
110        self.add_property(field, value);
111    }
112
113    // fn record_bytes(&mut self, field: &tracing_core::Field, value: &[u8]) {}
114
115    fn record_error(
116        &mut self,
117        field: &tracing_core::Field,
118        value: &(dyn core::error::Error + 'static),
119    ) {
120        self.add_property(field, format!("{}", value));
121    }
122
123    fn record_debug(&mut self, field: &tracing_core::Field, value: &dyn std::fmt::Debug) {
124        self.add_property(field, format!("{:?}", value));
125    }
126}
127
128#[derive(Eq, PartialEq, Hash)]
129pub struct LabelName(String);
130
131impl From<LabelName> for String {
132    fn from(value: LabelName) -> Self {
133        value.0
134    }
135}
136
137impl PartialEq<&str> for LabelName {
138    fn eq(&self, other: &&str) -> bool {
139        self.0 == *other
140    }
141}
142
143#[cfg(feature = "tracing")]
144impl From<&tracing_core::Field> for LabelName {
145    fn from(value: &tracing_core::Field) -> Self {
146        Self(value.name().into())
147    }
148}
149
150impl From<String> for LabelName {
151    fn from(value: String) -> Self {
152        Self(value)
153    }
154}
155
156impl From<&str> for LabelName {
157    fn from(value: &str) -> Self {
158        Self(value.into())
159    }
160}
161
162#[allow(non_camel_case_types)]
163pub enum LabelValue {
164    f64(f64),
165    i64(i64),
166    u64(u64),
167    i128(i128),
168    u128(u128),
169    bool(bool),
170    String(String),
171}
172
173macro_rules! impl_from_for_prop_value {
174    [$($type:ident),+] => {
175        $(
176            impl From<$type> for LabelValue {
177                fn from(value: $type) -> Self {
178                    Self::$type(value)
179                }
180            }
181        )+
182    };
183}
184
185impl From<&str> for LabelValue {
186    fn from(value: &str) -> Self {
187        Self::String(value.to_string())
188    }
189}
190
191impl_from_for_prop_value![f64, i64, u64, i128, u128, bool, String];
192
193#[derive(Clone, Copy)]
194pub enum Severity {
195    _Critical,
196    Error,
197    Warning,
198    Info,
199    Debug,
200}
201
202#[cfg(feature = "tracing")]
203impl From<&tracing::Level> for Severity {
204    fn from(value: &tracing::Level) -> Self {
205        use tracing::Level;
206
207        // TODO: consider letting user to specify his/her mapping
208        match *value {
209            Level::TRACE => Self::Debug,
210            Level::DEBUG => Self::Debug,
211            Level::INFO => Self::Info,
212            Level::WARN => Self::Warning,
213            Level::ERROR => Self::Error,
214        }
215    }
216}