diff mbox series

[1/2] rust/qemu-api: Add initial logging support based on C API

Message ID 20250330205857.1615-2-shentey@gmail.com (mailing list archive)
State New
Headers show
Series Initial logging support for Rust | expand

Commit Message

Bernhard Beschow March 30, 2025, 8:58 p.m. UTC
A qemu_log_mask!() macro is provided which expects similar arguments as the C
version. However, the formatting works as one would expect from Rust.

To maximize code reuse the macro is just a thin wrapper around qemu_log().
Also, just the bare minimum of logging masks is provided which should suffice
for the current use case of Rust in QEMU.

Signed-off-by: Bernhard Beschow <shentey@gmail.com>
---
 docs/devel/rust.rst       |  1 +
 rust/wrapper.h            |  2 ++
 rust/qemu-api/meson.build |  1 +
 rust/qemu-api/src/lib.rs  |  1 +
 rust/qemu-api/src/log.rs  | 55 +++++++++++++++++++++++++++++++++++++++
 5 files changed, 60 insertions(+)
 create mode 100644 rust/qemu-api/src/log.rs

Comments

Paolo Bonzini March 31, 2025, 9:53 a.m. UTC | #1
On 3/30/25 22:58, Bernhard Beschow wrote:
> A qemu_log_mask!() macro is provided which expects similar arguments as the C
> version. However, the formatting works as one would expect from Rust.
> 
> To maximize code reuse the macro is just a thin wrapper around qemu_log().
> Also, just the bare minimum of logging masks is provided which should suffice
> for the current use case of Rust in QEMU.

It's probably better to use an enum for this.  One possibility is also 
to change the #defines to a C enum, and see which enum translation of 
the several allowed by bindgen is best.

Also, while this is good for now, later on we probably want to 
reimplement logging at a lower level via the std::fmt::Write trait.  But 
that's just for efficiency and your macro is indeed good enough to 
define what the API would look like.  Right now I have a project for 
GSoC that will look at that, and the student can look into it later on.

This means answering the following two questions:

- the mapping the LOG_* constants into Rust

- whether to keep the "qemu" prefix for the API (personal opinion: no)

- whether it makes sense to add more macros such as log_guest_error! or 
log_unimp! for the most common LOG_* values

> +#[macro_export]
> +macro_rules! qemu_log_mask {
> +    ($mask:expr, $fmt:expr $(, $args:expr)*) => {{

Looking at https://doc.rust-lang.org/std/macro.write.html they just use 
$($arg:tt)* for what is passed to format_args! (or in your case 
format!), so we can do the same here too. The main advantage is that it 
allows giving a trailing comma to qemu_log_mask!.

Paolo

> +        if unsafe {
> +            (::qemu_api::bindings::qemu_loglevel & ($mask as std::os::raw::c_int)) != 0
> +        } {
> +            let formatted_string = format!($fmt, $($args),*);
> +            let c_string = std::ffi::CString::new(formatted_string).unwrap();
> +
> +            unsafe {
> +                ::qemu_api::bindings::qemu_log(c_string.as_ptr());
> +            }
> +        }
> +    }};
> +}
Bernhard Beschow April 1, 2025, 10:51 a.m. UTC | #2
Am 31. März 2025 09:53:41 UTC schrieb Paolo Bonzini <pbonzini@redhat.com>:
>On 3/30/25 22:58, Bernhard Beschow wrote:
>> A qemu_log_mask!() macro is provided which expects similar arguments as the C
>> version. However, the formatting works as one would expect from Rust.
>> 
>> To maximize code reuse the macro is just a thin wrapper around qemu_log().
>> Also, just the bare minimum of logging masks is provided which should suffice
>> for the current use case of Rust in QEMU.
>
>It's probably better to use an enum for this.  One possibility is also to change the #defines to a C enum, and see which enum translation of the several allowed by bindgen is best.

Currently the #defines contain some holes for "private" mask bits. Turning these into an enum without exposing all publicly, and changing the type of qemu_loglevel for consistency, would result in undefined behavior. Or do you suggest to convert just the public #defines into an enum to expose them to Rust, and keep the rest of the C API including the type of qemu_loglevel as is?

There are surely several tradeoffs and/or cleanups possible here, but that's way beyond for what I wanted to achieve -- which is closing a gap between C and Rust. My main goal is just to get my feet wet with Rust.

>
>Also, while this is good for now, later on we probably want to reimplement logging at a lower level via the std::fmt::Write trait.  But that's just for efficiency and your macro is indeed good enough to define what the API would look like.

Can we live with an easy solution then for now? As you suggest below, further abstractions like log_guest_error! can be built on top which further insulates client code from implementation details such as the representation of the mask bits.

> Right now I have a project for GSoC that will look at that, and the student can look into it later on.

Whoops, I didn't intend to spoil the project.

