diff --git a/Cargo.toml b/Cargo.toml index 41dadcd..f4116db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ required-features = [ default = ["progress-tree", "progress-tree-log", "localtime"] progress-tree = ["dashmap", "parking_lot"] progress-tree-log = ["log"] +log-progress = ["log"] unit-bytes = ["bytesize"] unit-human = ["human_format"] unit-duration = ["compound_duration"] diff --git a/README.md b/README.md index cb10c4f..b68c020 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,9 @@ This crate comes with various cargo features to tailor it to your needs. * **progress-tree-log** _(default)_ * If logging in the `log` crate is initialized, a `log` will be used to output all messages provided to `tree::Item::message(…)` and friends. No actual progress is written. - * May interfere with `tui-renderer` or `line-renderer` + * May interfere with `tui-renderer` or `line-renderer`, or any renderer outputting to the console. +* **log-progress** + * A `Progress` implementation which logs messages and progress using the `log` crate * **local-time** _(default)_ * If set, timestamps in the message pane of the `tui-renderer` will be using the local time, not UTC * If set, timestamps of the log messages of the `line-renderer` will be using the local time, not UTC diff --git a/src/progress/log.rs b/src/progress/log.rs new file mode 100644 index 0000000..e4ce790 --- /dev/null +++ b/src/progress/log.rs @@ -0,0 +1,88 @@ +use crate::{messages::MessageLevel, Progress, Unit}; +use std::time::Duration; + +pub struct Log { + name: String, + max: Option, + unit: Option, + last_set: Option, + step: usize, + current_level: usize, + max_level: usize, +} + +const EMIT_LOG_EVERY_S: f32 = 0.5; + +impl Log { + pub fn new(name: impl Into, max_level: Option) -> Self { + Log { + name: name.into(), + current_level: 0, + max_level: max_level.unwrap_or(usize::MAX), + max: None, + step: 0, + unit: None, + last_set: None, + } + } +} + +impl Progress for Log { + type SubProgress = Log; + + fn add_child(&mut self, name: impl Into) -> Self::SubProgress { + Log { + name: format!("{}::{}", self.name, Into::::into(name)), + current_level: self.current_level + 1, + max_level: self.max_level, + step: 0, + max: None, + unit: None, + last_set: None, + } + } + + fn init(&mut self, max: Option, unit: Option) { + self.max = max; + self.unit = unit; + } + + fn set(&mut self, step: usize) { + self.step = step; + if self.current_level > self.max_level { + return; + } + let now = std::time::SystemTime::now(); + if self + .last_set + .map(|last| { + now.duration_since(last) + .unwrap_or_else(|_| Duration::default()) + .as_secs_f32() + }) + .unwrap_or_else(|| EMIT_LOG_EVERY_S * 2.0) + > EMIT_LOG_EVERY_S + { + self.last_set = Some(now); + match (self.max, self.unit) { + (Some(max), Some(unit)) => log::info!("{} → {} / {} {}", self.name, step, max, unit), + (None, Some(unit)) => log::info!("{} → {} {}", self.name, step, unit), + (Some(max), None) => log::info!("{} → {} / {}", self.name, step, max), + (None, None) => log::info!("{} → {}", self.name, step), + } + } + } + + fn inc_by(&mut self, step: usize) { + self.set(self.step + step) + } + + fn message(&mut self, level: MessageLevel, message: impl Into) { + let message: String = message.into(); + match level { + MessageLevel::Info => log::info!("ℹ{} → {}", self.name, message), + MessageLevel::Failure => log::error!("𐄂{} → {}", self.name, message), + MessageLevel::Success => log::info!("✓{} → {}", self.name, message), + } + } +} diff --git a/src/progress/mod.rs b/src/progress/mod.rs index 7be0b67..054b522 100644 --- a/src/progress/mod.rs +++ b/src/progress/mod.rs @@ -3,6 +3,10 @@ use std::time::SystemTime; pub mod key; mod utils; + +#[cfg(feature = "log-progress")] +pub mod log; + #[doc(inline)] pub use key::Key;