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}