Skip to main content

hyperlight_common/
log_level.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025 The Hyperlight Authors.
3
4/// This type is a unified definition of log level filters between the guest and host.
5///
6/// This is needed because currently the guest uses both the `log` and `tracing` crates,
7/// and needs each type of `LevelFilter` from both crates.
8///
9/// To avoid as much as possible the amount of conversions between the two types, we define a
10/// single type that can be converted to both `log::LevelFilter` and `tracing_core::LevelFilter`.
11/// NOTE: This also takes care of the fact that the `tracing` and `log` enum types for the log
12/// levels are not guaranteed to have the same discriminants, so we can't just cast between them.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum GuestLogFilter {
15    Off,
16    Error,
17    Warn,
18    Info,
19    Debug,
20    Trace,
21}
22
23impl From<GuestLogFilter> for tracing_core::LevelFilter {
24    fn from(filter: GuestLogFilter) -> Self {
25        match filter {
26            GuestLogFilter::Off => tracing_core::LevelFilter::OFF,
27            GuestLogFilter::Error => tracing_core::LevelFilter::ERROR,
28            GuestLogFilter::Warn => tracing_core::LevelFilter::WARN,
29            GuestLogFilter::Info => tracing_core::LevelFilter::INFO,
30            GuestLogFilter::Debug => tracing_core::LevelFilter::DEBUG,
31            GuestLogFilter::Trace => tracing_core::LevelFilter::TRACE,
32        }
33    }
34}
35
36impl From<GuestLogFilter> for log::LevelFilter {
37    fn from(filter: GuestLogFilter) -> Self {
38        match filter {
39            GuestLogFilter::Off => log::LevelFilter::Off,
40            GuestLogFilter::Error => log::LevelFilter::Error,
41            GuestLogFilter::Warn => log::LevelFilter::Warn,
42            GuestLogFilter::Info => log::LevelFilter::Info,
43            GuestLogFilter::Debug => log::LevelFilter::Debug,
44            GuestLogFilter::Trace => log::LevelFilter::Trace,
45        }
46    }
47}
48
49/// Used by the host to convert a [`tracing_core::LevelFilter`] to the intermediary [`GuestLogFilter`]
50/// filter that is later converted to `u64` and passed to the guest via the C API.
51impl From<tracing_core::LevelFilter> for GuestLogFilter {
52    fn from(value: tracing_core::LevelFilter) -> Self {
53        match value {
54            tracing_core::LevelFilter::OFF => Self::Off,
55            tracing_core::LevelFilter::ERROR => Self::Error,
56            tracing_core::LevelFilter::WARN => Self::Warn,
57            tracing_core::LevelFilter::INFO => Self::Info,
58            tracing_core::LevelFilter::DEBUG => Self::Debug,
59            tracing_core::LevelFilter::TRACE => Self::Trace,
60        }
61    }
62}
63
64/// Used by the guest to convert a `u64` value passed from the host via the C API to the
65/// intermediary [`GuestLogFilter`] filter that is later converted to both
66/// `tracing_core::LevelFilter` and `log::LevelFilter`.
67impl TryFrom<u64> for GuestLogFilter {
68    type Error = ();
69
70    fn try_from(value: u64) -> Result<Self, <GuestLogFilter as TryFrom<u64>>::Error> {
71        match value {
72            0 => Ok(Self::Off),
73            1 => Ok(Self::Error),
74            2 => Ok(Self::Warn),
75            3 => Ok(Self::Info),
76            4 => Ok(Self::Debug),
77            5 => Ok(Self::Trace),
78            _ => Err(()),
79        }
80    }
81}
82
83/// Used by the host to convert the [`GuestLogFilter`] to a `u64` that is passed to the guest via
84/// the C API.
85impl From<GuestLogFilter> for u64 {
86    fn from(value: GuestLogFilter) -> Self {
87        match value {
88            GuestLogFilter::Off => 0,
89            GuestLogFilter::Error => 1,
90            GuestLogFilter::Warn => 2,
91            GuestLogFilter::Info => 3,
92            GuestLogFilter::Debug => 4,
93            GuestLogFilter::Trace => 5,
94        }
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::GuestLogFilter;
101
102    #[test]
103    fn guest_log_filter_u64_roundtrip() {
104        let variants = [
105            GuestLogFilter::Off,
106            GuestLogFilter::Error,
107            GuestLogFilter::Warn,
108            GuestLogFilter::Info,
109            GuestLogFilter::Debug,
110            GuestLogFilter::Trace,
111        ];
112
113        for variant in variants {
114            let as_u64: u64 = variant.into();
115            let back =
116                GuestLogFilter::try_from(as_u64).expect("conversion from u64 should succeed");
117            assert_eq!(variant, back);
118        }
119    }
120
121    #[test]
122    fn guest_log_filter_tracing_roundtrip() {
123        let variants = [
124            GuestLogFilter::Off,
125            GuestLogFilter::Error,
126            GuestLogFilter::Warn,
127            GuestLogFilter::Info,
128            GuestLogFilter::Debug,
129            GuestLogFilter::Trace,
130        ];
131
132        for variant in variants {
133            let tracing_filter: tracing_core::LevelFilter = variant.into();
134            let back: GuestLogFilter = tracing_filter.into();
135            assert_eq!(variant, back);
136        }
137    }
138
139    #[test]
140    fn guest_log_filter_log_conversion() {
141        let variants = [
142            GuestLogFilter::Off,
143            GuestLogFilter::Error,
144            GuestLogFilter::Warn,
145            GuestLogFilter::Info,
146            GuestLogFilter::Debug,
147            GuestLogFilter::Trace,
148        ];
149
150        let log_variants = [
151            log::LevelFilter::Off,
152            log::LevelFilter::Error,
153            log::LevelFilter::Warn,
154            log::LevelFilter::Info,
155            log::LevelFilter::Debug,
156            log::LevelFilter::Trace,
157        ];
158
159        for (variant, log_variant) in variants.into_iter().zip(log_variants) {
160            let log_filter = log::LevelFilter::from(variant);
161            assert_eq!(log_filter, log_variant);
162        }
163    }
164
165    #[test]
166    fn guest_log_filter_try_from_u64_rejects_invalid() {
167        // Any value outside the defined range [0, 5] should be rejected.
168        assert!(GuestLogFilter::try_from(u64::MAX).is_err());
169        assert!(GuestLogFilter::try_from(6).is_err());
170    }
171}