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}