Skip to main content

ferroui_base/logging/
logger.rs

1use std::cell::{Cell, RefCell};
2use std::rc::Rc;
3use std::sync::atomic::{AtomicBool, Ordering};
4use std::sync::{Arc, PoisonError, RwLock};
5
6use super::parametrized_logger::SinkHandle;
7use super::{ILogSink, LogEventLevel, ParametrizedLogger};
8
9static SINK: RwLock<Option<Arc<dyn ILogSink + Send + Sync>>> = RwLock::new(None);
10static HAS_SINK: AtomicBool = AtomicBool::new(false);
11
12crate::ferro_thread_local! {
13    static THREAD_SINK: RefCell<Option<Rc<dyn ILogSink>>> = const { RefCell::new(None) };
14    static HAS_THREAD_SINK: Cell<bool> = const { Cell::new(false) };
15}
16
17/// Logs events.
18///
19/// The sink is process-wide and therefore has to be thread-safe. In
20/// addition, a thread can install a sink of its own with
21/// [`set_thread_sink`](Self::set_thread_sink); it takes precedence over the
22/// process-wide sink on that thread, does not have to be thread-safe, and is
23/// what unit tests (which run in parallel) use to observe log events.
24pub struct Logger;
25
26impl Logger {
27    /// Gets the application-defined sink for logging.
28    pub fn sink() -> Option<Arc<dyn ILogSink + Send + Sync>> {
29        if !HAS_SINK.load(Ordering::Acquire) {
30            return None;
31        }
32        SINK.read().unwrap_or_else(PoisonError::into_inner).clone()
33    }
34
35    /// Sets the application-defined sink for logging.
36    pub fn set_sink(sink: Option<Arc<dyn ILogSink + Send + Sync>>) {
37        let previous = {
38            let mut slot = SINK.write().unwrap_or_else(PoisonError::into_inner);
39            HAS_SINK.store(sink.is_some(), Ordering::Release);
40            std::mem::replace(&mut *slot, sink)
41        };
42        drop(previous);
43    }
44
45    /// Gets the sink of the calling thread, if it has one.
46    pub fn thread_sink() -> Option<Rc<dyn ILogSink>> {
47        if !HAS_THREAD_SINK.try_with(Cell::get).unwrap_or(false) {
48            return None;
49        }
50        THREAD_SINK.try_with(|sink| sink.borrow().clone()).ok().flatten()
51    }
52
53    /// Sets a sink that receives the events logged on the calling thread
54    /// instead of the process-wide sink. Returns the previous thread sink.
55    pub fn set_thread_sink(sink: Option<Rc<dyn ILogSink>>) -> Option<Rc<dyn ILogSink>> {
56        HAS_THREAD_SINK.with(|has| has.set(sink.is_some()));
57        THREAD_SINK.with(|slot| std::mem::replace(&mut *slot.borrow_mut(), sink))
58    }
59
60    fn current_sink() -> Option<SinkHandle> {
61        if let Some(sink) = Self::thread_sink() {
62            return Some(SinkHandle::Thread(sink));
63        }
64        Self::sink().map(SinkHandle::Global)
65    }
66
67    /// Checks if given log level is enabled.
68    pub fn is_enabled(level: LogEventLevel, area: &str) -> bool {
69        match Self::current_sink() {
70            Some(sink) => sink.get().is_enabled(level, area),
71            None => false,
72        }
73    }
74
75    /// Returns a parametrized logging sink if given log level is enabled.
76    pub fn try_get(level: LogEventLevel, area: &'static str) -> Option<ParametrizedLogger> {
77        let sink = Self::current_sink()?;
78        if !sink.get().is_enabled(level, area) {
79            return None;
80        }
81
82        Some(ParametrizedLogger::from_handle(sink, level, area))
83    }
84}