semantic_release_cargo/
logger.rs

1use std::io::{self, Write};
2use std::sync::Mutex;
3
4use log::{Level, LevelFilter, Log};
5
6/// The default log level to use if no other is specified
7const DEFAULT_LOG_LEVEL: Level = Level::Warn;
8
9/// Return a default writer for a given level. In this case stderr for warn
10/// and error and stdout for all others.
11fn default_log_dest_for_level(level: Level) -> LogDestination<'static> {
12    match level {
13        Level::Error | Level::Warn => LogDestination::from(io::stderr()),
14        Level::Info | Level::Debug | Level::Trace => LogDestination::from(io::stdout()),
15    }
16}
17
18const fn level_into_level_filter(level: Level) -> LevelFilter {
19    match level {
20        Level::Error => LevelFilter::Error,
21        Level::Warn => LevelFilter::Warn,
22        Level::Info => LevelFilter::Info,
23        Level::Debug => LevelFilter::Debug,
24        Level::Trace => LevelFilter::Trace,
25    }
26}
27
28#[derive(Debug)]
29#[allow(unused)]
30pub enum Error {
31    Initialization,
32}
33
34impl std::error::Error for Error {}
35
36impl std::fmt::Display for Error {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        match self {
39            Self::Initialization => writeln!(f, "unable to initialize logger"),
40        }
41    }
42}
43
44/// LogDestinationWriter represents a Boxed dynamic trait object for Writing
45/// logs to. This will exclusively be exposed behind a [Mutex] and assigned to.
46/// an owning [Logger]
47type LogDestinationWriter<'a> = Box<dyn Write + Send + Sync + 'a>;
48
49/// A [Mutex]-wrapped dynamic [LogDestinationWriter]
50type LockableLogDestinationWriter<'a> = Mutex<LogDestinationWriter<'a>>;
51
52#[allow(unused)]
53enum LogDestination<'data> {
54    Single(LockableLogDestinationWriter<'data>),
55    Multi(Vec<LockableLogDestinationWriter<'data>>),
56}
57
58impl<'data> LogDestination<'data> {
59    #[allow(unused)]
60    fn push<W>(self, writer: W) -> Self
61    where
62        W: Write + Send + Sync + Sized + 'data,
63    {
64        let dest = {
65            let boxed_dest = Box::new(writer) as LogDestinationWriter;
66
67            Mutex::new(boxed_dest)
68        };
69
70        match self {
71            LogDestination::Single(inner) => Self::Multi(vec![inner, dest]),
72            LogDestination::Multi(mut inner) => {
73                inner.push(dest);
74                Self::Multi(inner)
75            }
76        }
77    }
78}
79
80impl<'data, W> From<W> for LogDestination<'data>
81where
82    W: Write + Send + Sync + Sized + 'data,
83{
84    fn from(writer: W) -> Self {
85        let dest = {
86            let boxed_dest = Box::new(writer) as LogDestinationWriter;
87
88            Mutex::new(boxed_dest)
89        };
90
91        LogDestination::Single(dest)
92    }
93}
94
95/// A builder for the internal logger. This exposes a builder pattern for
96/// setting all configurable options on the logger. Once finalized, init is
97/// called to finalize the configuration and set it globally..
98pub struct LoggerBuilder<'data> {
99    logger: Logger<'data>,
100}
101
102impl<'data> LoggerBuilder<'data> {
103    // A mirror of the module level default rescoped to a static logger constant.
104    const DEFAULT_LOG_LEVEL: Level = DEFAULT_LOG_LEVEL;
105}
106
107impl<'data> LoggerBuilder<'data> {
108    /// Finalizes a [Logger]'s configuration and assigns the global logger to
109    /// the current settings.
110    ///
111    /// # Errors
112    /// An error is returned if this is already set. Caller must guarantee this
113    /// is called no more than once.
114    #[allow(unused)]
115    pub fn finalize(self) -> Result<Box<Logger<'data>>, Error> {
116        let boxed_logger = Box::new(self.logger);
117
118        Ok(boxed_logger)
119    }
120
121    /// Set the error log level destination.
122    fn set_error_dest(mut self, dest: LogDestination<'data>) -> Self {
123        self.logger.error = dest;
124        self
125    }
126
127    /// Set the warn log level destination.
128    fn set_warn_dest(mut self, dest: LogDestination<'data>) -> Self {
129        self.logger.warn = dest;
130        self
131    }
132
133    /// Set the info log level destination.
134    fn set_info_dest(mut self, dest: LogDestination<'data>) -> Self {
135        self.logger.info = dest;
136        self
137    }
138
139    /// Set the debug log level destination.
140    fn set_debug_dest(mut self, dest: LogDestination<'data>) -> Self {
141        self.logger.debug = dest;
142        self
143    }
144
145    /// Set the trace log level destination.
146    fn set_trace_dest(mut self, dest: LogDestination<'data>) -> Self {
147        self.logger.trace = dest;
148        self
149    }
150
151    /// Append a error log level destination writer.
152    fn append_error_dest<W>(mut self, dest: W) -> Self
153    where
154        W: Write + Send + Sync + Sized + 'static,
155    {
156        self.logger.error = self.logger.error.push(dest);
157        self
158    }
159
160    /// Append a warn log level destination writer.
161    fn append_warn_dest<W>(mut self, dest: W) -> Self
162    where
163        W: Write + Send + Sync + Sized + 'static,
164    {
165        self.logger.warn = self.logger.warn.push(dest);
166        self
167    }
168
169    /// Append a info log level destination writer.
170    fn append_info_dest<W>(mut self, dest: W) -> Self
171    where
172        W: Write + Send + Sync + Sized + 'static,
173    {
174        self.logger.info = self.logger.info.push(dest);
175        self
176    }
177
178    /// Append a debug log level destination writer.
179    fn append_debug_dest<W>(mut self, dest: W) -> Self
180    where
181        W: Write + Send + Sync + Sized + 'static,
182    {
183        self.logger.debug = self.logger.debug.push(dest);
184        self
185    }
186
187    /// Append a trace log level destination writer.
188    fn append_trace_dest<W>(mut self, dest: W) -> Self
189    where
190        W: Write + Send + Sync + Sized + 'static,
191    {
192        self.logger.trace = self.logger.trace.push(dest);
193        self
194    }
195
196    /// Set the output destination for a given log level.
197    pub fn output<W>(self, level: Level, dest: W) -> Self
198    where
199        W: Write + Send + Sync + Sized + 'static,
200    {
201        let log_destination = LogDestination::from(dest);
202        match level {
203            Level::Error => self.set_error_dest(log_destination),
204            Level::Warn => self.set_warn_dest(log_destination),
205            Level::Info => self.set_info_dest(log_destination),
206            Level::Debug => self.set_debug_dest(log_destination),
207            Level::Trace => self.set_trace_dest(log_destination),
208        }
209    }
210
211    /// Append an output destination for a given log level.
212    #[allow(unused)]
213    pub fn append_output<W>(self, level: Level, dest: W) -> Self
214    where
215        W: Write + Send + Sync + Sized + 'static,
216    {
217        match level {
218            Level::Error => self.append_error_dest(dest),
219            Level::Warn => self.append_warn_dest(dest),
220            Level::Info => self.append_info_dest(dest),
221            Level::Debug => self.append_debug_dest(dest),
222            Level::Trace => self.append_trace_dest(dest),
223        }
224    }
225
226    /// Sets the maximum log level explicitly to the value passed.
227    pub fn max_level(mut self, max_level: Level) -> Self {
228        self.logger.max_level = level_into_level_filter(max_level);
229
230        self
231    }
232
233    /// Sets the log level based off the number of verbosity flags are passed.
234    /// The verbosity argument functions as an offset from the default log
235    /// level where a value of `0` represents the default. Any value exceeding
236    /// the offset of `Trace`, will be counted as `Trace`.
237    #[allow(unused)]
238    pub(crate) fn verbosity(mut self, verbosity: u8) -> Self {
239        let verbosity = verbosity as usize;
240
241        // The new verbosity offset from the default log level.
242        let offset = (DEFAULT_LOG_LEVEL as usize) + verbosity;
243
244        let adjusted_max_level = match offset {
245            // there should be no case where 0 will occur, but this is for the
246            // sake of being explicit.
247            0 => unreachable!(),
248            1 => Level::Error,
249            2 => Level::Warn,
250            3 => Level::Info,
251            4 => Level::Debug,
252            _ => Level::Trace,
253        };
254
255        let adjusted_max_level_filter = level_into_level_filter(adjusted_max_level);
256        self.logger.max_level = adjusted_max_level_filter;
257
258        self
259    }
260}
261
262impl Default for LoggerBuilder<'static> {
263    fn default() -> Self {
264        let logger = Logger {
265            max_level: level_into_level_filter(Self::DEFAULT_LOG_LEVEL),
266            error: default_log_dest_for_level(Level::Error),
267            warn: default_log_dest_for_level(Level::Warn),
268            info: default_log_dest_for_level(Level::Info),
269            debug: default_log_dest_for_level(Level::Debug),
270            trace: default_log_dest_for_level(Level::Trace),
271        };
272
273        LoggerBuilder { logger }
274    }
275}
276
277/// A generic logger type that allows an arbitrary destination for each level.
278#[allow(unused)]
279pub struct Logger<'data> {
280    max_level: LevelFilter,
281    error: LogDestination<'data>,
282    warn: LogDestination<'data>,
283    info: LogDestination<'data>,
284    debug: LogDestination<'data>,
285    trace: LogDestination<'data>,
286}
287
288impl<'data> Logger<'data> {
289    fn as_logdestination_from_level(&self, level: Level) -> &LogDestination<'data> {
290        match level {
291            Level::Error => &self.error,
292            Level::Warn => &self.warn,
293            Level::Info => &self.info,
294            Level::Debug => &self.debug,
295            Level::Trace => &self.trace,
296        }
297    }
298
299    #[allow(unused)]
300    pub fn max_level_filter(&self) -> LevelFilter {
301        self.max_level
302    }
303}
304
305impl<'data> Log for Logger<'data> {
306    fn enabled(&self, metadata: &log::Metadata) -> bool {
307        metadata.level() <= self.max_level
308    }
309
310    fn log(&self, record: &log::Record) {
311        if !self.enabled(record.metadata()) {
312            return;
313        }
314
315        let record_level = record.level();
316        let level_oriented_log_destination = self.as_logdestination_from_level(record_level);
317
318        match level_oriented_log_destination {
319            LogDestination::Single(writer) => {
320                if let Ok(mut log_writer) = writer.lock() {
321                    let _ = writeln!(log_writer, "{}", record.args());
322                }
323            }
324            LogDestination::Multi(writers) => {
325                let lockable_writers = writers
326                    .iter()
327                    .flat_map(|lockable_writer| lockable_writer.lock());
328
329                for mut log_writer in lockable_writers {
330                    let _ = writeln!(log_writer, "{}", record.args());
331                }
332            }
333        }
334    }
335
336    fn flush(&self) {}
337}
338
339#[cfg(test)]
340mod tests {}