chalkydri/
logger.rs

1//!
2//! Chalkydri's custom logger
3//!
4
5#[cfg(feature = "rerun")]
6use crate::Rerun;
7
8use env_logger::Builder;
9use log::Log;
10#[cfg(feature = "rerun")]
11use re_sdk::{external::re_log, RecordingStream};
12#[cfg(feature = "rerun")]
13use re_types::{archetypes::TextLog, components::TextLogLevel};
14
15/// Custom [log::Log] implementation based on `rerun`'s
16///
17/// Implements a [`log::Log`] that forwards all events to the Rerun SDK.
18#[derive(Debug)]
19pub struct Logger {
20    logger: Option<env_logger::Logger>,
21    path_prefix: Option<String>,
22}
23impl Logger {
24    /// Returns a new [`Logger`] that forwards all events to the specified [`RecordingStream`].
25    pub fn new() -> Self {
26        Self {
27            logger: None,
28            path_prefix: None,
29        }
30    }
31
32    /// Configures the [`Logger`] to prefix the specified `path_prefix` to all events.
33    #[inline]
34    pub fn with_path_prefix(mut self, path_prefix: impl Into<String>) -> Self {
35        self.path_prefix = Some(path_prefix.into());
36        self
37    }
38
39    /// Configures the [`Logger`] to filter events.
40    ///
41    /// This uses the familiar [env_logger syntax].
42    ///
43    /// If you don't call this, the [`Logger`] will parse the `RUST_LOG` environment variable
44    /// instead when you [`Logger::init`] it.
45    ///
46    /// [env_logger syntax]: https://docs.rs/env_logger/latest/env_logger/index.html#enabling-logging
47    #[inline]
48    pub fn with_filter(mut self, filter: impl AsRef<str>) -> Self {
49        self.logger = Some(Builder::new().parse_filters(filter.as_ref()).build());
50        self
51    }
52
53    /// Sets the [`Logger`] as global logger.
54    ///
55    /// All calls to [`log`] macros will go through this [`Logger`] from this point on.
56    pub fn init(mut self) -> Result<(), log::SetLoggerError> {
57        if self.logger.is_none() {
58            #[cfg(feature = "rerun")]
59            {
60                self.logger = Some(
61                    Builder::new()
62                        .parse_filters(&re_log::default_log_filter())
63                        .build(),
64                );
65            }
66            #[cfg(not(feature = "rerun"))]
67            {
68                self.logger = Some(Builder::new().parse_default_env().build());
69            }
70        }
71
72        // NOTE: We will have to make filtering decisions on a per-crate/module basis, therefore
73        // there is no global filtering ceiling.
74        log::set_max_level(log::LevelFilter::max());
75        log::set_boxed_logger(Box::new(self))
76    }
77}
78impl log::Log for Logger {
79    #[inline]
80    fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
81        self.logger
82            .as_ref()
83            .map_or(true, |filter| filter.enabled(metadata))
84    }
85
86    #[inline]
87    fn log(&self, record: &log::Record<'_>) {
88        if !self
89            .logger
90            .as_ref()
91            .map_or(true, |filter| filter.matches(record))
92        {
93            return;
94        }
95
96        // Do normal logging to console
97        self.logger.as_ref().map(|logger| logger.log(record));
98
99        // Do logging to Rerun
100        #[cfg(feature = "rerun")]
101        {
102            let target = record.metadata().target().replace("::", "/");
103            let ent_path = if let Some(path_prefix) = self.path_prefix.as_ref() {
104                format!("{path_prefix}/{target}")
105            } else {
106                target
107            };
108
109            let level = log_level_to_rerun_level(record.metadata().level());
110
111            let body = format!("{}", record.args());
112
113            Rerun
114                .log(ent_path, &TextLog::new(body).with_level(level))
115                .ok(); // ignore error
116        }
117    }
118
119    #[inline]
120    fn flush(&self) {
121        #[cfg(feature = "rerun")]
122        Rerun.flush_blocking();
123    }
124}
125impl Drop for Logger {
126    fn drop(&mut self) {
127        self.flush();
128    }
129}
130
131// ---
132
133#[cfg(feature = "rerun")]
134fn log_level_to_rerun_level(lvl: log::Level) -> TextLogLevel {
135    match lvl {
136        log::Level::Error => TextLogLevel::ERROR,
137        log::Level::Warn => TextLogLevel::WARN,
138        log::Level::Info => TextLogLevel::INFO,
139        log::Level::Debug => TextLogLevel::DEBUG,
140        log::Level::Trace => TextLogLevel::TRACE,
141    }
142    .into()
143}