>
>This means answering the following two questions:
>
>- the mapping the LOG_* constants into Rust
>
>- whether to keep the "qemu" prefix for the API (personal opinion: no)
>
>- whether it makes sense to add more macros such as log_guest_error! or log_unimp! for the most common LOG_* values
>
>> +#[macro_export]
>> +macro_rules! qemu_log_mask {
>> +    ($mask:expr, $fmt:expr $(, $args:expr)*) => {{
>
>Looking at https://doc.rust-lang.org/std/macro.write.html they just use $($arg:tt)* for what is passed to format_args! (or in your case format!), so we can do the same here too. The main advantage is that it allows giving a trailing comma to qemu_log_mask!.

Easy to fix. Before proceeding I'd like to see a solution for the topic above.

Best regards,
Bernhard

>
>Paolo
>
>> +        if unsafe {
>> +            (::qemu_api::bindings::qemu_loglevel & ($mask as std::os::raw::c_int)) != 0
>> +        } {
>> +            let formatted_string = format!($fmt, $($args),*);
>> +            let c_string = std::ffi::CString::new(formatted_string).unwrap();
>> +
>> +            unsafe {
>> +                ::qemu_api::bindings::qemu_log(c_string.as_ptr());
>> +            }
>> +        }
>> +    }};
>> +}
>
diff mbox series

Patch

diff --git a/docs/devel/rust.rst b/docs/devel/rust.rst
index 88bdec1eb2..bc50216dc6 100644
--- a/docs/devel/rust.rst
+++ b/docs/devel/rust.rst
@@ -187,6 +187,7 @@  module           status
 ``c_str``        complete
 ``errno``        complete
 ``irq``          complete
+``log``          proof of concept
 ``memory``       stable
 ``module``       complete
 ``offset_of``    stable
diff --git a/rust/wrapper.h b/rust/wrapper.h
index d4fec54657..9518face53 100644
--- a/rust/wrapper.h
+++ b/rust/wrapper.h
@@ -48,6 +48,8 @@  typedef enum memory_order {
 #endif /* __CLANG_STDATOMIC_H */
 
 #include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/log-for-trace.h"
 #include "qemu/module.h"
 #include "qemu-io.h"
 #include "system/system.h"
diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build
index 858685ddd4..112f6735a9 100644
--- a/rust/qemu-api/meson.build
+++ b/rust/qemu-api/meson.build
@@ -26,6 +26,7 @@  _qemu_api_rs = static_library(
       'src/c_str.rs',
       'src/errno.rs',
       'src/irq.rs',
+      'src/log.rs',
       'src/memory.rs',
       'src/module.rs',
       'src/offset_of.rs',
diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs
index 05f38b51d3..b54989a243 100644
--- a/rust/qemu-api/src/lib.rs
+++ b/rust/qemu-api/src/lib.rs
@@ -21,6 +21,7 @@ 
 pub mod chardev;
 pub mod errno;
 pub mod irq;
+pub mod log;
 pub mod memory;
 pub mod module;
 pub mod offset_of;
diff --git a/rust/qemu-api/src/log.rs b/rust/qemu-api/src/log.rs
new file mode 100644
index 0000000000..3103a86efa
--- /dev/null
+++ b/rust/qemu-api/src/log.rs
@@ -0,0 +1,55 @@ 
+// Copyright 2025 Bernhard Beschow <shentey@gmail.com>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+pub use crate::bindings::{LOG_GUEST_ERROR, LOG_INVALID_MEM, LOG_UNIMP};
+
+/// A macro to log messages conditionally based on a provided mask.
+///
+/// The `qemu_log_mask` macro checks whether the given mask matches the
+/// current log level and, if so, formats and logs the message.
+///
+/// # Parameters
+///
+/// - `$mask`: The log mask to check. This should be an integer value
+///   indicating the log level mask.
+/// - `$fmt`: A format string following the syntax and rules of the `format!`
+///   macro. It specifies the structure of the log message.
+/// - `$args`: Optional arguments to be interpolated into the format string.
+///
+/// # Example
+///
+/// ```
+/// use qemu_api::log::LOG_GUEST_ERROR;
+///
+/// qemu_log_mask!(
+///     LOG_GUEST_ERROR,
+///     "Address 0x{error_address:x} out of range\n"
+/// );
+/// ```
+///
+/// It is also possible to use printf style:
+///
+/// ```
+/// use qemu_api::log::LOG_GUEST_ERROR;
+///
+/// qemu_log_mask!(
+///     LOG_GUEST_ERROR,
+///     "Address 0x{:x} out of range\n",
+///     error_address
+/// );
+/// ```
+#[macro_export]
+macro_rules! qemu_log_mask {
+    ($mask:expr, $fmt:expr $(, $args:expr)*) => {{
+        if unsafe {
+            (::qemu_api::bindings::qemu_loglevel & ($mask as std::os::raw::c_int)) != 0
+        } {
+            let formatted_string = format!($fmt, $($args),*);
+            let c_string = std::ffi::CString::new(formatted_string).unwrap();
+
+            unsafe {
+                ::qemu_api::bindings::qemu_log(c_string.as_ptr());
+            }
+        }
+    }};
+}