Skip to content

Commit 6e47f8f

Browse files
committed
[trace-guest] Define a common log level filter to be used for tracing and logging
Signed-off-by: Doru Blânzeanu <dblnz@pm.me>
1 parent 887d003 commit 6e47f8f

16 files changed

Lines changed: 205 additions & 83 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: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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+
}

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/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

src/hyperlight_host/src/hypervisor/hyperlight_vm.rs

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,17 @@ limitations under the License.
1818
use std::collections::HashMap;
1919
#[cfg(crashdump)]
2020
use std::path::Path;
21+
use std::str::FromStr;
2122
#[cfg(any(kvm, mshv3))]
2223
use std::sync::atomic::AtomicBool;
2324
use std::sync::atomic::AtomicU8;
2425
#[cfg(any(kvm, mshv3))]
2526
use std::sync::atomic::AtomicU64;
2627
use std::sync::{Arc, Mutex};
2728

28-
use log::LevelFilter;
29+
use hyperlight_common::log_level::GuestLogFilter;
2930
use tracing::{Span, instrument};
31+
use tracing_core::LevelFilter;
3032

3133
#[cfg(gdb)]
3234
use super::gdb::arch::VcpuStopReasonError;
@@ -59,7 +61,7 @@ use crate::hypervisor::virtual_machine::{
5961
HypervisorType, MapMemoryError, RegisterError, RunVcpuError, UnmapMemoryError, VmError, VmExit,
6062
get_available_hypervisor,
6163
};
62-
use crate::hypervisor::{InterruptHandle, InterruptHandleImpl, get_max_log_level};
64+
use crate::hypervisor::{InterruptHandle, InterruptHandleImpl};
6365
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags, MemoryRegionType};
6466
use crate::mem::mgr::SandboxMemoryManager;
6567
use crate::mem::ptr::RawPtr;
@@ -73,6 +75,52 @@ use crate::sandbox::trace::MemTraceInfo;
7375
#[cfg(crashdump)]
7476
use crate::sandbox::uninitialized::SandboxRuntimeConfig;
7577

78+
/// Get the logging level filter to pass to the guest entrypoint
79+
fn get_max_log_level_filter() -> LevelFilter {
80+
// Check to see if the RUST_LOG environment variable is set
81+
// and if so, parse it to get the log_level for hyperlight_guest
82+
// if that is not set get the log level for the hyperlight_host
83+
84+
// This is done as the guest will produce logs based on the log level returned here
85+
// producing those logs is expensive and we don't want to do it if the host is not
86+
// going to process them
87+
88+
let val = std::env::var("RUST_LOG").unwrap_or_default();
89+
90+
let level = if val.contains("hyperlight_guest") {
91+
val.split(',')
92+
.find(|s| s.contains("hyperlight_guest"))
93+
.unwrap_or("")
94+
.split('=')
95+
.nth(1)
96+
.unwrap_or("")
97+
} else if val.contains("hyperlight_host") {
98+
val.split(',')
99+
.find(|s| s.contains("hyperlight_host"))
100+
.unwrap_or("")
101+
.split('=')
102+
.nth(1)
103+
.unwrap_or("")
104+
} else {
105+
// look for a value string that does not contain "="
106+
val.split(',').find(|s| !s.contains("=")).unwrap_or("")
107+
};
108+
109+
tracing::info!("Determined guest log level: {}", level);
110+
// Convert the log level string to a LevelFilter
111+
// If no value is found, default to Error
112+
LevelFilter::from_str(level).unwrap_or(LevelFilter::ERROR)
113+
}
114+
115+
/// Converts a given [`Option<LevelFilter>`] to a `u64` value to be passed to the guest entrypoint
116+
fn get_guest_log_filter(guest_max_log_level: Option<LevelFilter>) -> u64 {
117+
let guest_log_level_filter = match guest_max_log_level {
118+
Some(level) => level,
119+
None => get_max_log_level_filter(),
120+
};
121+
GuestLogFilter::from(guest_log_level_filter).into()
122+
}
123+
76124
/// Represents a Hyperlight Virtual Machine instance.
77125
///
78126
/// This struct manages the lifecycle of the VM, including:
@@ -468,11 +516,6 @@ impl HyperlightVm {
468516

469517
self.page_size = page_size as usize;
470518

471-
let guest_max_log_level: u64 = match guest_max_log_level {
472-
Some(level) => level as u64,
473-
None => get_max_log_level().into(),
474-
};
475-
476519
let regs = CommonRegisters {
477520
rip: entrypoint,
478521
// We usually keep the top of the stack 16-byte
@@ -487,7 +530,7 @@ impl HyperlightVm {
487530
rdi: peb_addr.into(),
488531
rsi: seed,
489532
rdx: page_size.into(),
490-
rcx: guest_max_log_level,
533+
rcx: get_guest_log_filter(guest_max_log_level),
491534
rflags: 1 << 1,
492535

493536
..Default::default()

src/hyperlight_host/src/hypervisor/mod.rs

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
use log::LevelFilter;
18-
1917
/// GDB debugging support
2018
#[cfg(gdb)]
2119
pub(crate) mod gdb;
@@ -41,51 +39,13 @@ pub(crate) mod crashdump;
4139
pub(crate) mod hyperlight_vm;
4240

4341
use std::fmt::Debug;
44-
use std::str::FromStr;
4542
#[cfg(any(kvm, mshv3))]
4643
use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU64, Ordering};
4744
#[cfg(target_os = "windows")]
4845
use std::sync::atomic::{AtomicU8, Ordering};
4946
#[cfg(any(kvm, mshv3))]
5047
use std::time::Duration;
5148

52-
/// Get the logging level to pass to the guest entrypoint
53-
fn get_max_log_level() -> u32 {
54-
// Check to see if the RUST_LOG environment variable is set
55-
// and if so, parse it to get the log_level for hyperlight_guest
56-
// if that is not set get the log level for the hyperlight_host
57-
58-
// This is done as the guest will produce logs based on the log level returned here
59-
// producing those logs is expensive and we don't want to do it if the host is not
60-
// going to process them
61-
62-
let val = std::env::var("RUST_LOG").unwrap_or_default();
63-
64-
let level = if val.contains("hyperlight_guest") {
65-
val.split(',')
66-
.find(|s| s.contains("hyperlight_guest"))
67-
.unwrap_or("")
68-
.split('=')
69-
.nth(1)
70-
.unwrap_or("")
71-
} else if val.contains("hyperlight_host") {
72-
val.split(',')
73-
.find(|s| s.contains("hyperlight_host"))
74-
.unwrap_or("")
75-
.split('=')
76-
.nth(1)
77-
.unwrap_or("")
78-
} else {
79-
// look for a value string that does not contain "="
80-
val.split(',').find(|s| !s.contains("=")).unwrap_or("")
81-
};
82-
83-
log::info!("Determined guest log level: {}", level);
84-
// Convert the log level string to a LevelFilter
85-
// If no value is found, default to Error
86-
LevelFilter::from_str(level).unwrap_or(LevelFilter::Error) as u32
87-
}
88-
8949
/// A trait for platform-specific interrupt handle implementation details
9050
pub(crate) trait InterruptHandleImpl: InterruptHandle {
9151
/// Set the thread ID for the vcpu thread

0 commit comments

Comments
 (0)