spdlog/
log_crate_proxy.rs

1use std::time::SystemTime;
2
3use crate::{default_logger, sync::*, utils, LogCrateRecord, Logger};
4
5/// Proxy layer for compatible [log crate].
6///
7/// Call [`init_log_crate_proxy`] to initialize the proxy, and then configure
8/// the proxy via [`log_crate_proxy`].
9///
10/// After the proxy is initialized, it will forward all log messages from `log`
11/// crate to the global default logger or the logger set by
12/// [`LogCrateProxy::set_logger`].
13///
14/// To set filters or read from `RUST_LOG` variable, call
15/// [`LogCrateProxy::set_filter`] after initialization.
16///
17/// Note that the `log` crate uses a different log level filter and by default
18/// it rejects all log messages. To make `LogCrateProxy` able to receive log
19/// messages from `log` crate, you may need to call
20/// [`re_export::log::set_max_level`] with [`re_export::log::LevelFilter`].
21///
22/// ## Examples
23///
24/// ```
25/// use spdlog::re_export::log;
26///
27/// # fn main() -> Result<(), log::SetLoggerError> {
28/// spdlog::init_log_crate_proxy()?;
29/// // Enable all log messages from `log` crate.
30/// log::set_max_level(log::LevelFilter::Trace);
31/// # Ok(()) }
32/// ```
33///
34/// For more and detailed examples, see [./examples] directory.
35///
36/// [log crate]: https://crates.io/crates/log
37/// [`init_log_crate_proxy`]: crate::init_log_crate_proxy
38/// [`log_crate_proxy`]: crate::log_crate_proxy()
39/// [`re_export::log::set_max_level`]: crate::re_export::log::set_max_level
40/// [`re_export::log::LevelFilter`]: crate::re_export::log::LevelFilter
41/// [./examples]: https://github.com/SpriteOvO/spdlog-rs/tree/main/spdlog/examples
42#[derive(Default)]
43pub struct LogCrateProxy {
44    logger: ArcSwapOption<Logger>,
45    filter: ArcSwapOption<env_filter::Filter>,
46}
47
48impl LogCrateProxy {
49    #[must_use]
50    pub(crate) fn new() -> Self {
51        Self::default()
52    }
53
54    /// Sets a logger as the new receiver, and returens the old one.
55    ///
56    /// If the argument `logger` is `None`, the global default logger will be
57    /// used.
58    pub fn swap_logger(&self, logger: Option<Arc<Logger>>) -> Option<Arc<Logger>> {
59        self.logger.swap(logger)
60    }
61
62    /// Sets a logger as the new receiver.
63    ///
64    /// If the argument `logger` is `None`, the global default logger will be
65    /// used.
66    pub fn set_logger(&self, logger: Option<Arc<Logger>>) {
67        self.swap_logger(logger);
68    }
69
70    /// Sets a filter for records from `log` crate.
71    ///
72    /// This is useful if users want to support `RUST_LOG` environment variable.
73    pub fn set_filter(&self, filter: Option<env_filter::Filter>) {
74        self.filter.swap(filter.map(Arc::new));
75    }
76
77    #[must_use]
78    fn logger(&self) -> Arc<Logger> {
79        self.logger.load_full().unwrap_or_else(default_logger)
80    }
81}
82
83impl log::Log for LogCrateProxy {
84    fn enabled(&self, metadata: &log::Metadata) -> bool {
85        let filter = self.filter.load();
86        utils::is_none_or(filter.as_deref(), |filter| filter.enabled(metadata))
87            && self.logger().should_log(metadata.level().into())
88    }
89
90    fn log(&self, record: &log::Record) {
91        if utils::is_none_or(self.filter.load().as_deref(), |filter| {
92            filter.matches(record)
93        }) {
94            let logger = self.logger();
95            let record = LogCrateRecord::new(&logger, record, SystemTime::now());
96            logger.log(&record.as_record())
97        }
98    }
99
100    fn flush(&self) {
101        self.logger().flush()
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use crate::test_utils::*;
109
110    #[test]
111    fn proxy() {
112        crate::init_log_crate_proxy().unwrap();
113        log::set_max_level(log::LevelFilter::Debug);
114
115        let sink = Arc::new(TestSink::new());
116        crate::log_crate_proxy()
117            .set_logger(Some(Arc::new(build_test_logger(|b| b.sink(sink.clone())))));
118        crate::log_crate_proxy().set_filter(Some(
119            env_filter::Builder::new()
120                .filter_module(
121                    "spdlog::log_crate_proxy::tests::should_be_filtered_out",
122                    log::LevelFilter::Off,
123                )
124                .filter(None, log::LevelFilter::Trace)
125                .build(),
126        ));
127
128        assert_eq!(sink.log_count(), 0);
129
130        log::info!("hello");
131        log::error!("world");
132        mod should_be_filtered_out {
133            pub fn log_something() {
134                log::warn!("this should be filtered out");
135            }
136        }
137        should_be_filtered_out::log_something();
138
139        assert_eq!(sink.log_count(), 2);
140        assert_eq!(
141            sink.payloads(),
142            vec!["hello".to_string(), "world".to_string()]
143        );
144    }
145}