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