Skip to main content

qubit_progress/reporter/impls/
logger_progress_reporter.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10use crate::{
11    model::ProgressEvent,
12    reporter::ProgressReporter,
13};
14
15/// Progress reporter that emits progress events through the `log` crate.
16///
17/// # Examples
18///
19/// ```
20/// use log::Level;
21/// use qubit_progress::LoggerProgressReporter;
22///
23/// let reporter = LoggerProgressReporter::new("my_app::progress")
24///     .with_level(Level::Debug);
25///
26/// assert_eq!(reporter.target(), "my_app::progress");
27/// assert_eq!(reporter.level(), Level::Debug);
28/// ```
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct LoggerProgressReporter {
31    /// Log target used for emitted records.
32    target: String,
33    /// Log level used for emitted records.
34    level: log::Level,
35}
36
37impl LoggerProgressReporter {
38    /// Creates a logger reporter at [`log::Level::Info`].
39    ///
40    /// # Parameters
41    ///
42    /// * `target` - Log target used for emitted records.
43    ///
44    /// # Returns
45    ///
46    /// A logger-backed progress reporter.
47    #[inline]
48    pub fn new(target: &str) -> Self {
49        Self {
50            target: target.to_owned(),
51            level: log::Level::Info,
52        }
53    }
54
55    /// Returns a copy configured with a log level.
56    ///
57    /// # Parameters
58    ///
59    /// * `level` - Log level used for emitted records.
60    ///
61    /// # Returns
62    ///
63    /// This reporter configured with `level`.
64    #[inline]
65    pub const fn with_level(mut self, level: log::Level) -> Self {
66        self.level = level;
67        self
68    }
69
70    /// Returns the log target.
71    ///
72    /// # Returns
73    ///
74    /// The target used for emitted log records.
75    #[inline]
76    pub fn target(&self) -> &str {
77        self.target.as_str()
78    }
79
80    /// Returns the log level.
81    ///
82    /// # Returns
83    ///
84    /// The level used for emitted log records.
85    #[inline]
86    pub const fn level(&self) -> log::Level {
87        self.level
88    }
89
90    /// Emits one message through the `log` crate.
91    ///
92    /// # Parameters
93    ///
94    /// * `message` - Preformatted progress message.
95    #[inline]
96    fn log_line(&self, message: &str) {
97        log::log!(target: self.target.as_str(), self.level, "{message}");
98    }
99}
100
101impl Default for LoggerProgressReporter {
102    /// Creates a logger reporter with the default target.
103    ///
104    /// # Returns
105    ///
106    /// A logger-backed reporter at [`log::Level::Info`].
107    #[inline]
108    fn default() -> Self {
109        Self::new("qubit_progress")
110    }
111}
112
113impl ProgressReporter for LoggerProgressReporter {
114    /// Logs one progress event.
115    ///
116    /// # Parameters
117    ///
118    /// * `event` - Progress event to log.
119    #[inline]
120    fn report(&self, event: &ProgressEvent) {
121        self.log_line(&format_event(event));
122    }
123}
124
125/// Formats one progress event for log output.
126///
127/// # Parameters
128///
129/// * `event` - Event to format.
130///
131/// # Returns
132///
133/// A single-line log message.
134fn format_event(event: &ProgressEvent) -> String {
135    let counters = event.counters();
136    let progress = match counters.progress_percent() {
137        Some(percent) => format!("{percent:.2}%"),
138        None => "unknown".to_owned(),
139    };
140    match event.stage() {
141        Some(stage) => format!(
142            "progress phase={}, stage={}, completed={}, total={:?}, active={}, failed={}, progress={}",
143            event.phase(),
144            stage.name(),
145            counters.completed_count(),
146            counters.total_count(),
147            counters.active_count(),
148            counters.failed_count(),
149            progress,
150        ),
151        None => format!(
152            "progress phase={}, completed={}, total={:?}, active={}, failed={}, progress={}",
153            event.phase(),
154            counters.completed_count(),
155            counters.total_count(),
156            counters.active_count(),
157            counters.failed_count(),
158            progress,
159        ),
160    }
161}