Skip to content

Commit 214c2de

Browse files
committed
[trace-guest] Define a common log level filter to be used for tracing and logging
- Adds a `GuestLogFilter` enum that is used as an intermediary type between the `u64` used as a register to call the guest entrypoint and the `log::LogFilter` or `tracing_core::LogFilter`. - Adds unit tests for the `GuestLogFilter` Signed-off-by: Doru Blânzeanu <dblnz@pm.me>
1 parent dee1529 commit 214c2de

17 files changed

Lines changed: 288 additions & 89 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/hyperlight_common/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ tracing = { version = "0.1.44", optional = true }
2222
arbitrary = {version = "1.4.2", optional = true, features = ["derive"]}
2323
spin = "0.10.0"
2424
thiserror = { version = "2.0.18", default-features = false }
25+
tracing-core = { version = "0.1.36", default-features = false }
2526

2627
[features]
2728
default = ["tracing"]

src/hyperlight_common/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ mod flatbuffers;
3030
// cbindgen:ignore
3131
pub mod layout;
3232

33+
// cbindgen:ignore
34+
pub mod log_level;
35+
3336
/// cbindgen:ignore
3437
pub mod mem;
3538

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

src/hyperlight_guest_bin/src/guest_logger.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ use crate::GUEST_HANDLE;
2424
// this is private on purpose so that `log` can only be called though the `log!` macros.
2525
struct GuestLogger {}
2626

27-
pub(crate) fn init_logger(level: LevelFilter) {
27+
pub(crate) fn init_logger(filter: LevelFilter) {
2828
// if this `expect` fails we have no way to recover anyway, so we actually prefer a panic here
2929
// below temporary guest logger is promoted to static by the compiler.
3030
log::set_logger(&GuestLogger {}).expect("unable to setup guest logger");
31-
log::set_max_level(level);
31+
log::set_max_level(filter);
3232
}
3333

3434
impl log::Log for GuestLogger {

src/hyperlight_guest_bin/src/lib.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ use guest_function::call::dispatch_function;
2525
use guest_function::register::GuestFunctionRegister;
2626
use guest_logger::init_logger;
2727
use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
28+
use hyperlight_common::log_level::GuestLogFilter;
2829
use hyperlight_common::mem::HyperlightPEB;
2930
#[cfg(feature = "mem_profile")]
3031
use hyperlight_common::outb::OutBAction;
3132
use hyperlight_guest::exit::write_abort;
3233
use hyperlight_guest::guest_handle::handle::GuestHandle;
33-
use log::LevelFilter;
3434

3535
// === Modules ===
3636
#[cfg_attr(target_arch = "x86_64", path = "arch/amd64/mod.rs")]
@@ -212,15 +212,17 @@ pub(crate) extern "C" fn generic_init(peb_address: u64, seed: u64, ops: u64, max
212212
}
213213

214214
// set up the logger
215-
let max_log_level = LevelFilter::iter()
216-
.nth(max_log_level as usize)
217-
.expect("Invalid log level");
218-
init_logger(max_log_level);
215+
let guest_log_level_filter =
216+
GuestLogFilter::try_from(max_log_level).expect("Invalid log level");
217+
init_logger(guest_log_level_filter.into());
219218

220219
// It is important that all the tracing events are produced after the tracing is initialized.
221220
#[cfg(feature = "trace_guest")]
222-
if max_log_level != LevelFilter::Off {
223-
hyperlight_guest_tracing::init_guest_tracing(guest_start_tsc, max_log_level);
221+
if guest_log_level_filter != GuestLogFilter::Off {
222+
hyperlight_guest_tracing::init_guest_tracing(
223+
guest_start_tsc,
224+
guest_log_level_filter.into(),
225+
);
224226
}
225227

226228
#[cfg(feature = "macros")]

src/hyperlight_guest_tracing/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ description = """Provides the tracing functionality for the hyperlight guest."""
1212
[dependencies]
1313
hyperlight-common = { workspace = true, default-features = false }
1414
spin = "0.10.0"
15-
tracing = { version = "0.1.44", default-features = false, features = ["attributes", "log"] }
15+
tracing = { version = "0.1.44", default-features = false, features = ["attributes"] }
1616
tracing-core = { version = "0.1.36", default-features = false }
1717

1818
[lints]

src/hyperlight_guest_tracing/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ mod trace {
4848
use alloc::sync::{Arc, Weak};
4949

5050
use spin::Mutex;
51-
use tracing::log::LevelFilter;
51+
use tracing_core::LevelFilter;
5252

5353
use crate::state::GuestState;
5454
use crate::subscriber::GuestSubscriber;

src/hyperlight_guest_tracing/src/subscriber.rs

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,25 +35,12 @@ pub(crate) struct GuestSubscriber {
3535
max_log_level: LevelFilter,
3636
}
3737

38-
/// Converts a `tracing::log::LevelFilter` to a `tracing_core::LevelFilter`
39-
/// Used to check if an event should be recorded based on the maximum log level
40-
fn convert_level_filter(filter: tracing::log::LevelFilter) -> tracing_core::LevelFilter {
41-
match filter {
42-
tracing::log::LevelFilter::Off => tracing_core::LevelFilter::OFF,
43-
tracing::log::LevelFilter::Error => tracing_core::LevelFilter::ERROR,
44-
tracing::log::LevelFilter::Warn => tracing_core::LevelFilter::WARN,
45-
tracing::log::LevelFilter::Info => tracing_core::LevelFilter::INFO,
46-
tracing::log::LevelFilter::Debug => tracing_core::LevelFilter::DEBUG,
47-
tracing::log::LevelFilter::Trace => tracing_core::LevelFilter::TRACE,
48-
}
49-
}
50-
5138
impl GuestSubscriber {
5239
/// Creates a new `GuestSubscriber` with the given guest start TSC and maximum log level
53-
pub(crate) fn new(guest_start_tsc: u64, max_log_level: tracing::log::LevelFilter) -> Self {
40+
pub(crate) fn new(guest_start_tsc: u64, filter: LevelFilter) -> Self {
5441
Self {
5542
state: Arc::new(Mutex::new(GuestState::new(guest_start_tsc))),
56-
max_log_level: convert_level_filter(max_log_level),
43+
max_log_level: filter,
5744
}
5845
}
5946
/// Returns a reference to the internal state of the subscriber

0 commit comments

Comments
 (0)