fclones/
log.rs

1//! Logging and progress reporting.
2
3use std::sync::{Arc, Mutex, Weak};
4
5use console::style;
6use nom::lib::std::fmt::Display;
7
8use crate::progress::{ProgressBar, ProgressTracker};
9use chrono::Local;
10
11/// Determines the size of the task tracked by ProgressTracker.
12#[derive(Debug, Clone, Copy)]
13pub enum ProgressBarLength {
14    Items(u64),
15    Bytes(u64),
16    Unknown,
17}
18
19#[derive(Debug, Clone, Copy)]
20pub enum LogLevel {
21    Info,
22    Warn,
23    Error,
24}
25
26/// Common interface for logging diagnostics and progress.
27pub trait Log: Sync + Send {
28    /// Clears any previous progress bar or spinner and installs a new progress bar.
29    fn progress_bar(&self, msg: &str, len: ProgressBarLength) -> Arc<dyn ProgressTracker>;
30
31    /// Logs a message.
32    fn log(&self, level: LogLevel, msg: String);
33}
34
35/// Additional convenience methods for logging.
36pub trait LogExt {
37    /// Logs an info message.
38    fn info(&self, msg: impl Display);
39    /// Logs an warning.
40    fn warn(&self, msg: impl Display);
41    /// Logs an error.
42    fn err(&self, msg: impl Display);
43}
44
45/// Additional convenience methods for logging.
46impl<L: Log + ?Sized> LogExt for L {
47    /// Logs an info message.
48    fn info(&self, msg: impl Display) {
49        self.log(LogLevel::Info, msg.to_string())
50    }
51
52    /// Logs an warning.
53    fn warn(&self, msg: impl Display) {
54        self.log(LogLevel::Warn, msg.to_string())
55    }
56
57    /// Logs an error.
58    fn err(&self, msg: impl Display) {
59        self.log(LogLevel::Error, msg.to_string())
60    }
61}
62
63/// A logger that uses standard error stream to communicate with the user.
64pub struct StdLog {
65    program_name: String,
66    progress_bar: Mutex<Weak<ProgressBar>>,
67    pub log_stderr_to_stdout: bool,
68    pub no_progress: bool,
69}
70
71impl StdLog {
72    pub fn new() -> StdLog {
73        StdLog {
74            progress_bar: Mutex::new(Weak::default()),
75            program_name: std::env::current_exe()
76                .unwrap()
77                .file_name()
78                .unwrap()
79                .to_string_lossy()
80                .to_string(),
81            log_stderr_to_stdout: false,
82            no_progress: false,
83        }
84    }
85
86    /// Clears any previous progress bar or spinner and installs a new spinner.
87    pub fn spinner(&self, msg: &str) -> Arc<ProgressBar> {
88        if self.no_progress {
89            return Arc::new(ProgressBar::new_hidden());
90        }
91        self.progress_bar
92            .lock()
93            .unwrap()
94            .upgrade()
95            .iter()
96            .for_each(|pb| pb.finish_and_clear());
97        let result = Arc::new(ProgressBar::new_spinner(msg));
98        *self.progress_bar.lock().unwrap() = Arc::downgrade(&result);
99        result
100    }
101
102    /// Clears any previous progress bar or spinner and installs a new progress bar.
103    pub fn progress_bar(&self, msg: &str, len: u64) -> Arc<ProgressBar> {
104        if self.no_progress {
105            return Arc::new(ProgressBar::new_hidden());
106        }
107        let result = Arc::new(ProgressBar::new_progress_bar(msg, len));
108        *self.progress_bar.lock().unwrap() = Arc::downgrade(&result);
109        result
110    }
111
112    /// Creates a no-op progressbar that doesn't display itself.
113    pub fn hidden(&self) -> Arc<ProgressBar> {
114        Arc::new(ProgressBar::new_hidden())
115    }
116
117    /// Clears any previous progress bar or spinner and installs a new progress bar.
118    pub fn bytes_progress_bar(&self, msg: &str, len: u64) -> Arc<ProgressBar> {
119        if self.no_progress {
120            return Arc::new(ProgressBar::new_hidden());
121        }
122        self.progress_bar
123            .lock()
124            .unwrap()
125            .upgrade()
126            .iter()
127            .for_each(|pb| pb.finish_and_clear());
128        let result = Arc::new(ProgressBar::new_bytes_progress_bar(msg, len));
129        *self.progress_bar.lock().unwrap() = Arc::downgrade(&result);
130        result
131    }
132
133    /// Prints a message to stderr.
134    /// Does not interfere with progress bar.
135    fn eprintln<I: Display>(&self, msg: I) {
136        match self.progress_bar.lock().unwrap().upgrade() {
137            Some(pb) if pb.is_visible() => pb.eprintln(format!("{msg}")),
138            _ if self.log_stderr_to_stdout => println!("{msg}"),
139            _ => eprintln!("{msg}"),
140        }
141    }
142
143    const TIMESTAMP_FMT: &'static str = "[%Y-%m-%d %H:%M:%S.%3f]";
144}
145
146impl Log for StdLog {
147    fn progress_bar(&self, msg: &str, len: ProgressBarLength) -> Arc<dyn ProgressTracker> {
148        match len {
149            ProgressBarLength::Items(count) => self.progress_bar(msg, count),
150            ProgressBarLength::Bytes(count) => self.bytes_progress_bar(msg, count),
151            ProgressBarLength::Unknown => self.spinner(msg),
152        }
153    }
154
155    fn log(&self, level: LogLevel, msg: String) {
156        let timestamp = Local::now();
157        let level = match level {
158            LogLevel::Info => style(" info:").for_stderr().green(),
159            LogLevel::Warn => style("warn:").for_stderr().yellow(),
160            LogLevel::Error => style("error:").for_stderr().red(),
161        };
162        let msg = format!(
163            "{} {}: {} {}",
164            style(timestamp.format(Self::TIMESTAMP_FMT))
165                .for_stderr()
166                .dim()
167                .white(),
168            style(&self.program_name).for_stderr().yellow(),
169            level,
170            msg
171        );
172        self.eprintln(msg);
173    }
174}
175
176impl Default for StdLog {
177    fn default() -> Self {
178        StdLog::new()
179    }
180}