Skip to content

Commit f4bfa17

Browse files
pjungkampsdroege
authored andcommitted
glib: Add logging bridge for structured logs
1 parent 9e632e5 commit f4bfa17

File tree

3 files changed

+107
-3
lines changed

3 files changed

+107
-3
lines changed

glib/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ v2_82 = ["v2_80", "glib-sys/v2_82", "gobject-sys/v2_82"]
5656
v2_84 = ["v2_82", "glib-sys/v2_84", "gobject-sys/v2_84"]
5757
v2_86 = ["v2_84", "glib-sys/v2_86", "gobject-sys/v2_86"]
5858
log = ["rs-log"]
59+
log_kv = ["log", "rs-log/kv"]
5960
log_macros = ["log"]
6061
compiletests = []
6162
gio = ["gio-sys"]

glib/src/bridged_logging.rs

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Take a look at the license at the top of the repository in the LICENSE file.
22

3-
use crate::{gstr, log as glib_log, log_structured_array, translate::*, LogField};
3+
use crate::{gstr, log as glib_log, log_structured_array, translate::*, LogField, LogWriterOutput};
44

55
// rustdoc-stripper-ignore-next
66
/// Enumeration of the possible formatting behaviours for a
@@ -262,7 +262,108 @@ pub fn rust_log_handler(domain: Option<&str>, level: glib_log::LogLevel, message
262262
glib_log::LogLevel::Debug => rs_log::Level::Debug,
263263
};
264264

265-
rs_log::log!(target: domain.unwrap_or("<null>"), level, "{}", message);
265+
rs_log::log!(target: domain.unwrap_or_default(), level, "{message}");
266+
}
267+
268+
#[cfg(feature = "log_kv")]
269+
struct LogFields<'field>(&'field [LogField<'field>]);
270+
271+
#[cfg(feature = "log_kv")]
272+
impl<'fields> rs_log::kv::Source for LogFields<'fields> {
273+
fn visit<'kvs>(
274+
&'kvs self,
275+
visitor: &mut dyn rs_log::kv::VisitSource<'kvs>,
276+
) -> Result<(), rs_log::kv::Error> {
277+
use rs_log::kv::{Key, Value};
278+
279+
for field in self.0 {
280+
let key = Key::from_str(field.key());
281+
let value = field
282+
.value_str()
283+
.map(Value::from)
284+
.unwrap_or_else(Value::null);
285+
visitor.visit_pair(key, value)?;
286+
}
287+
288+
Ok(())
289+
}
290+
291+
fn count(&self) -> usize {
292+
self.0.len()
293+
}
294+
}
295+
296+
// rustdoc-stripper-ignore-next
297+
/// Provides a glib log writer which routes all structured logging messages to the
298+
/// [`log crate`](https://crates.io/crates/log).
299+
///
300+
/// In order to use this function, `glib` must be built with the `log` feature
301+
/// enabled.
302+
///
303+
/// Use this function if you want to use the log crate as the main logging
304+
/// output in your application, and want to route all structured logging happening in
305+
/// glib to the log crate. If you want the opposite, use [`GlibLogger`](struct.GlibLogger.html).
306+
///
307+
/// NOTE: This should never be used when [`GlibLogger`](struct.GlibLogger.html) is
308+
/// registered as a logger, otherwise a stack overflow will occur.
309+
///
310+
/// ```no_run
311+
/// glib::log_set_writer_func(glib::rust_log_writer);
312+
/// ```
313+
pub fn rust_log_writer(log_level: glib_log::LogLevel, fields: &[LogField<'_>]) -> LogWriterOutput {
314+
let lvl = match log_level {
315+
glib_log::LogLevel::Error | glib_log::LogLevel::Critical => rs_log::Level::Error,
316+
glib_log::LogLevel::Warning => rs_log::Level::Warn,
317+
glib_log::LogLevel::Info | glib_log::LogLevel::Message => rs_log::Level::Info,
318+
glib_log::LogLevel::Debug => rs_log::Level::Debug,
319+
};
320+
321+
if lvl > rs_log::STATIC_MAX_LEVEL {
322+
return LogWriterOutput::Handled;
323+
}
324+
325+
let mut domain = None::<&str>;
326+
let mut message = None::<&str>;
327+
let mut file = None::<&str>;
328+
let mut line = None::<u32>;
329+
let mut func = None::<&str>;
330+
331+
for field in fields {
332+
let Some(value) = field.value_str() else {
333+
continue;
334+
};
335+
336+
match field.key() {
337+
"GLIB_DOMAIN" => domain = Some(value),
338+
"MESSAGE" => message = Some(value),
339+
"CODE_FILE" => file = Some(value),
340+
"CODE_LINE" => line = value.parse().ok(),
341+
"CODE_FUNC" => func = Some(value),
342+
_ => continue,
343+
};
344+
}
345+
346+
if let Some(message) = message {
347+
let logger = rs_log::logger();
348+
let mut record = rs_log::Record::builder();
349+
350+
#[cfg(feature = "log_kv")]
351+
let fields = LogFields(fields);
352+
353+
record
354+
.level(lvl)
355+
.target(domain.unwrap_or_default())
356+
.file(file)
357+
.line(line)
358+
.module_path(func);
359+
360+
#[cfg(feature = "log_kv")]
361+
record.key_values(&fields);
362+
363+
logger.log(&record.args(format_args!("{message}")).build());
364+
}
365+
366+
LogWriterOutput::Handled
266367
}
267368

268369
// rustdoc-stripper-ignore-next

glib/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,9 @@ pub use self::log::{log_writer_is_journald, log_writer_supports_color};
221221
mod bridged_logging;
222222
#[cfg(feature = "log")]
223223
#[cfg_attr(docsrs, doc(cfg(feature = "log")))]
224-
pub use self::bridged_logging::{rust_log_handler, GlibLogger, GlibLoggerDomain, GlibLoggerFormat};
224+
pub use self::bridged_logging::{
225+
rust_log_handler, rust_log_writer, GlibLogger, GlibLoggerDomain, GlibLoggerFormat,
226+
};
225227

226228
#[macro_use]
227229
pub mod subclass;

0 commit comments

Comments
 (0)