1 // Take a look at the license at the top of the repository in the LICENSE file.
2 
3 use crate::log as glib_log;
4 use crate::translate::*;
5 
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)]
12 pub enum GlibLoggerFormat {
13     /// A simple format, writing only the message on output.
14     Plain,
15     /// A simple format, writing file, line and message on output.
16     LineAndFile,
17     /// A logger using glib structured logging. Structured logging is available
18     /// only on features `v2_56` and later.
19     #[cfg(any(feature = "v2_56", feature = "dox"))]
20     Structured,
21 }
22 
23 /// Enumeration of the possible domain handling behaviours for a
24 /// [`GlibLogger`](struct.GlibLogger.html).
25 ///
26 /// In order to use this type, `glib` must be built with the `log` feature
27 /// enabled.
28 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
29 pub enum GlibLoggerDomain {
30     /// Logs will have no domain specified.
31     None,
32     /// Logs will use the `target` of the log crate as a domain; this allows
33     /// Rust code, like `warn!(target: "my-domain", "...");` to log to the glib
34     /// logger using the specified domain.
35     CrateTarget,
36     /// Logs will use the crate path as the log domain.
37     CratePath,
38 }
39 
40 /// An implementation of a [`log`](https://crates.io/crates/log) compatible
41 /// logger which logs over glib logging facilities.
42 ///
43 /// In order to use this type, `glib` must be built with the `log` feature
44 /// enabled.
45 ///
46 /// Use this if you want to use glib as the main logging output in your application,
47 /// and want to route all logging happening through the log crate to glib logging.
48 /// If you want the opposite, see
49 /// [`rust_log_handler`](fn.rust_log_handler.html).
50 ///
51 /// NOTE: This should never be used when
52 /// [`rust_log_handler`](fn.rust_log_handler.html) has
53 /// been registered as a default glib log handler, otherwise a stack overflow
54 /// will occur.
55 ///
56 /// Example:
57 ///
58 /// ```no_compile
59 /// static glib_logger: glib::GlibLogger = glib::GlibLogger::new(
60 ///     glib::GlibLoggerFormat::Plain,
61 ///     glib::GlibLoggerDomain::CrateTarget,
62 /// );
63 ///
64 /// log::set_logger(&glib_logger);
65 /// log::set_max_level(log::LevelFilter::Debug);
66 ///
67 /// log::info!("This line will get logged by glib");
68 /// ```
69 #[derive(Debug)]
70 pub struct GlibLogger {
71     format: GlibLoggerFormat,
72     domain: GlibLoggerDomain,
73 }
74 
75 impl GlibLogger {
76     /// Creates a new instance of [`GlibLogger`](struct.GlibLogger.html).
77     /// See documentation of [`GlibLogger`](struct.GlibLogger.html) for more
78     /// information.
79     ///
80     /// Example:
81     ///
82     /// ```no_compile
83     /// static glib_logger: glib::GlibLogger = glib::GlibLogger::new(
84     ///     glib::GlibLoggerFormat::Plain,
85     ///     glib::GlibLoggerDomain::CrateTarget,
86     /// );
87     ///
88     /// log::set_logger(&glib_logger);
89     /// log::set_max_level(log::LevelFilter::Debug);
90     ///
91     /// log::info!("This line will get logged by glib");
92     /// ```
new(format: GlibLoggerFormat, domain: GlibLoggerDomain) -> Self93     pub const fn new(format: GlibLoggerFormat, domain: GlibLoggerDomain) -> Self {
94         Self { format, domain }
95     }
96 
level_to_glib(level: rs_log::Level) -> crate::ffi::GLogLevelFlags97     fn level_to_glib(level: rs_log::Level) -> crate::ffi::GLogLevelFlags {
98         match level {
99             // Errors are mapped to critical to avoid automatic termination
100             rs_log::Level::Error => crate::ffi::G_LOG_LEVEL_CRITICAL,
101             rs_log::Level::Warn => crate::ffi::G_LOG_LEVEL_WARNING,
102             rs_log::Level::Info => crate::ffi::G_LOG_LEVEL_INFO,
103             rs_log::Level::Debug => crate::ffi::G_LOG_LEVEL_DEBUG,
104             // There is no equivalent to trace level in glib
105             rs_log::Level::Trace => crate::ffi::G_LOG_LEVEL_DEBUG,
106         }
107     }
108 
109     #[doc(alias = "g_log")]
write_log(domain: Option<&str>, level: rs_log::Level, message: &str)110     fn write_log(domain: Option<&str>, level: rs_log::Level, message: &str) {
111         unsafe {
112             crate::ffi::g_log(
113                 domain.to_glib_none().0,
114                 GlibLogger::level_to_glib(level),
115                 message.replace("%", "%%").to_glib_none().0,
116             );
117         }
118     }
119 
120     #[cfg(any(feature = "v2_56", feature = "dox"))]
121     #[cfg_attr(feature = "dox", doc(cfg(feature = "v2_56")))]
122     #[doc(alias = "g_log_structured_standard")]
write_log_structured( domain: Option<&str>, level: rs_log::Level, file: Option<&str>, line: Option<u32>, func: Option<&str>, message: &str, )123     fn write_log_structured(
124         domain: Option<&str>,
125         level: rs_log::Level,
126         file: Option<&str>,
127         line: Option<u32>,
128         func: Option<&str>,
129         message: &str,
130     ) {
131         let line_str = line.map(|l| l.to_string());
132 
133         unsafe {
134             crate::ffi::g_log_structured_standard(
135                 domain.to_glib_none().0,
136                 GlibLogger::level_to_glib(level),
137                 file.to_glib_none().0,
138                 line_str.to_glib_none().0,
139                 func.to_glib_none().0,
140                 message.replace("%", "%%").to_glib_none().0,
141             );
142         }
143     }
144 }
145 
146 impl rs_log::Log for GlibLogger {
enabled(&self, _: &rs_log::Metadata) -> bool147     fn enabled(&self, _: &rs_log::Metadata) -> bool {
148         true
149     }
150 
log(&self, record: &rs_log::Record)151     fn log(&self, record: &rs_log::Record) {
152         if !self.enabled(record.metadata()) {
153             return;
154         }
155 
156         let domain = match &self.domain {
157             GlibLoggerDomain::None => None,
158             GlibLoggerDomain::CrateTarget => Some(record.metadata().target()),
159             GlibLoggerDomain::CratePath => record.module_path(),
160         };
161 
162         match self.format {
163             GlibLoggerFormat::Plain => {
164                 let s = format!("{}", record.args());
165                 GlibLogger::write_log(domain, record.level(), &s)
166             }
167             GlibLoggerFormat::LineAndFile => {
168                 let s = match (record.file(), record.line()) {
169                     (Some(file), Some(line)) => format!("{}:{}: {}", file, line, record.args()),
170                     (Some(file), None) => format!("{}: {}", file, record.args()),
171                     _ => format!("{}", record.args()),
172                 };
173 
174                 GlibLogger::write_log(domain, record.level(), &s);
175             }
176             #[cfg(any(feature = "v2_56", feature = "dox"))]
177             GlibLoggerFormat::Structured => {
178                 GlibLogger::write_log_structured(
179                     domain,
180                     record.level(),
181                     record.file(),
182                     record.line(),
183                     None,
184                     &format!("{}", record.args()),
185                 );
186             }
187         };
188     }
189 
flush(&self)190     fn flush(&self) {}
191 }
192 
193 /// Provides a glib log handler which routes all logging messages to the
194 /// [`log crate`](https://crates.io/crates/log).
195 ///
196 /// In order to use this function, `glib` must be built with the `log` feature
197 /// enabled.
198 ///
199 /// Use this function if you want to use the log crate as the main logging
200 /// output in your application, and want to route all logging happening in
201 /// glib to the log crate. If you want the opposite, use [`GlibLogger`](struct.GlibLogger.html).
202 ///
203 /// NOTE: This should never be used when [`GlibLogger`](struct.GlibLogger.html) is
204 /// registered as a logger, otherwise a stack overflow will occur.
205 ///
206 /// ```no_run
207 /// glib::log_set_default_handler(glib::rust_log_handler);
208 /// ```
rust_log_handler(domain: Option<&str>, level: glib_log::LogLevel, message: &str)209 pub fn rust_log_handler(domain: Option<&str>, level: glib_log::LogLevel, message: &str) {
210     let level = match level {
211         glib_log::LogLevel::Error | glib_log::LogLevel::Critical => rs_log::Level::Error,
212         glib_log::LogLevel::Warning => rs_log::Level::Warn,
213         glib_log::LogLevel::Message | glib_log::LogLevel::Info => rs_log::Level::Info,
214         glib_log::LogLevel::Debug => rs_log::Level::Debug,
215     };
216 
217     rs_log::log!(target: domain.unwrap_or("<null>"), level, "{}", message);
218 }
219 
220 /// A macro which behaves exactly as `log::error!` except that it sets the
221 /// current log target to the contents of a `G_LOG_DOMAIN` constant (and fails
222 /// to build if not defined).
223 ///
224 /// In order to use this macro, `glib` must be built with the `log_macros`
225 /// feature enabled and the [`GlibLogger`](struct.GlibLogger.html) must have been
226 /// initialized using [`GlibLoggerDomain::CrateTarget`](enum.GlibLoggerDomain.html).
227 ///
228 /// ```no_run
229 /// static G_LOG_DOMAIN: &str = "my-domain";
230 ///
231 /// glib::error!("This will be logged under 'my-domain'");
232 /// ```
233 #[macro_export]
234 #[cfg(any(feature = "dox", feature = "log_macros"))]
235 #[cfg_attr(feature = "dox", doc(cfg(feature = "log_macros")))]
236 macro_rules! error {
237     (target: $target:expr, $($arg:tt)+) => (
238         $crate::rs_log::log!(target: $target, $crate::rs_log::Level::Error, $($arg)+);
239     );
240     ($($arg:tt)+) => (
241         $crate::rs_log::log!(target: G_LOG_DOMAIN, $crate::rs_log::Level::Error, $($arg)+);
242     )
243 }
244 
245 /// A macro which behaves exactly as `log::warn!` except that it sets the
246 /// current log target to the contents of a `G_LOG_DOMAIN` constant (and fails
247 /// to build if not defined).
248 ///
249 /// In order to use this macro, `glib` must be built with the `log_macros`
250 /// feature enabled and the [`GlibLogger`](struct.GlibLogger.html) must have been
251 /// initialized using [`GlibLoggerDomain::CrateTarget`](enum.GlibLoggerDomain.html).
252 ///
253 /// ```no_run
254 /// static G_LOG_DOMAIN: &str = "my-domain";
255 ///
256 /// glib::warn!("This will be logged under 'my-domain'");
257 /// ```
258 #[macro_export]
259 #[cfg(any(feature = "dox", feature = "log_macros"))]
260 #[cfg_attr(feature = "dox", doc(cfg(feature = "log_macros")))]
261 macro_rules! warn {
262     (target: $target:expr, $($arg:tt)+) => (
263         $crate::rs_log::log!(target: $target, $crate::rs_log::Level::Warn, $($arg)+);
264     );
265     ($($arg:tt)+) => (
266         $crate::rs_log::log!(target: G_LOG_DOMAIN, $crate::rs_log::Level::Warn, $($arg)+);
267     )
268 }
269 
270 /// A macro which behaves exactly as `log::info!` except that it sets the
271 /// current log target to the contents of a `G_LOG_DOMAIN` constant (and fails
272 /// to build if not defined).
273 ///
274 /// In order to use this macro, `glib` must be built with the `log_macros`
275 /// feature enabled and the [`GlibLogger`](struct.GlibLogger.html) must have been
276 /// initialized using [`GlibLoggerDomain::CrateTarget`](enum.GlibLoggerDomain.html).
277 ///
278 /// ```no_run
279 /// static G_LOG_DOMAIN: &str = "my-domain";
280 ///
281 /// glib::info!("This will be logged under 'my-domain'");
282 /// ```
283 #[macro_export]
284 #[cfg(any(feature = "dox", feature = "log_macros"))]
285 #[cfg_attr(feature = "dox", doc(cfg(feature = "log_macros")))]
286 macro_rules! info {
287     (target: $target:expr, $($arg:tt)+) => (
288         $crate::rs_log::log!(target: $target, $crate::rs_log::Level::Info, $($arg)+);
289     );
290     ($($arg:tt)+) => (
291         $crate::rs_log::log!(target: G_LOG_DOMAIN, $crate::rs_log::Level::Info, $($arg)+);
292     )
293 }
294 
295 /// A macro which behaves exactly as `log::debug!` except that it sets the
296 /// current log target to the contents of a `G_LOG_DOMAIN` constant (and fails
297 /// to build if not defined).
298 ///
299 /// In order to use this macro, `glib` must be built with the `log_macros`
300 /// feature enabled and the [`GlibLogger`](struct.GlibLogger.html) must have been
301 /// initialized using [`GlibLoggerDomain::CrateTarget`](enum.GlibLoggerDomain.html).
302 ///
303 /// ```no_run
304 /// static G_LOG_DOMAIN: &str = "my-domain";
305 ///
306 /// glib::debug!("This will be logged under 'my-domain'");
307 /// ```
308 #[macro_export]
309 #[cfg(any(feature = "dox", feature = "log_macros"))]
310 #[cfg_attr(feature = "dox", doc(cfg(feature = "log_macros")))]
311 macro_rules! debug {
312     (target: $target:expr, $($arg:tt)+) => (
313         $crate::rs_log::log!(target: $target, $crate::rs_log::Level::Debug, $($arg)+);
314     );
315     ($($arg:tt)+) => (
316         $crate::rs_log::log!(target: G_LOG_DOMAIN, $crate::rs_log::Level::Debug, $($arg)+);
317     )
318 }
319 
320 /// A macro which behaves exactly as `log::trace!` except that it sets the
321 /// current log target to the contents of a `G_LOG_DOMAIN` constant (and fails
322 /// to build if not defined).
323 ///
324 /// In order to use this macro, `glib` must be built with the `log_macros`
325 /// feature enabled and the [`GlibLogger`](struct.GlibLogger.html) must have been
326 /// initialized using [`GlibLoggerDomain::CrateTarget`](enum.GlibLoggerDomain.html).
327 ///
328 /// ```no_run
329 /// static G_LOG_DOMAIN: &str = "my-domain";
330 ///
331 /// glib::trace!("This will be logged under 'my-domain'");
332 /// ```
333 #[macro_export]
334 #[cfg(any(feature = "dox", feature = "log_macros"))]
335 #[cfg_attr(feature = "dox", doc(cfg(feature = "log_macros")))]
336 macro_rules! trace {
337     (target: $target:expr, $($arg:tt)+) => (
338         $crate::rs_log::log!(target: $target, $crate::rs_log::Level::Trace, $($arg)+);
339     );
340     ($($arg:tt)+) => (
341         $crate::rs_log::log!(target: G_LOG_DOMAIN, $crate::rs_log::Level::Trace, $($arg)+);
342     )
343 }
344