Sign up for the KDAB Newsletter
Stay on top of the latest news, publications, events and more.
Go to Sign-up
Find what you need - explore our website and developer resources
8 August 2025
For projects incorporating Rust and Qt, a minor but important problem can arise: too many sources of log messages. In the usual case, one of these is from Rust (and crate dependencies) and another is from Qt. Our Rust and Qt solution, CXX-Qt, exposes a new API to help with this in the upcoming 0.8.0 release.
There are several benefits to ensuring your log messages go through one logger. It makes it easier to redirect said messages into a file, there's less configuration required for end users, and it makes consistent formatting easier.
Qt has its own logging infrastructure, and their suggested way to print messages in Qt applications. It has a category system, rules for filtering which messages you want to see, custom formatting and so on. Applications like GammaRay can also display messages from Qt, and configure which categories are emitted.
One of the more popular logging libraries in Rust is called tracing, and we're going to show how you can redirect messages from it to the Qt logger. The integration can work for any other logging crate too, including your own custom solution.
Before we start splicing the two log streams, let's quickly go over sending a Qt log message in Rust. Assuming you already have a CXX-Qt application (and if you don't, you can follow our book, construct a message log context with QMessageLogContext:
let file = CString::new("main.rs").unwrap();
let function = CString::new("main").unwrap();
let category = CString::new("lib").unwrap();
let context = QMessageLogContext::new(&file, 0, &function, &category);
You can specify the filename, the line number, the function name and even a category. We have to use CString
here because QMessageLogContext
in C++ uses const char*
, not QString
.
Note that there isn't a way to get the name of the currently calling function out of the box in Rust, but there are alternative solutions if you want that information.
Now that we have a context, we can send a message to the Qt logger. We can do this using the unfortunately undocumented qt_message_output
function. This sends a message to the currently installed message handler.
Note that CXX-Qt currently doesn't have Rust bindings to install a custom one.
The default message handler goes to the standard output and error streams. This function takes a QtMsgType
, the context we just created, and a QString message:
qt_message_output(
QtMsgType::QtInfoMsg,
&context,
&QString::from("This is an informational message..."),
);
And voilà:
lib: This is an informational message...
But that looks pretty plain, and we can't see any of our context! Most information available in the Qt logger isn't shown by default, but we can modify it by using the QT_MESSAGE_PATTERN
environment variable.
I used the comprehensive example given in the linked documentation, but it's useful for showcasing the context:
export QT_MESSAGE_PATTERN="[%{time yyyyMMdd h:mm:ss.zzz ttt} %{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}] %{file}:%{line} - %{message}"
[20250314 8:56:45.033 EDT I] main.rs:0 - This is an informational message...
Now that we have sent our first Qt log message from Rust, let's up the ante and integrate the tracing crate.
To redirect events from tracing, we have to add a subscriber that forwards events to Qt. This basically means anything that uses tracing (e.g. your application, another crate) will be picked up.
As a side effect of tracing's flexibility, we also have to create our own Visitor. This is necessary because tracing records structured data, and we need to flatten said data to a string.
use std::fmt::Write; // needed for write_fmt
struct StringVisitor<'a> {
string: &'a mut String,
}
impl tracing::field::Visit for StringVisitor<'_> {
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
write!(self.string, "{} = {:?} ", field.name(), value).unwrap();
}
}
And now for the custom layer that will catch the events, and send them to Qt:
pub struct QtSubscriber {}
impl<S> tracing_subscriber::Layer<S> for QtSubscriber
where
S: tracing::Subscriber,
{
fn on_event(
&self,
event: &tracing::Event<'_>,
_ctx: tracing_subscriber::layer::Context<'_, S>,
) {
let mut buffer: String = String::new();
let mut visitor = StringVisitor {
string: &mut buffer
};
event.record(&mut visitor);
let msg_type = match *event.metadata().level() {
tracing::Level::ERROR => QtMsgType::QtCriticalMsg,
tracing::Level::WARN => QtMsgType::QtWarningMsg,
tracing::Level::INFO => QtMsgType::QtInfoMsg,
tracing::Level::DEBUG => QtMsgType::QtDebugMsg,
tracing::Level::TRACE => QtMsgType::QtDebugMsg
};
let file = if let Some(file) = event.metadata().file() {
CString::new(file).unwrap()
} else {
CString::default()
};
let line = if let Some(line) = event.metadata().line() {
line as i32
} else {
0
};
let function = CString::default();
let category = CString::new("lib").unwrap();
let context = QMessageLogContext::new(&file, line, &function, &category);
qt_message_output(msg_type, &context, &QString::from(buffer));
}
}
And finally, we have to register our new layer with the registry:
use tracing_subscriber::layer::SubscriberExt; // needed for with
use tracing_subscriber::util::SubscriberInitExt; // needed for init
tracing_subscriber::registry().with(QtSubscriber{}).init();
Afterwards, try sending a message using tracing::info!
and see if it works:
tracing::info!("This is an informational message... from tracing!")
Using a more comprehensive QT_MESSAGE_PATTERN
, you should get a log message from Qt. Note that "message" is written here, as that's the default field in tracing:
[20250320 10:41:40.617 EDT I] examples/cargo_without_cmake/src/main.rs:104 - message = This is an informational message...
These new bindings in CXX-Qt should be useful to application developers who want to adopt the best logging crates in Rust, while ensuring their messages are forwarded to Qt. If you want to redirect Qt log messages to Rust, CXX-Qt doesn't have a way to install custom message handlers yet.
See our KDABLabs repository for complete examples of the snippets shown here.
About KDAB
The KDAB Group is a globally recognized provider for software consulting, development and training, specializing in embedded devices and complex cross-platform desktop applications. In addition to being leading experts in Qt, C++ and 3D technologies for over two decades, KDAB provides deep expertise across the stack, including Linux, Rust and modern UI frameworks. With 100+ employees from 20 countries and offices in Sweden, Germany, USA, France and UK, we serve clients around the world.
Stay on top of the latest news, publications, events and more.
Go to Sign-up
Come to Berlin 16th - 18th September and attend Oxidize 2025, the conference dealing with applied Rust. Check out the workshop program and book your seat.
Learn more
Learn Rust
In collaboration with our partners Ferrous Systems, KDAB provides a variety of introductory and advanced training courses for the Rust language.
Learn more
Learn Modern C++
Our hands-on Modern C++ training courses are designed to quickly familiarize newcomers with the language. They also update professional C++ developers on the latest changes in the language and standard library introduced in recent C++ editions.
Learn more