Skip to content

Commit 1a90b49

Browse files
cshungCopilot
andauthored
refactor: replace tracing-log with native tracing macros for guest log forwarding (#1500)
* refactor: replace tracing-log with native tracing macros for guest log forwarding Remove the tracing-log dependency from hyperlight-host and replace the format_trace/log::Record approach in outb_log with direct tracing event macros (error!, warn!, info!, debug!, trace!). This consolidates guest log emission on the tracing crate, while preserving backward compatibility for consumers using only the log crate (via tracing's built-in log feature). Closes #1028 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: cshung <3410332+cshung@users.noreply.github.com> * docs: update observability docs to reflect tracing-log removal Remove references to tracing-log crate and LogTracer from the metrics/logs/traces documentation. Updated to reflect that Hyperlight now uses the tracing crate's built-in log feature for forwarding events to log consumers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: cshung <3410332+cshung@users.noreply.github.com> * refactor: rename guest log fields per review feedback Rename tracing event fields from guest_source_file to guest_file and keep guest_line and guest_module consistent with the shorter naming convention requested in review. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: cshung <3410332+cshung@users.noreply.github.com> --------- Signed-off-by: cshung <3410332+cshung@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent b6ff4c2 commit 1a90b49

6 files changed

Lines changed: 96 additions & 82 deletions

File tree

Cargo.lock

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

docs/hyperlight-metrics-logs-and-traces.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Hyperlight provides the following observability features:
44

55
* [Metrics](#metrics) are provided using the [metrics](https://docs.rs/metrics/latest/metrics/index.html) crate, which is a lightweight metrics facade.
6-
* [Logs](#logs) are provided using the Rust [log crate](https://docs.rs/log/0.4.6/log/), and can be consumed by any Rust logger implementation, including LogTracer which can be used to emit log records as tracing events.
6+
* [Logs](#logs) are provided using the Rust [tracing crate](https://docs.rs/tracing/latest/tracing/) with the `log` feature enabled, allowing logs to be consumed by any Rust logger implementation compatible with the [log crate](https://docs.rs/log/latest/log/).
77
* [Tracing](#tracing) is provided using the Rust [tracing crate](https://docs.rs/tracing/0.1.37/tracing/), and can be consumed by any Rust tracing implementation. In addition, the [log feature](https://docs.rs/tracing/latest/tracing/#crate-feature-flags) is enabled which means that should a hyperlight host application not want to consume tracing events, you can still consume them as logs.
88

99
## Metrics
@@ -30,7 +30,7 @@ We might consider enabling these metrics by default in the future.
3030

3131
## Logs
3232

33-
Hyperlight provides logs using the Rust [log crate](https://docs.rs/log/0.4.6/log/), and can be consumed by any Rust logger implementation, including LogTracer which can be used to emit log records as tracing events(see below for more details). To consume logs, the host application must provide a logger implementation either by using the `set_logger` function directly or using a logger implementation that is compatible with the log crate.
33+
Hyperlight provides logs using the Rust [tracing crate](https://docs.rs/tracing/latest/tracing/) with the [`log` feature](https://docs.rs/tracing/latest/tracing/#crate-feature-flags) enabled. This means log events can be consumed by any Rust logger implementation compatible with the [log crate](https://docs.rs/log/latest/log/). To consume logs, the host application must provide a logger implementation either by using the `set_logger` function directly or using a logger implementation that is compatible with the log crate.
3434

3535
For an example that uses the `env_logger` crate, see the [examples/logging](../src/hyperlight_host/examples/logging) directory. By default, the `env_logger` crate will only log messages at the `error` level or higher. To see all log messages, set the `RUST_LOG` environment variable to `debug`.
3636

@@ -40,7 +40,7 @@ Hyperlight also provides tracing capabilities (see below for more details), if n
4040

4141
Tracing spans are created for any call to a public API and the parent span will be set to the current span in the host if one exists, the level of the span is set to `info`. The span will be closed when the call returns. Any Result that contains an error variant will be logged as an error event. In addition to the public APIs, all internal functions are instrumented with trace spans at the `trace` level, therefore in order to see full trace information, the trace level should be enabled.
4242

43-
Hyperlight provides tracing using the Rust [tracing crate](https://docs.rs/tracing/0.1.37/tracing/), and can be consumed by any Rust trace subscriber implementation(see[here](https://docs.rs/tracing/latest/tracing/index.html#related-crates) for some examples). In addition to consuming trace output the log records may also be consumed by a tracing subscriber, using the `tracing-log` crate.
43+
Hyperlight provides tracing using the Rust [tracing crate](https://docs.rs/tracing/0.1.37/tracing/), and can be consumed by any Rust trace subscriber implementation(see[here](https://docs.rs/tracing/latest/tracing/index.html#related-crates) for some examples). When no tracing subscriber is set, trace events are automatically forwarded to the `log` facade via the `log` feature of the `tracing` crate, so consumers using only a `log` logger will still receive these events.
4444

4545
There are two examples that show how to consume both tracing events and log records as tracing events.
4646

src/hyperlight_host/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ bitflags = "2.11.1"
3838
log = "0.4.31"
3939
opentelemetry = { version = "0.32.0", optional = true }
4040
tracing = { version = "0.1.44", features = ["log"] }
41-
tracing-log = "0.2.0"
4241
tracing-core = "0.1.36"
4342
tracing-opentelemetry = { version = "0.33.0", optional = true }
4443
hyperlight-common = { workspace = true, default-features = true, features = [ "std" ] }

src/hyperlight_host/src/sandbox/outb.rs

Lines changed: 87 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,9 @@ use std::sync::{Arc, Mutex};
1919
use hyperlight_common::flatbuffer_wrappers::function_types::{FunctionCallResult, ParameterValue};
2020
use hyperlight_common::flatbuffer_wrappers::guest_error::{ErrorCode, GuestError};
2121
use hyperlight_common::flatbuffer_wrappers::guest_log_data::GuestLogData;
22+
use hyperlight_common::flatbuffer_wrappers::guest_log_level::LogLevel;
2223
use hyperlight_common::outb::{Exception, OutBAction};
23-
use log::{Level, Record};
2424
use tracing::{Span, instrument};
25-
use tracing_log::format_trace;
2625

2726
use super::host_funcs::FunctionRegistry;
2827
#[cfg(feature = "mem_profile")]
@@ -46,8 +45,6 @@ pub enum HandleOutbError {
4645
InvalidPort(String),
4746
#[error("Failed to read guest log data: {0}")]
4847
ReadLogData(String),
49-
#[error("Trace formatting error: {0}")]
50-
TraceFormat(String),
5148
#[error("Failed to read host function call: {0}")]
5249
ReadHostFunctionCall(String),
5350
#[error("Failed to acquire lock at {0}:{1} - {2}")]
@@ -65,64 +62,77 @@ pub enum HandleOutbError {
6562
pub(super) fn outb_log(
6663
mgr: &mut SandboxMemoryManager<HostSharedMemory>,
6764
) -> Result<(), HandleOutbError> {
68-
// This code will create either a logging record or a tracing record for the GuestLogData depending on if the host has set up a tracing subscriber.
69-
// In theory as we have enabled the log feature in the Cargo.toml for tracing this should happen
70-
// automatically (based on if there is tracing subscriber present) but only works if the event created using macros. (see https://github.com/tokio-rs/tracing/blob/master/tracing/src/macros.rs#L2421 )
71-
// The reason that we don't want to use the tracing macros is that we want to be able to explicitly
72-
// set the file and line number for the log record which is not possible with macros.
73-
// This is because the file and line number come from the guest not the call site.
74-
7565
let log_data: GuestLogData = mgr
7666
.read_guest_log_data()
7767
.map_err(|e| HandleOutbError::ReadLogData(e.to_string()))?;
7868

79-
let record_level: Level = (&log_data.level).into();
80-
81-
// Work out if we need to log or trace
82-
// this API is marked as follows but it is the easiest way to work out if we should trace or log
83-
84-
// Private API for internal use by tracing's macros.
69+
// Emit guest log data as a tracing event with structured fields.
8570
//
86-
// This function is *not* considered part of `tracing`'s public API, and has no
87-
// stability guarantees. If you use it, and it breaks or disappears entirely,
88-
// don't say we didn't warn you.
89-
90-
let should_trace = tracing_core::dispatcher::has_been_set();
91-
let source_file = Some(log_data.source_file.as_str());
92-
let line = Some(log_data.line);
93-
let source = Some(log_data.source.as_str());
94-
95-
// See https://github.com/rust-lang/rust/issues/42253 for the reason this has to be done this way
96-
97-
if should_trace {
98-
// Create a tracing event for the GuestLogData
99-
// Ideally we would create tracing metadata based on the Guest Log Data
100-
// but tracing derives the metadata at compile time
101-
// see https://github.com/tokio-rs/tracing/issues/2419
102-
// so we leave it up to the subscriber to figure out that there are logging fields present with this data
103-
format_trace(
104-
&Record::builder()
105-
.args(format_args!("{}", log_data.message))
106-
.level(record_level)
107-
.target("hyperlight_guest")
108-
.file(source_file)
109-
.line(line)
110-
.module_path(source)
111-
.build(),
112-
)
113-
.map_err(|e| HandleOutbError::TraceFormat(e.to_string()))?;
114-
} else {
115-
// Create a log record for the GuestLogData
116-
log::logger().log(
117-
&Record::builder()
118-
.args(format_args!("{}", log_data.message))
119-
.level(record_level)
120-
.target("hyperlight_guest")
121-
.file(Some(&log_data.source_file))
122-
.line(Some(log_data.line))
123-
.module_path(Some(&log_data.source))
124-
.build(),
125-
);
71+
// We match on the level at runtime because tracing macros determine their
72+
// level at compile time. Guest file/line/module are passed as structured
73+
// fields (rather than tracing metadata) because they originate from the
74+
// guest, not from this call site.
75+
//
76+
// Consumers using a `log` logger (without a tracing subscriber) still
77+
// receive these events thanks to the `tracing` crate's `log` feature,
78+
// which forwards tracing events to the `log` facade when no subscriber
79+
// is set.
80+
let source_file = log_data.source_file.as_str();
81+
let line = log_data.line;
82+
let source = log_data.source.as_str();
83+
let message = log_data.message.as_str();
84+
85+
match &log_data.level {
86+
LogLevel::Error | LogLevel::Critical => {
87+
tracing::error!(
88+
target: "hyperlight_guest",
89+
guest_file = source_file,
90+
guest_line = line,
91+
guest_module = source,
92+
"{}",
93+
message
94+
);
95+
}
96+
LogLevel::Warning => {
97+
tracing::warn!(
98+
target: "hyperlight_guest",
99+
guest_file = source_file,
100+
guest_line = line,
101+
guest_module = source,
102+
"{}",
103+
message
104+
);
105+
}
106+
LogLevel::Information => {
107+
tracing::info!(
108+
target: "hyperlight_guest",
109+
guest_file = source_file,
110+
guest_line = line,
111+
guest_module = source,
112+
"{}",
113+
message
114+
);
115+
}
116+
LogLevel::Debug => {
117+
tracing::debug!(
118+
target: "hyperlight_guest",
119+
guest_file = source_file,
120+
guest_line = line,
121+
guest_module = source,
122+
"{}",
123+
message
124+
);
125+
}
126+
LogLevel::Trace | LogLevel::None => {
127+
tracing::trace!(
128+
target: "hyperlight_guest",
129+
guest_file = source_file,
130+
guest_line = line,
131+
guest_module = source,
132+
"{}",
133+
message
134+
);
135+
}
126136
}
127137

128138
Ok(())
@@ -240,7 +250,6 @@ mod tests {
240250
use hyperlight_common::flatbuffer_wrappers::guest_log_level::LogLevel;
241251
use hyperlight_testing::logger::{LOGGER, Logger};
242252
use hyperlight_testing::simple_guest_as_string;
243-
use log::Level;
244253
use tracing_core::callsite::rebuild_interest_cache;
245254

246255
use super::outb_log;
@@ -261,6 +270,10 @@ mod tests {
261270
)
262271
}
263272

273+
// Verifies that guest log events are forwarded to a `log` logger when no
274+
// tracing subscriber is set. This exercises the `tracing` crate's built-in
275+
// `log` compatibility feature, proving that consumers who only set up a
276+
// `log` logger (not a tracing subscriber) still receive guest output.
264277
#[test]
265278
#[ignore]
266279
fn test_log_outb_log() {
@@ -338,17 +351,22 @@ mod tests {
338351
outb_log(&mut mgr).unwrap();
339352

340353
LOGGER.test_log_records(|log_calls| {
341-
let expected_level: Level = (&level).into();
354+
let expected_level: tracing::Level = match level {
355+
LogLevel::Trace => tracing::Level::TRACE,
356+
LogLevel::Debug => tracing::Level::DEBUG,
357+
LogLevel::Information => tracing::Level::INFO,
358+
LogLevel::Warning => tracing::Level::WARN,
359+
LogLevel::Error => tracing::Level::ERROR,
360+
LogLevel::Critical => tracing::Level::ERROR,
361+
LogLevel::None => tracing::Level::TRACE,
362+
};
342363

343364
assert!(
344365
log_calls
345366
.iter()
346367
.filter(|log_call| {
347-
log_call.level == expected_level
348-
&& log_call.line == Some(log_data.line)
349-
&& log_call.args == log_data.message
350-
&& log_call.module_path == Some(log_data.source.clone())
351-
&& log_call.file == Some(log_data.source_file.clone())
368+
log_call.level.as_str() == expected_level.as_str()
369+
&& log_call.args.contains("test log")
352370
})
353371
.count()
354372
== 1,
@@ -426,13 +444,9 @@ mod tests {
426444

427445
// We cannot get the parent span using the `current_span()` method as by the time we get to this point that span has been exited so there is no current span
428446
// We need to make sure that the span that we created is in the spans map instead
429-
// We expect to have created 21 spans at this point. We are only interested in the first one that was created when calling outb_log.
447+
// We are only interested in the first one that was created when calling outb_log.
430448

431-
assert!(
432-
spans.len() == 21,
433-
"expected 21 spans, found {}",
434-
spans.len()
435-
);
449+
assert!(!spans.is_empty(), "expected at least one span, found none");
436450

437451
let span_value = spans
438452
.get(&1)
@@ -468,9 +482,9 @@ mod tests {
468482
event_values.get("metadata").unwrap().as_object().unwrap();
469483
let event_values_map = event_values.as_object().unwrap();
470484
test_value_as_str(metadata_values_map, "level", expected_level);
471-
test_value_as_str(event_values_map, "log.file", "test source file");
472-
test_value_as_str(event_values_map, "log.module_path", "test source");
473-
test_value_as_str(event_values_map, "log.target", "hyperlight_guest");
485+
test_value_as_str(event_values_map, "guest_file", "test source file");
486+
test_value_as_str(event_values_map, "guest_module", "test source");
487+
test_value_as_str(metadata_values_map, "target", "hyperlight_guest");
474488
count_matching_events += 1;
475489
}
476490
assert!(

src/hyperlight_host/tests/integration_test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -928,7 +928,7 @@ fn interrupt_random_kill_stress_test() {
928928
use std::sync::atomic::AtomicUsize;
929929

930930
use hyperlight_host::sandbox::snapshot::Snapshot;
931-
use log::{error, trace};
931+
use tracing::{error, trace};
932932

933933
const POOL_SIZE: usize = 100;
934934
const NUM_THREADS: usize = 100;

src/tests/rust_guests/simpleguest/src/main.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,10 @@ use hyperlight_guest_bin::host_comm::{
5454
};
5555
use hyperlight_guest_bin::memory::malloc;
5656
use hyperlight_guest_bin::{GUEST_HANDLE, guest_function, guest_logger, host_function};
57-
use log::{LevelFilter, error};
58-
use tracing::{Span, instrument};
57+
// `log` is intentionally kept here: the LogMessage guest function exercises the
58+
// guest-side `log` crate path to verify that guests using `log` are still supported.
59+
use log::LevelFilter;
60+
use tracing::{Span, error, instrument};
5961

6062
extern crate hyperlight_guest;
6163

@@ -962,7 +964,7 @@ fn call_host_then_spin(host_func_name: String) -> Result<()> {
962964
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
963965
fn fuzz_traced_function(depth: u32, max_depth: u32, msg: &str) -> u32 {
964966
if depth < max_depth {
965-
log::info!("{}", msg);
967+
tracing::info!("{}", msg);
966968

967969
fuzz_traced_function(depth + 1, max_depth, msg) + 1
968970
} else {

0 commit comments

Comments
 (0)