Skip to content

Commit b2fa78d

Browse files
committed
Add log forwarding from Qt to log!() facade
1 parent 5bea47c commit b2fa78d

File tree

3 files changed

+112
-1
lines changed

3 files changed

+112
-1
lines changed

qmetaobject/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ chrono_qdatetime = ["chrono"]
1616
[dependencies]
1717
qmetaobject_impl = { path = "../qmetaobject_impl", version = "=0.1.4"}
1818
lazy_static = "1.0"
19+
log = "0.4"
1920
cpp = "0.5.4"
2021
chrono = { version = "0.4", optional = true }
2122

qmetaobject/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -863,13 +863,15 @@ impl QMessageLogContext {
863863

864864
/// Wrap Qt's QtMsgType enum
865865
#[repr(C)]
866-
#[derive(Debug)]
866+
#[derive(Copy, Clone, Debug)]
867867
pub enum QtMsgType {
868868
QtDebugMsg,
869869
QtWarningMsg,
870870
QtCriticalMsg,
871871
QtFatalMsg,
872872
QtInfoMsg,
873+
// there is also one level defined in C++ code:
874+
// QtSystemMsg = QtCriticalMsg
873875
}
874876

875877
/// Wrap qt's qInstallMessageHandler.
@@ -1022,6 +1024,7 @@ pub mod itemmodel;
10221024
pub use itemmodel::*;
10231025
pub mod listmodel;
10241026
pub use listmodel::*;
1027+
pub mod log;
10251028
pub mod qtdeclarative;
10261029
pub use qtdeclarative::*;
10271030
pub mod qmetatype;

qmetaobject/src/log.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
//! Logging facilities and forwarding
2+
3+
use log::{Level, logger, Record};
4+
5+
use crate::{install_message_handler, QMessageLogContext, QString, QtMsgType};
6+
use crate::QtMsgType::*;
7+
8+
/// Mapping from Qt logging levels to Rust logging facade's levels.
9+
///
10+
/// Due to the limited range of levels from both sides,
11+
/// [`QtCriticalMsg`][`QtMsgType`] and [`QtFatalMsg`][`QtMsgType`]
12+
/// both map to [`log::Level::Error`][Level],
13+
/// while [`log::Level::Trace`][Level] is never returned.
14+
///
15+
/// [Level]: https://docs.rs/log/0.4.10/log/enum.Level.html
16+
/// [`QtMsgType`]: https://doc.qt.io/qt-5/qtglobal.html#QtMsgType-enum
17+
pub fn map_level(lvl: &QtMsgType) -> Level {
18+
match lvl {
19+
QtDebugMsg => Level::Debug,
20+
QtInfoMsg => Level::Info,
21+
QtWarningMsg => Level::Warn,
22+
QtCriticalMsg => Level::Error,
23+
QtFatalMsg => Level::Error,
24+
// XXX: What are the external guarantees about possible values of QtMsgType?
25+
// XXX: Are they promised to be limited to the valid enum variants?
26+
}
27+
}
28+
29+
/// Mapping back from Rust logging facade's levels to Qt logging levels.
30+
///
31+
/// Not used internally, exists just for completeness of API.
32+
///
33+
/// Due to the limited range of levels from both sides,
34+
/// [`log::Level::Debug`][Level] and [`log::Level::Trace`][Level]
35+
/// both map to [`QtDebugMsg`][`QtMsgType`],
36+
/// while [`QtFatalMsg`][`QtMsgType`] is never returned.
37+
///
38+
/// [Level]: https://docs.rs/log/0.4.10/log/enum.Level.html
39+
/// [`QtMsgType`]: https://doc.qt.io/qt-5/qtglobal.html#QtMsgType-enum
40+
pub fn unmap_level(lvl: Level) -> QtMsgType {
41+
match lvl {
42+
Level::Error => QtCriticalMsg,
43+
Level::Warn => QtWarningMsg,
44+
Level::Info => QtInfoMsg,
45+
Level::Debug => QtDebugMsg,
46+
Level::Trace => QtDebugMsg,
47+
}
48+
}
49+
50+
// Logging middleware, pass-though, or just proxy function.
51+
// It is called from Qt code, then it converts Qt logging data
52+
// into Rust logging facade's log::Record object, and sends it
53+
// to the currently active logger.
54+
extern "C" fn log_capture(msg_type: QtMsgType,
55+
context: &QMessageLogContext,
56+
message: &QString) {
57+
let level = map_level(&msg_type);
58+
let target = match context.category() {
59+
"" => "default",
60+
x => x,
61+
};
62+
let file = match context.file() {
63+
"" => None,
64+
x => Some(x),
65+
};
66+
let line = match context.line() {
67+
// In Qt, line numbers start from 1, while 0 is just a placeholder
68+
0 => None,
69+
x => Some(x as _),
70+
};
71+
let mut record = Record::builder();
72+
record.level(level)
73+
.target(target)
74+
.file(file)
75+
.line(line)
76+
.module_path(None);
77+
// (inner) match with single all-capturing arm is a hack that allows us
78+
// to extend the lifetime of a matched object for "a little longer".
79+
// Basically, it retains bounded temporary values together with their
80+
// intermediate values etc. This is also the way how println! macro works.
81+
match context.function() {
82+
"" => match format_args!("{}", message) {
83+
args => logger().log(&record.args(args).build()),
84+
},
85+
f => match format_args!("[in {}] {}", f, message) {
86+
args => logger().log(&record.args(args).build()),
87+
}
88+
}
89+
}
90+
91+
/// Installs into Qt logging system a function which forwards messages to the
92+
/// [Rust logging facade][log].
93+
///
94+
/// Most metadata from Qt logging context is retained and passed to [`log::Record`][].
95+
/// Logging levels are mapped with [this][map_level] algorithm.
96+
///
97+
/// This function may be called more than once.
98+
///
99+
/// [log]: https://docs.rs/log
100+
/// [`log::Record`]: https://docs.rs/log/0.4.10/log/struct.Record.html
101+
/// [map_level]: ./fn.map_level.html
102+
pub fn init_qt_to_rust() {
103+
// The reason it is named so complex instead of simple `init` is that
104+
// such descriptive name is future-proof. Consider if someone someday
105+
// would want to implement the opposite forwarding logger?
106+
install_message_handler(log_capture);
107+
}

0 commit comments

Comments
 (0)