Skip to main content

diagnostics_log_types/
lib.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
3
4//! This crate provides the basic types that we rely on for logs.
5
6#![warn(missing_docs)]
7
8use std::str::FromStr;
9use std::{cmp, fmt};
10
11#[cfg(fuchsia_api_level_less_than = "27")]
12use fidl_fuchsia_diagnostics as fdiagnostics;
13#[cfg(fuchsia_api_level_at_least = "27")]
14use fidl_fuchsia_diagnostics_types as fdiagnostics;
15
16#[cfg(feature = "serde")]
17#[doc(hidden)]
18pub mod serde_ext;
19
20/// Severities a log message can have, often called the log's "level".
21#[cfg_attr(feature = "serde", derive(schemars::JsonSchema))]
22#[cfg_attr(feature = "serde", serde(rename_all = "UPPERCASE"))]
23#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
24#[repr(u8)]
25pub enum Severity {
26    /// Trace severity level
27    Trace = 0x10,
28    /// Debug severity level
29    Debug = 0x20,
30    /// Info severity level
31    Info = 0x30,
32    /// Warn severity level
33    Warn = 0x40,
34    /// Error severity level
35    Error = 0x50,
36    /// Fatal severity level
37    Fatal = 0x60,
38}
39
40#[cfg(feature = "serde")]
41impl serde::Serialize for Severity {
42    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
43    where
44        S: serde::Serializer,
45    {
46        serde_ext::severity::serialize(self, serializer)
47    }
48}
49
50#[cfg(feature = "serde")]
51impl<'de> serde::Deserialize<'de> for Severity {
52    fn deserialize<D>(deserializer: D) -> Result<Severity, D::Error>
53    where
54        D: serde::Deserializer<'de>,
55    {
56        serde_ext::severity::deserialize(deserializer)
57    }
58}
59
60impl Severity {
61    /// Returns a severity and also the raw severity if it's  not an exact match of a severity value.
62    pub fn parse_exact(raw_severity: u8) -> (Option<u8>, Severity) {
63        if raw_severity == Severity::Trace as u8 {
64            (None, Severity::Trace)
65        } else if raw_severity == Severity::Debug as u8 {
66            (None, Severity::Debug)
67        } else if raw_severity == Severity::Info as u8 {
68            (None, Severity::Info)
69        } else if raw_severity == Severity::Warn as u8 {
70            (None, Severity::Warn)
71        } else if raw_severity == Severity::Error as u8 {
72            (None, Severity::Error)
73        } else if raw_severity == Severity::Fatal as u8 {
74            (None, Severity::Fatal)
75        } else {
76            (Some(raw_severity), Severity::from(raw_severity))
77        }
78    }
79
80    /// Returns the string representation of a severity.
81    pub fn as_str(&self) -> &'static str {
82        match self {
83            Severity::Trace => "TRACE",
84            Severity::Debug => "DEBUG",
85            Severity::Info => "INFO",
86            Severity::Warn => "WARN",
87            Severity::Error => "ERROR",
88            Severity::Fatal => "FATAL",
89        }
90    }
91}
92
93macro_rules! impl_from_signed {
94    ($($type:ty),*) => {
95        $(
96            impl From<$type> for Severity {
97                fn from(value: $type) -> Severity {
98                    match value {
99                        ..0x00 => Severity::Trace,
100                        0x00..=0x10 => Severity::Trace,
101                        0x11..=0x20 => Severity::Debug,
102                        0x21..=0x30 => Severity::Info,
103                        0x31..=0x40 => Severity::Warn,
104                        0x41..=0x50 => Severity::Error,
105                        0x51.. => Severity::Fatal,
106                    }
107                }
108            }
109        )*
110    }
111}
112
113macro_rules! impl_from_unsigned {
114    ($($type:ty),*) => {
115        $(
116            impl From<$type> for Severity {
117                fn from(value: $type) -> Severity {
118                    match value {
119                        0x00..=0x10 => Severity::Trace,
120                        0x11..=0x20 => Severity::Debug,
121                        0x21..=0x30 => Severity::Info,
122                        0x31..=0x40 => Severity::Warn,
123                        0x41..=0x50 => Severity::Error,
124                        0x51.. => Severity::Fatal,
125                    }
126                }
127            }
128        )*
129    }
130}
131
132impl_from_signed!(i8, i16, i32, i64, i128);
133impl_from_unsigned!(u8, u16, u32, u64, u128);
134
135impl From<log::Level> for Severity {
136    fn from(level: log::Level) -> Severity {
137        match level {
138            log::Level::Trace => Severity::Trace,
139            log::Level::Debug => Severity::Debug,
140            log::Level::Info => Severity::Info,
141            log::Level::Warn => Severity::Warn,
142            log::Level::Error => Severity::Error,
143        }
144    }
145}
146
147impl TryFrom<log::LevelFilter> for Severity {
148    type Error = ();
149    fn try_from(s: log::LevelFilter) -> Result<Severity, ()> {
150        match s {
151            log::LevelFilter::Off => Err(()),
152            log::LevelFilter::Trace => Ok(Severity::Trace),
153            log::LevelFilter::Debug => Ok(Severity::Debug),
154            log::LevelFilter::Info => Ok(Severity::Info),
155            log::LevelFilter::Warn => Ok(Severity::Warn),
156            log::LevelFilter::Error => Ok(Severity::Error),
157        }
158    }
159}
160
161impl From<Severity> for log::LevelFilter {
162    fn from(s: Severity) -> log::LevelFilter {
163        match s {
164            Severity::Trace => log::LevelFilter::Trace,
165            Severity::Debug => log::LevelFilter::Debug,
166            Severity::Info => log::LevelFilter::Info,
167            Severity::Warn => log::LevelFilter::Warn,
168            Severity::Fatal | Severity::Error => log::LevelFilter::Error,
169        }
170    }
171}
172
173impl From<Severity> for fdiagnostics::Severity {
174    fn from(s: Severity) -> fdiagnostics::Severity {
175        match s {
176            Severity::Trace => fdiagnostics::Severity::Trace,
177            Severity::Debug => fdiagnostics::Severity::Debug,
178            Severity::Info => fdiagnostics::Severity::Info,
179            Severity::Warn => fdiagnostics::Severity::Warn,
180            Severity::Error => fdiagnostics::Severity::Error,
181            Severity::Fatal => fdiagnostics::Severity::Fatal,
182        }
183    }
184}
185
186impl From<fdiagnostics::Severity> for Severity {
187    fn from(s: fdiagnostics::Severity) -> Severity {
188        match s {
189            fdiagnostics::Severity::Trace => Severity::Trace,
190            fdiagnostics::Severity::Debug => Severity::Debug,
191            fdiagnostics::Severity::Info => Severity::Info,
192            fdiagnostics::Severity::Warn => Severity::Warn,
193            fdiagnostics::Severity::Error => Severity::Error,
194            fdiagnostics::Severity::Fatal => Severity::Fatal,
195            #[cfg(fuchsia_api_level_at_least = "27")]
196            fdiagnostics::Severity::__SourceBreaking { .. } => Severity::Info,
197        }
198    }
199}
200
201impl AsRef<str> for Severity {
202    fn as_ref(&self) -> &str {
203        self.as_str()
204    }
205}
206
207impl fmt::Display for Severity {
208    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209        write!(f, "{}", self.as_str())
210    }
211}
212
213/// Parsing error for severities.
214#[derive(thiserror::Error, Debug)]
215pub enum Error {
216    /// Attempted to parse a string that didn't map to a valid severity.
217    #[error("invalid severity: {0}")]
218    Invalid(String),
219}
220
221impl FromStr for Severity {
222    type Err = Error;
223    fn from_str(s: &str) -> Result<Self, Self::Err> {
224        let s = s.to_lowercase();
225        match s.as_str() {
226            "trace" => Ok(Severity::Trace),
227            "debug" => Ok(Severity::Debug),
228            "info" => Ok(Severity::Info),
229            "warn" | "warning" => Ok(Severity::Warn),
230            "error" => Ok(Severity::Error),
231            "fatal" => Ok(Severity::Fatal),
232            other => Err(Error::Invalid(other.to_string())),
233        }
234    }
235}
236
237impl PartialEq<fdiagnostics::Severity> for Severity {
238    fn eq(&self, other: &fdiagnostics::Severity) -> bool {
239        matches!(
240            (self, other),
241            (Severity::Trace, fdiagnostics::Severity::Trace)
242                | (Severity::Debug, fdiagnostics::Severity::Debug)
243                | (Severity::Info, fdiagnostics::Severity::Info)
244                | (Severity::Warn, fdiagnostics::Severity::Warn)
245                | (Severity::Error, fdiagnostics::Severity::Error)
246                | (Severity::Fatal, fdiagnostics::Severity::Fatal)
247        )
248    }
249}
250
251impl PartialOrd<fdiagnostics::Severity> for Severity {
252    fn partial_cmp(&self, other: &fdiagnostics::Severity) -> Option<cmp::Ordering> {
253        let other = match other {
254            fdiagnostics::Severity::Trace => Severity::Trace,
255            fdiagnostics::Severity::Debug => Severity::Debug,
256            fdiagnostics::Severity::Info => Severity::Info,
257            fdiagnostics::Severity::Warn => Severity::Warn,
258            fdiagnostics::Severity::Error => Severity::Error,
259            fdiagnostics::Severity::Fatal => Severity::Fatal,
260            #[cfg(fuchsia_api_level_at_least = "27")]
261            fdiagnostics::Severity::__SourceBreaking { .. } => return None,
262        };
263        self.partial_cmp(&other)
264    }
265}
266
267#[cfg(test)]
268mod tests {
269    use super::*;
270
271    #[test]
272    fn from_fdiagnostics_severity() {
273        assert_eq!(Severity::from(fdiagnostics::Severity::Trace), Severity::Trace);
274        assert_eq!(Severity::from(fdiagnostics::Severity::Debug), Severity::Debug);
275        assert_eq!(Severity::from(fdiagnostics::Severity::Info), Severity::Info);
276        assert_eq!(Severity::from(fdiagnostics::Severity::Warn), Severity::Warn);
277        assert_eq!(Severity::from(fdiagnostics::Severity::Error), Severity::Error);
278        assert_eq!(Severity::from(fdiagnostics::Severity::Fatal), Severity::Fatal);
279        #[cfg(fuchsia_api_level_at_least = "27")]
280        assert_eq!(
281            Severity::from(fdiagnostics::Severity::__SourceBreaking { unknown_ordinal: 123 }),
282            Severity::Info
283        );
284    }
285
286    #[test]
287    fn partial_cmp_fdiagnostics_severity() {
288        assert_eq!(
289            Severity::Info.partial_cmp(&fdiagnostics::Severity::Warn),
290            Some(cmp::Ordering::Less)
291        );
292        assert_eq!(
293            Severity::Warn.partial_cmp(&fdiagnostics::Severity::Info),
294            Some(cmp::Ordering::Greater)
295        );
296        assert_eq!(
297            Severity::Info.partial_cmp(&fdiagnostics::Severity::Info),
298            Some(cmp::Ordering::Equal)
299        );
300        #[cfg(fuchsia_api_level_at_least = "27")]
301        assert_eq!(
302            Severity::Info
303                .partial_cmp(&fdiagnostics::Severity::__SourceBreaking { unknown_ordinal: 123 }),
304            None
305        );
306    }
307}