drive_abci/logging/
logger.rs

1use crate::logging::config::LogConfig;
2use crate::logging::destination::{LogDestinationWriter, Writer};
3use crate::logging::error::Error;
4use crate::logging::{LogConfigs, LogFormat, LogLevel};
5use lazy_static::__Deref;
6use std::collections::HashMap;
7use std::fmt::Debug;
8use std::io::{IsTerminal, Write};
9use std::sync::Arc;
10use std::sync::Mutex;
11use tracing_subscriber::fmt;
12use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt;
13use tracing_subscriber::registry;
14use tracing_subscriber::util::SubscriberInitExt;
15use tracing_subscriber::EnvFilter;
16use tracing_subscriber::Layer;
17use tracing_subscriber::Registry;
18
19/// Name of logging configuration
20pub type LoggerID = String;
21
22/// LogBuilder is a builder for configuring and initializing logging subsystem.
23///
24/// # Examples
25///
26/// ```
27/// use drive_abci::logging::LogBuilder;
28/// use drive_abci::logging::LogConfigs;
29/// use drive_abci::logging::LogConfig;
30///
31/// // Create a new LogBuilder instance
32/// let mut log_builder = LogBuilder::new();
33///
34/// // Define your LogConfigs
35/// let mut log_configs = LogConfigs::new();
36/// log_configs.insert("config1".to_string(), LogConfig::default());
37///
38/// // Add all configs to the LogBuilder
39/// log_builder = log_builder.with_configs(&log_configs).unwrap();
40///
41/// // Add an individual config to the LogBuilder
42/// let config2 = LogConfig::default();
43/// log_builder = log_builder.with_config("config2", &config2).unwrap();
44///
45/// // Build the logging subsystem
46/// let loggers = log_builder.build();
47///
48/// // Install logging subsystem handler
49/// loggers.install();
50/// ```
51#[derive(Default)]
52pub struct LogBuilder {
53    loggers: HashMap<LoggerID, Logger>,
54}
55
56use std::sync::OnceLock;
57use tracing::Dispatch;
58// std, no external crate
59
60static LOGGING_INSTALLED: OnceLock<()> = OnceLock::new();
61
62impl LogBuilder {
63    /// Creates a new `LogBuilder` instance with default settings.
64    pub fn new() -> Self {
65        Default::default()
66    }
67
68    /// Adds multiple logging configurations to the `LogBuilder` at once.
69    ///
70    /// # Examples
71    ///
72    /// ```
73    /// use drive_abci::logging::LogBuilder;
74    /// use drive_abci::logging::LogConfigs;
75    ///
76    /// let mut log_builder = LogBuilder::new();
77    /// let mut log_configs = LogConfigs::new();
78    ///
79    /// // Add configurations to log_configs
80    ///
81    /// log_builder = log_builder.with_configs(&log_configs).unwrap();
82    /// ```
83    pub fn with_configs(self, configs: &LogConfigs) -> Result<Self, Error> {
84        let mut me = self;
85        for (name, config) in configs {
86            me.add(name, config)?;
87        }
88        Ok(me)
89    }
90
91    /// Adds a single logging configuration to the `LogBuilder`.
92    ///
93    /// # Examples
94    ///
95    /// ```
96    /// use drive_abci::logging::LogBuilder;
97    /// use drive_abci::logging::LogConfig;
98    ///
99    /// let log_builder = LogBuilder::new();
100    /// let config = LogConfig::default();
101    ///
102    /// let log_builder = log_builder.with_config("config_name", &config).unwrap();
103    /// ```
104    pub fn with_config(self, configuration_name: &str, config: &LogConfig) -> Result<Self, Error> {
105        let mut me = self;
106        me.add(configuration_name, config)?;
107        Ok(me)
108    }
109
110    /// Adds a new logger to the `LogBuilder`.
111    pub fn add(&mut self, configuration_name: &str, config: &LogConfig) -> Result<(), Error> {
112        let logger = Logger::try_from(config)?;
113        if self.loggers.contains_key(configuration_name) {
114            return Err(Error::DuplicateConfigName(configuration_name.to_string()));
115        }
116        self.loggers.insert(configuration_name.to_string(), logger);
117        Ok(())
118    }
119
120    /// Finalizes the build process and constructs loggers collection.
121    ///
122    /// This method is called after configuring the builder with all desired settings. It consumes
123    /// the builder and returns the constructed object.
124    ///
125    /// # Panics
126    pub fn build(self) -> Loggers {
127        Loggers(self.loggers)
128    }
129}
130
131/// Collection of loggers defined using [LogBuilder].
132///
133/// This struct holds a collection of loggers created using the [LogBuilder].
134/// It provides methods for installing, flushing, and rotating logs.
135pub struct Loggers(HashMap<LoggerID, Logger>);
136
137impl Loggers {
138    /// Installs loggers as a global tracing handler.
139    ///
140    /// Installs loggers prepared in the [LogBuilder] as a global tracing handler. It must be called exactly once.
141    /// Panics if a global tracing handler was already defined.
142    ///
143    /// # Examples
144    ///
145    /// ```no_run
146    /// use drive_abci::logging::{LogBuilder, Loggers};
147    ///
148    /// // Create logger(s) using LogBuilder
149    /// let mut logger_builder = LogBuilder::new();
150    /// // Configure logger_builder using its methods
151    ///
152    /// // Build Loggers instance
153    /// let loggers: Loggers = logger_builder.build();
154    ///
155    /// // Install loggers as a global tracing handler
156    /// loggers.install();
157    /// ```
158    ///
159    /// # Panics
160    ///
161    /// This method panics if the logging subsystem is already initialized.
162    pub fn install(&self) {
163        if let Err(e) = self.try_install() {
164            panic!("Logging subsystem is already initialized: {}", e)
165        }
166    }
167
168    /// Returns a Logger with the specified ID.
169    pub fn get(&self, id: &str) -> Option<&Logger> {
170        self.0.get(id)
171    }
172
173    /// Build a subscriber containing all layers from these loggers.
174    pub fn as_subscriber(&self) -> Result<Dispatch, Error> {
175        let layers = self.tracing_subscriber_layers()?;
176        Ok(Dispatch::new(Registry::default().with(layers)))
177    }
178
179    /// Installs loggers prepared in the [LogBuilder] as a global tracing handler.
180    ///
181    /// Same as [Loggers::install()], but returns error if the logging subsystem is already initialized.
182    ///
183    /// # Example
184    ///
185    /// The following code can be used in tests. It ignores errors, as tests might actually call it more than once,
186    /// and we don't want to panic in this case.
187    ///
188    /// ```
189    /// drive_abci::logging::Loggers::default().try_install().ok();
190    /// ```
191    pub fn try_install(&self) -> Result<(), Error> {
192        // Fast path: somebody already installed – just return Ok(())
193        if LOGGING_INSTALLED.get().is_some() {
194            return Ok(()); // <- second and later calls are ignored
195        }
196
197        let layers = self.tracing_subscriber_layers()?;
198
199        registry()
200            .with(layers)
201            .try_init()
202            .map_err(Error::TryInitError)?;
203
204        // Mark as installed
205        let _ = LOGGING_INSTALLED.set(());
206        Ok(())
207    }
208    /// Returns tracing subscriber layers
209    pub fn tracing_subscriber_layers(&self) -> Result<Vec<Box<impl Layer<Registry>>>, Error> {
210        // Based on examples from https://docs.rs/tracing-subscriber/0.3.17/tracing_subscriber/layer/index.html
211
212        self.0
213            .values()
214            .map(|l| Ok(Box::new(l.layer()?)))
215            .collect::<Result<Vec<_>, _>>()
216    }
217
218    /// Flushes all loggers.
219    ///
220    /// In case of multiple errors, returns only the last one.
221    ///
222    /// # Errors
223    ///
224    /// Returns an error if there's an issue flushing any of the loggers.
225    pub fn flush(&self) -> Result<(), std::io::Error> {
226        let mut result = Ok(());
227        for logger in self.0.values() {
228            if let Err(e) = logger
229                .destination
230                .clone()
231                .lock()
232                .expect("logging lock poisoned")
233                .to_write()
234                .flush()
235            {
236                result = Err(e);
237            };
238        }
239        result
240    }
241
242    /// Triggers log rotation for log destinations that support this.
243    ///
244    /// In case of multiple errors, returns the error from the last logger.
245    ///
246    /// # Errors
247    ///
248    /// Returns an error if there's an issue rotating any of the logs.
249    pub fn rotate(&self) -> Result<(), Error> {
250        self.0.values().try_for_each(|logger| logger.rotate())
251    }
252}
253
254impl Default for Loggers {
255    /// Default loggers that are just printing human-readable logs, based on `RUST_LOG` env variable.
256    ///
257    /// Useful for tests.
258    ///
259    /// # Panics
260    ///
261    /// Panics in (a very unlikely) event when logger builder fails to add new logger.
262    ///
263    /// # Example
264    ///
265    /// ```
266    /// use drive_abci::logging::Loggers;
267    ///
268    /// Loggers::default().try_install().ok();
269    /// ```
270    fn default() -> Self {
271        let mut logger_builder = LogBuilder::new();
272
273        logger_builder
274            .add("default", &LogConfig::default())
275            .expect("cannot configure default logger");
276
277        logger_builder.build()
278    }
279}
280
281// Individual logger
282#[derive(Debug)]
283pub struct Logger {
284    /// Destination of logs; either absolute path to dir where log files will be stored, `stdout` or `stderr`
285    pub(super) destination: Arc<Mutex<LogDestinationWriter>>,
286
287    /// Log verbosity level preset
288    level: LogLevel,
289
290    /// Whether to use colored output
291    color: Option<bool>,
292
293    /// Log format to use
294    format: LogFormat,
295}
296
297impl TryFrom<&LogConfig> for Logger {
298    type Error = Error;
299    fn try_from(config: &LogConfig) -> Result<Self, Self::Error> {
300        let destination = config.try_into()?;
301
302        Ok(Self {
303            destination: Arc::new(Mutex::new(destination)),
304            level: config.level.clone(),
305            color: config.color,
306            format: config.format,
307        })
308    }
309}
310
311impl Default for Logger {
312    fn default() -> Self {
313        Self {
314            destination: Arc::new(Mutex::new(LogDestinationWriter::StdOut)),
315            level: LogLevel::Info,
316            color: None,
317            format: LogFormat::Full,
318        }
319    }
320}
321
322impl Logger {
323    /// Register the logger in a registry
324    // : Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>
325    fn layer(&self) -> Result<impl Layer<Registry>, Error> {
326        let ansi = self
327            .color
328            .unwrap_or_else(|| match self.destination.lock().unwrap().deref() {
329                LogDestinationWriter::StdOut => std::io::stdout().is_terminal(),
330                LogDestinationWriter::StdErr => std::io::stderr().is_terminal(),
331                _ => false,
332            });
333
334        let cloned = self.destination.clone();
335        let make_writer = { move || Writer::new(Arc::clone(&cloned)) };
336
337        let filter = EnvFilter::try_from(&self.level)?;
338
339        let formatter = fmt::layer::<Registry>()
340            .with_writer(make_writer)
341            .with_ansi(ansi)
342            .with_thread_names(true)
343            .with_thread_ids(true);
344
345        let formatter = match self.format {
346            LogFormat::Full => formatter.with_filter(filter).boxed(),
347            LogFormat::Compact => formatter.compact().with_filter(filter).boxed(),
348            LogFormat::Pretty => formatter.pretty().with_filter(filter).boxed(),
349            LogFormat::Json => formatter.json().with_filter(filter).boxed(),
350        };
351
352        Ok(formatter)
353    }
354
355    /// Rotate log files
356    pub fn rotate(&self) -> Result<(), Error> {
357        let cloned = self.destination.clone();
358        let guard = cloned.lock().expect("logging lock poisoned");
359
360        guard.rotate()
361    }
362}