glib/bridged_logging.rs
1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use crate::{gstr, log as glib_log, log_structured_array, translate::*, LogField};
4
5// rustdoc-stripper-ignore-next
6/// Enumeration of the possible formatting behaviours for a
7/// [`GlibLogger`](struct.GlibLogger.html).
8///
9/// In order to use this type, `glib` must be built with the `log` feature
10/// enabled.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum GlibLoggerFormat {
13 // rustdoc-stripper-ignore-next
14 /// A simple format, writing only the message on output.
15 Plain,
16 // rustdoc-stripper-ignore-next
17 /// A simple format, writing file, line and message on output.
18 LineAndFile,
19 // rustdoc-stripper-ignore-next
20 /// A logger using glib structured logging.
21 Structured,
22}
23
24// rustdoc-stripper-ignore-next
25/// Enumeration of the possible domain handling behaviours for a
26/// [`GlibLogger`](struct.GlibLogger.html).
27///
28/// In order to use this type, `glib` must be built with the `log` feature
29/// enabled.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum GlibLoggerDomain {
32 // rustdoc-stripper-ignore-next
33 /// Logs will have no domain specified.
34 None,
35 // rustdoc-stripper-ignore-next
36 /// Logs will use the `target` of the log crate as a domain; this allows
37 /// Rust code, like `warn!(target: "my-domain", "...");` to log to the glib
38 /// logger using the specified domain.
39 CrateTarget,
40 // rustdoc-stripper-ignore-next
41 /// Logs will use the crate path as the log domain.
42 CratePath,
43}
44
45// rustdoc-stripper-ignore-next
46/// An implementation of a [`log`](https://crates.io/crates/log) compatible
47/// logger which logs over glib logging facilities.
48///
49/// In order to use this type, `glib` must be built with the `log` feature
50/// enabled.
51///
52/// Use this if you want to use glib as the main logging output in your application,
53/// and want to route all logging happening through the log crate to glib logging.
54/// If you want the opposite, see
55/// [`rust_log_handler`](fn.rust_log_handler.html).
56///
57/// NOTE: This should never be used when
58/// [`rust_log_handler`](fn.rust_log_handler.html) has
59/// been registered as a default glib log handler, otherwise a stack overflow
60/// will occur.
61///
62/// Example:
63///
64/// ```no_compile
65/// static glib_logger: glib::GlibLogger = glib::GlibLogger::new(
66/// glib::GlibLoggerFormat::Plain,
67/// glib::GlibLoggerDomain::CrateTarget,
68/// );
69///
70/// log::set_logger(&glib_logger);
71/// log::set_max_level(log::LevelFilter::Debug);
72///
73/// log::info!("This line will get logged by glib");
74/// ```
75#[derive(Debug)]
76pub struct GlibLogger {
77 format: GlibLoggerFormat,
78 domain: GlibLoggerDomain,
79}
80
81impl GlibLogger {
82 // rustdoc-stripper-ignore-next
83 /// Creates a new instance of [`GlibLogger`](struct.GlibLogger.html).
84 /// See documentation of [`GlibLogger`](struct.GlibLogger.html) for more
85 /// information.
86 ///
87 /// Example:
88 ///
89 /// ```no_compile
90 /// static glib_logger: glib::GlibLogger = glib::GlibLogger::new(
91 /// glib::GlibLoggerFormat::Plain,
92 /// glib::GlibLoggerDomain::CrateTarget,
93 /// );
94 ///
95 /// log::set_logger(&glib_logger);
96 /// log::set_max_level(log::LevelFilter::Debug);
97 ///
98 /// log::info!("This line will get logged by glib");
99 /// ```
100 pub const fn new(format: GlibLoggerFormat, domain: GlibLoggerDomain) -> Self {
101 Self { format, domain }
102 }
103
104 fn level_to_glib(level: rs_log::Level) -> crate::LogLevel {
105 match level {
106 // Errors are mapped to critical to avoid automatic termination
107 rs_log::Level::Error => crate::LogLevel::Critical,
108 rs_log::Level::Warn => crate::LogLevel::Warning,
109 rs_log::Level::Info => crate::LogLevel::Info,
110 rs_log::Level::Debug => crate::LogLevel::Debug,
111 // There is no equivalent to trace level in glib
112 rs_log::Level::Trace => crate::LogLevel::Debug,
113 }
114 }
115
116 #[doc(alias = "g_log")]
117 fn write_log(domain: Option<&str>, level: rs_log::Level, message: &std::fmt::Arguments<'_>) {
118 unsafe {
119 use std::fmt::Write;
120
121 let mut message_builder = crate::GStringBuilder::default();
122 if write!(&mut message_builder, "{}", message).is_err() {
123 return;
124 }
125 let message = message_builder.into_string();
126
127 crate::ffi::g_log(
128 domain.to_glib_none().0,
129 GlibLogger::level_to_glib(level).into_glib(),
130 b"%s\0".as_ptr() as *const _,
131 ToGlibPtr::<*const std::os::raw::c_char>::to_glib_none(&message).0,
132 );
133 }
134 }
135
136 fn write_log_structured(
137 domain: Option<&str>,
138 level: rs_log::Level,
139 file: Option<&str>,
140 line: Option<u32>,
141 func: Option<&str>,
142 message: &str,
143 ) {
144 // Write line number into a static array to avoid allocating its string
145 // representation. 16 bytes allow 10^15 lines, which should be more than
146 // sufficient.
147 let mut line_buffer = [0u8; 16];
148 let line = {
149 use std::io::{Cursor, Write};
150 let mut c = Cursor::new(line_buffer.as_mut_slice());
151 match line {
152 Some(lineno) => write!(&mut c, "{lineno}").ok(),
153 None => write!(&mut c, "<unknown line>").ok(),
154 };
155 let pos = c.position() as usize;
156 &line_buffer[..pos]
157 };
158 let glib_level = GlibLogger::level_to_glib(level);
159 let fields = [
160 LogField::new(gstr!("PRIORITY"), glib_level.priority().as_bytes()),
161 LogField::new(
162 gstr!("CODE_FILE"),
163 file.unwrap_or("<unknown file>").as_bytes(),
164 ),
165 LogField::new(gstr!("CODE_LINE"), line),
166 LogField::new(
167 gstr!("CODE_FUNC"),
168 func.unwrap_or("<unknown module path>").as_bytes(),
169 ),
170 LogField::new(gstr!("MESSAGE"), message.as_bytes()),
171 LogField::new(gstr!("GLIB_DOMAIN"), domain.unwrap_or("default").as_bytes()),
172 ];
173 log_structured_array(glib_level, &fields);
174 }
175}
176
177impl rs_log::Log for GlibLogger {
178 fn enabled(&self, _: &rs_log::Metadata) -> bool {
179 true
180 }
181
182 fn log(&self, record: &rs_log::Record) {
183 if !self.enabled(record.metadata()) {
184 return;
185 }
186
187 let domain = match &self.domain {
188 GlibLoggerDomain::None => None,
189 GlibLoggerDomain::CrateTarget => Some(record.metadata().target()),
190 GlibLoggerDomain::CratePath => record.module_path(),
191 };
192
193 match self.format {
194 GlibLoggerFormat::Plain => {
195 GlibLogger::write_log(domain, record.level(), record.args());
196 }
197 GlibLoggerFormat::LineAndFile => {
198 match (record.file(), record.line()) {
199 (Some(file), Some(line)) => {
200 GlibLogger::write_log(
201 domain,
202 record.level(),
203 &format_args!("{}:{}: {}", file, line, record.args()),
204 );
205 }
206 (Some(file), None) => {
207 GlibLogger::write_log(
208 domain,
209 record.level(),
210 &format_args!("{}: {}", file, record.args()),
211 );
212 }
213 _ => {
214 GlibLogger::write_log(domain, record.level(), record.args());
215 }
216 };
217 }
218 GlibLoggerFormat::Structured => {
219 let args = record.args();
220 let args_str;
221 let message = if let Some(s) = args.as_str() {
222 s
223 } else {
224 args_str = args.to_string();
225 &args_str
226 };
227 GlibLogger::write_log_structured(
228 domain,
229 record.level(),
230 record.file(),
231 record.line(),
232 record.module_path(),
233 message,
234 );
235 }
236 };
237 }
238
239 fn flush(&self) {}
240}
241
242// rustdoc-stripper-ignore-next
243/// Provides a glib log handler which routes all logging messages to the
244/// [`log crate`](https://crates.io/crates/log).
245///
246/// In order to use this function, `glib` must be built with the `log` feature
247/// enabled.
248///
249/// Use this function if you want to use the log crate as the main logging
250/// output in your application, and want to route all logging happening in
251/// glib to the log crate. If you want the opposite, use [`GlibLogger`](struct.GlibLogger.html).
252///
253/// NOTE: This should never be used when [`GlibLogger`](struct.GlibLogger.html) is
254/// registered as a logger, otherwise a stack overflow will occur.
255///
256/// ```no_run
257/// glib::log_set_default_handler(glib::rust_log_handler);
258/// ```
259pub fn rust_log_handler(domain: Option<&str>, level: glib_log::LogLevel, message: &str) {
260 let level = match level {
261 glib_log::LogLevel::Error | glib_log::LogLevel::Critical => rs_log::Level::Error,
262 glib_log::LogLevel::Warning => rs_log::Level::Warn,
263 glib_log::LogLevel::Message | glib_log::LogLevel::Info => rs_log::Level::Info,
264 glib_log::LogLevel::Debug => rs_log::Level::Debug,
265 };
266
267 rs_log::log!(target: domain.unwrap_or("<null>"), level, "{}", message);
268}
269
270// rustdoc-stripper-ignore-next
271/// A macro which behaves exactly as `log::error!` except that it sets the
272/// current log target to the contents of a `G_LOG_DOMAIN` constant (and fails
273/// to build if not defined).
274///
275/// In order to use this macro, `glib` must be built with the `log_macros`
276/// feature enabled and the [`GlibLogger`](struct.GlibLogger.html) must have been
277/// initialized using [`GlibLoggerDomain::CrateTarget`](enum.GlibLoggerDomain.html).
278///
279/// ```no_run
280/// static G_LOG_DOMAIN: &str = "my-domain";
281///
282/// glib::error!("This will be logged under 'my-domain'");
283/// ```
284#[macro_export]
285#[cfg(any(docsrs, feature = "log_macros"))]
286#[cfg_attr(docsrs, doc(cfg(feature = "log_macros")))]
287macro_rules! error {
288 (target: $target:expr, $($arg:tt)+) => (
289 $crate::rs_log::log!(target: $target, $crate::rs_log::Level::Error, $($arg)+);
290 );
291 ($($arg:tt)+) => (
292 $crate::rs_log::log!(target: G_LOG_DOMAIN, $crate::rs_log::Level::Error, $($arg)+);
293 )
294}
295
296// rustdoc-stripper-ignore-next
297/// A macro which behaves exactly as `log::warn!` except that it sets the
298/// current log target to the contents of a `G_LOG_DOMAIN` constant (and fails
299/// to build if not defined).
300///
301/// In order to use this macro, `glib` must be built with the `log_macros`
302/// feature enabled and the [`GlibLogger`](struct.GlibLogger.html) must have been
303/// initialized using [`GlibLoggerDomain::CrateTarget`](enum.GlibLoggerDomain.html).
304///
305/// ```no_run
306/// static G_LOG_DOMAIN: &str = "my-domain";
307///
308/// glib::warn!("This will be logged under 'my-domain'");
309/// ```
310#[macro_export]
311#[cfg(any(docsrs, feature = "log_macros"))]
312#[cfg_attr(docsrs, doc(cfg(feature = "log_macros")))]
313macro_rules! warn {
314 (target: $target:expr, $($arg:tt)+) => (
315 $crate::rs_log::log!(target: $target, $crate::rs_log::Level::Warn, $($arg)+);
316 );
317 ($($arg:tt)+) => (
318 $crate::rs_log::log!(target: G_LOG_DOMAIN, $crate::rs_log::Level::Warn, $($arg)+);
319 )
320}
321
322// rustdoc-stripper-ignore-next
323/// A macro which behaves exactly as `log::info!` except that it sets the
324/// current log target to the contents of a `G_LOG_DOMAIN` constant (and fails
325/// to build if not defined).
326///
327/// In order to use this macro, `glib` must be built with the `log_macros`
328/// feature enabled and the [`GlibLogger`](struct.GlibLogger.html) must have been
329/// initialized using [`GlibLoggerDomain::CrateTarget`](enum.GlibLoggerDomain.html).
330///
331/// ```no_run
332/// static G_LOG_DOMAIN: &str = "my-domain";
333///
334/// glib::info!("This will be logged under 'my-domain'");
335/// ```
336#[macro_export]
337#[cfg(any(docsrs, feature = "log_macros"))]
338#[cfg_attr(docsrs, doc(cfg(feature = "log_macros")))]
339macro_rules! info {
340 (target: $target:expr, $($arg:tt)+) => (
341 $crate::rs_log::log!(target: $target, $crate::rs_log::Level::Info, $($arg)+);
342 );
343 ($($arg:tt)+) => (
344 $crate::rs_log::log!(target: G_LOG_DOMAIN, $crate::rs_log::Level::Info, $($arg)+);
345 )
346}
347
348// rustdoc-stripper-ignore-next
349/// A macro which behaves exactly as `log::debug!` except that it sets the
350/// current log target to the contents of a `G_LOG_DOMAIN` constant (and fails
351/// to build if not defined).
352///
353/// In order to use this macro, `glib` must be built with the `log_macros`
354/// feature enabled and the [`GlibLogger`](struct.GlibLogger.html) must have been
355/// initialized using [`GlibLoggerDomain::CrateTarget`](enum.GlibLoggerDomain.html).
356///
357/// ```no_run
358/// static G_LOG_DOMAIN: &str = "my-domain";
359///
360/// glib::debug!("This will be logged under 'my-domain'");
361/// ```
362#[macro_export]
363#[cfg(any(docsrs, feature = "log_macros"))]
364#[cfg_attr(docsrs, doc(cfg(feature = "log_macros")))]
365macro_rules! debug {
366 (target: $target:expr, $($arg:tt)+) => (
367 $crate::rs_log::log!(target: $target, $crate::rs_log::Level::Debug, $($arg)+);
368 );
369 ($($arg:tt)+) => (
370 $crate::rs_log::log!(target: G_LOG_DOMAIN, $crate::rs_log::Level::Debug, $($arg)+);
371 )
372}
373
374// rustdoc-stripper-ignore-next
375/// A macro which behaves exactly as `log::trace!` except that it sets the
376/// current log target to the contents of a `G_LOG_DOMAIN` constant (and fails
377/// to build if not defined).
378///
379/// In order to use this macro, `glib` must be built with the `log_macros`
380/// feature enabled and the [`GlibLogger`](struct.GlibLogger.html) must have been
381/// initialized using [`GlibLoggerDomain::CrateTarget`](enum.GlibLoggerDomain.html).
382///
383/// ```no_run
384/// static G_LOG_DOMAIN: &str = "my-domain";
385///
386/// glib::trace!("This will be logged under 'my-domain'");
387/// ```
388#[macro_export]
389#[cfg(any(docsrs, feature = "log_macros"))]
390#[cfg_attr(docsrs, doc(cfg(feature = "log_macros")))]
391macro_rules! trace {
392 (target: $target:expr, $($arg:tt)+) => (
393 $crate::rs_log::log!(target: $target, $crate::rs_log::Level::Trace, $($arg)+);
394 );
395 ($($arg:tt)+) => (
396 $crate::rs_log::log!(target: G_LOG_DOMAIN, $crate::rs_log::Level::Trace, $($arg)+);
397 )
398}