Skip to content

Commit facac5d

Browse files
authored
Fix guest tracing filter (#977)
* [trace-guest] Update guest library trace levels and remove explicit parent - To make the codebase more readable, remove the `parent` argument from the `#[instrument]` attribute. Signed-off-by: Doru Blânzeanu <dblnz@pm.me> * [trace-guest] Add trace filtering based on max_log_level - The `max_log_level` argument provided to the guest function is now used to filter traces also Signed-off-by: Doru Blânzeanu <dblnz@pm.me> * [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> * [host] Simplify `get_max_log_level_filter` function - Add more comments and tests to verify the new logic works as expected Signed-off-by: Doru Blânzeanu <dblnz@pm.me> --------- Signed-off-by: Doru Blânzeanu <dblnz@pm.me>
1 parent 7741d51 commit facac5d

20 files changed

Lines changed: 432 additions & 87 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_core::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 log_filter = log::LevelFilter::from(variant);
174+
assert_eq!(log_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/src/guest_handle/host_comm.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ impl GuestHandle {
6565
///
6666
/// When calling `call_host_function<T>`, this function is called
6767
/// internally to get the return value.
68+
#[instrument(skip_all, level = "Trace")]
6869
pub fn get_host_return_value<T: TryFrom<ReturnValue>>(&self) -> Result<T> {
6970
let inner = self
7071
.try_pop_shared_input_data_into::<FunctionCallResult>()
@@ -106,6 +107,7 @@ impl GuestHandle {
106107
///
107108
/// Note: The function return value must be obtained by calling
108109
/// `get_host_return_value`.
110+
#[instrument(skip_all, level = "Trace")]
109111
pub fn call_host_function_without_returning_result(
110112
&self,
111113
function_name: &str,
@@ -139,7 +141,7 @@ impl GuestHandle {
139141
/// sends it to the host, and then retrieves the return value.
140142
///
141143
/// The return value is deserialized into the specified type `T`.
142-
#[instrument(skip_all, level = "Trace")]
144+
#[instrument(skip_all, level = "Info")]
143145
pub fn call_host_function<T: TryFrom<ReturnValue>>(
144146
&self,
145147
function_name: &str,

src/hyperlight_guest/src/guest_handle/io.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ use core::any::type_name;
2020
use core::slice::from_raw_parts_mut;
2121

2222
use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
23-
use tracing::{Span, instrument};
23+
use tracing::instrument;
2424

2525
use super::handle::GuestHandle;
2626
use crate::error::{HyperlightGuestError, Result};
2727

2828
impl GuestHandle {
2929
/// Pops the top element from the shared input data buffer and returns it as a T
30-
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
30+
#[instrument(skip_all, level = "Trace")]
3131
pub fn try_pop_shared_input_data_into<T>(&self) -> Result<T>
3232
where
3333
T: for<'a> TryFrom<&'a [u8]>,

src/hyperlight_guest_bin/src/guest_function/call.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ use hyperlight_common::flatbuffer_wrappers::function_call::{FunctionCall, Functi
2222
use hyperlight_common::flatbuffer_wrappers::function_types::{FunctionCallResult, ParameterType};
2323
use hyperlight_common::flatbuffer_wrappers::guest_error::{ErrorCode, GuestError};
2424
use hyperlight_guest::error::{HyperlightGuestError, Result};
25-
use tracing::{Span, instrument};
25+
use tracing::instrument;
2626

2727
use crate::{GUEST_HANDLE, REGISTERED_GUEST_FUNCTIONS};
2828

29-
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
29+
#[instrument(skip_all, level = "Info")]
3030
pub(crate) fn call_guest_function(function_call: FunctionCall) -> Result<Vec<u8>> {
3131
// Validate this is a Guest Function Call
3232
if function_call.function_call_type() != FunctionCallType::Guest {

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 buddy_system_allocator::LockedHeap;
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")]
@@ -215,15 +215,17 @@ pub(crate) extern "C" fn generic_init(
215215
}
216216

217217
// set up the logger
218-
let max_log_level = LevelFilter::iter()
219-
.nth(max_log_level as usize)
220-
.expect("Invalid log level");
221-
init_logger(max_log_level);
218+
let guest_log_level_filter =
219+
GuestLogFilter::try_from(max_log_level).expect("Invalid log level");
220+
init_logger(guest_log_level_filter.into());
222221

223222
// It is important that all the tracing events are produced after the tracing is initialized.
224223
#[cfg(feature = "trace_guest")]
225-
if max_log_level != LevelFilter::Off {
226-
hyperlight_guest_tracing::init_guest_tracing(guest_start_tsc);
224+
if guest_log_level_filter != GuestLogFilter::Off {
225+
hyperlight_guest_tracing::init_guest_tracing(
226+
guest_start_tsc,
227+
guest_log_level_filter.into(),
228+
);
227229
}
228230

229231
// Open a span to partly capture the initialization of the guest.

src/hyperlight_guest_tracing/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ mod trace {
4848
use alloc::sync::{Arc, Weak};
4949

5050
use spin::Mutex;
51+
use tracing_core::LevelFilter;
5152

5253
use crate::state::GuestState;
5354
use crate::subscriber::GuestSubscriber;
@@ -61,12 +62,12 @@ mod trace {
6162
static GUEST_STATE: spin::Once<Weak<Mutex<GuestState>>> = spin::Once::new();
6263

6364
/// Initialize the guest tracing subscriber as global default.
64-
pub fn init_guest_tracing(guest_start_tsc: u64) {
65+
pub fn init_guest_tracing(guest_start_tsc: u64, max_log_level: LevelFilter) {
6566
// Set as global default if not already set.
6667
if tracing_core::dispatcher::has_been_set() {
6768
return;
6869
}
69-
let sub = GuestSubscriber::new(guest_start_tsc);
70+
let sub = GuestSubscriber::new(guest_start_tsc, max_log_level);
7071
let state = sub.state();
7172
// Store state Weak<GuestState> to use later at runtime
7273
GUEST_STATE.call_once(|| Arc::downgrade(state));

0 commit comments

Comments
 (0)