docker_image_pusher/logging/
mod.rs

1//! Enhanced logging and output control
2//!
3//! This module provides the [`Logger`] for controlling output verbosity, formatting logs,
4//! and tracking operation timing. It supports quiet, verbose, and structured output.
5
6use std::io::{self, Write};
7use std::time::{Duration, Instant};
8use std::collections::HashMap;
9
10/// Logger responsible for all user-visible output
11#[derive(Debug, Clone)]
12pub struct Logger {
13    pub verbose: bool,
14    pub quiet: bool,
15    pub start_time: Option<Instant>,
16}
17
18impl Logger {
19    pub fn new(verbose: bool) -> Self {
20        Self {
21            verbose,
22            quiet: false,
23            start_time: Some(Instant::now()),
24        }
25    }
26
27    pub fn new_quiet() -> Self {
28        Self {
29            verbose: false,
30            quiet: true,
31            start_time: Some(Instant::now()),
32        }
33    }
34
35    /// Main section heading
36    pub fn section(&self, title: &str) {
37        if !self.quiet {
38            println!("\n=== {} ===", title);
39        }
40    }
41
42    /// Sub-section heading
43    pub fn subsection(&self, title: &str) {
44        if !self.quiet {
45            println!("\n--- {} ---", title);
46        }
47    }
48
49    // Structured logging levels
50    pub fn trace(&self, message: &str) {
51        if self.verbose && !self.quiet {
52            println!("🔍 TRACE: {}", message);
53        }
54    }
55
56    pub fn debug(&self, message: &str) {
57        if self.verbose && !self.quiet {
58            println!("🐛 DEBUG: {}", message);
59        }
60    }
61
62    pub fn verbose(&self, message: &str) {
63        if self.verbose && !self.quiet {
64            println!("📝 {}", message);
65        }
66    }
67
68    /// Information message
69    pub fn info(&self, message: &str) {
70        if !self.quiet {
71            println!("ℹ️  {}", message);
72        }
73    }
74
75    /// Success message
76    pub fn success(&self, message: &str) {
77        if !self.quiet {
78            println!("✅ {}", message);
79        }
80    }
81
82    /// Warning message
83    pub fn warning(&self, message: &str) {
84        if !self.quiet {
85            println!("⚠️  WARNING: {}", message);
86        }
87    }
88
89    /// Error message
90    pub fn error(&self, message: &str) {
91        eprintln!("❌ ERROR: {}", message);
92    }
93
94    /// Step information
95    pub fn step(&self, message: &str) {
96        if !self.quiet {
97            println!("▶️  {}", message);
98        }
99    }
100
101    /// Progress information
102    pub fn progress(&self, message: &str) {
103        if !self.quiet {
104            print!("⏳ {}...", message);
105            let _ = io::stdout().flush();
106        }
107    }
108
109    /// Progress completion
110    pub fn progress_done(&self) {
111        if !self.quiet {
112            println!(" Done");
113        }
114    }
115
116    /// Detailed information (only shown in verbose mode)
117    pub fn detail(&self, message: &str) {
118        if self.verbose && !self.quiet {
119            println!("   {}", message);
120        }
121    }
122
123    // Summary method for displaying structured information
124    pub fn summary(&self, title: &str, items: &[String]) {
125        if !self.quiet {
126            println!("\n📋 {}", title);
127            println!("{}", "─".repeat(title.len() + 3));
128
129            for item in items {
130                println!("  • {}", item);
131            }
132
133            if items.is_empty() {
134                println!("  (No items to display)");
135            }
136        }
137    }
138
139    /// Key-value pair summary display
140    pub fn summary_kv(&self, title: &str, items: &[(&str, String)]) {
141        if !self.quiet {
142            self.subsection(title);
143            for (key, value) in items {
144                println!("  {}: {}", key, value);
145            }
146        }
147    }
148
149    // Structured list output
150    pub fn list(&self, title: &str, items: &[String]) {
151        if !self.quiet {
152            self.subsection(title);
153            for (i, item) in items.iter().enumerate() {
154                println!("  {}. {}", i + 1, item);
155            }
156
157            if items.is_empty() {
158                println!("  (No items to display)");
159            }
160        }
161    }
162
163    /// Format file size in human-readable units
164    pub fn format_size(&self, bytes: u64) -> String {
165        if bytes < 1024 {
166            format!("{} B", bytes)
167        } else if bytes < 1024 * 1024 {
168            format!("{:.1} KB", bytes as f64 / 1024.0)
169        } else if bytes < 1024 * 1024 * 1024 {
170            format!("{:.1} MB", bytes as f64 / (1024.0 * 1024.0))
171        } else {
172            format!("{:.1} GB", bytes as f64 / (1024.0 * 1024.0 * 1024.0))
173        }
174    }
175
176    /// Format duration in human-readable format
177    pub fn format_duration(&self, duration: Duration) -> String {
178        let secs = duration.as_secs();
179        if secs < 60 {
180            format!("{}s", secs)
181        } else if secs < 3600 {
182            format!("{}m{}s", secs / 60, secs % 60)
183        } else {
184            format!("{}h{}m{}s", secs / 3600, (secs % 3600) / 60, secs % 60)
185        }
186    }
187
188    /// Format transfer speed in human-readable format
189    pub fn format_speed(&self, bytes_per_sec: u64) -> String {
190        format!("{}/s", self.format_size(bytes_per_sec))
191    }
192
193    /// 显示实时进度状态
194    pub fn display_live_progress(&self, progress: &ProgressState) {
195        if self.quiet {
196            return;
197        }
198
199        // 清除当前行并重新定位光标
200        print!("\r\x1b[K");
201
202        let percentage = progress.get_progress_percentage();
203        let current_speed = progress.get_current_speed();
204        
205        // 创建进度条
206        let bar_width = 30;
207        let filled = ((percentage / 100.0) * bar_width as f64) as usize;
208        let empty = bar_width - filled;
209        let bar = format!("[{}{}]", "█".repeat(filled), "░".repeat(empty));
210
211        // 基础进度信息
212        print!("⏳ {} {:.1}% | ", bar, percentage);
213        print!("{}/{} tasks | ", progress.completed_tasks, progress.total_tasks);
214        print!("{} active | ", progress.active_tasks);
215        print!("{} | ", self.format_speed(current_speed));
216
217        // 并发信息
218        if progress.current_concurrent != progress.max_concurrent {
219            print!("🔧 {}/{} concurrent | ", progress.current_concurrent, progress.max_concurrent);
220        } else {
221            print!("{} concurrent | ", progress.current_concurrent);
222        }
223
224        // 剩余时间估计
225        if let Some(eta) = progress.get_estimated_time_remaining() {
226            print!("ETA: {}", self.format_duration(eta));
227        } else {
228            print!("ETA: calculating...");
229        }
230
231        let _ = io::stdout().flush();
232    }
233
234    /// Display detailed parallel task status
235    pub fn display_detailed_progress(&self, progress: &ProgressState) {
236        if self.quiet || !self.verbose {
237            return;
238        }
239
240        self.subsection("Detailed Progress Status");
241        
242        // Overall statistics
243        println!("📊 Overall Progress:");
244        println!("   • Total Tasks: {}", progress.total_tasks);
245        println!("   • Completed: {} ({:.1}%)", progress.completed_tasks, 
246                (progress.completed_tasks as f64 / progress.total_tasks as f64) * 100.0);
247        println!("   • Active: {}", progress.active_tasks);
248        println!("   • Data: {} / {} ({:.1}%)", 
249                self.format_size(progress.processed_bytes),
250                self.format_size(progress.total_bytes),
251                progress.get_progress_percentage());
252
253        // Concurrency status
254        println!("\n🔧 Concurrency Status:");
255        println!("   • Current: {}/{}", progress.current_concurrent, progress.max_concurrent);
256        
257        // Concurrency adjustment history
258        if !progress.concurrency_adjustments.is_empty() {
259            println!("   • Recent Adjustments:");
260            for adjustment in progress.concurrency_adjustments.iter().rev().take(3) {
261                let elapsed = adjustment.timestamp.elapsed();
262                println!("     - {}s ago: {} → {} ({})", 
263                        elapsed.as_secs(),
264                        adjustment.old_value,
265                        adjustment.new_value,
266                        adjustment.reason);
267            }
268        }
269
270        // Active task details
271        if !progress.active_task_details.is_empty() {
272            println!("\n🔄 Active Tasks:");
273            let mut tasks: Vec<_> = progress.active_task_details.values().collect();
274            tasks.sort_by_key(|t| t.layer_index);
275            
276            for task in tasks.iter().take(5) { // Show only first 5 tasks
277                let task_progress = if task.layer_size > 0 {
278                    (task.processed_bytes as f64 / task.layer_size as f64) * 100.0
279                } else {
280                    0.0
281                };
282                let elapsed = task.start_time.elapsed();
283                let speed = if elapsed.as_secs() > 0 {
284                    task.processed_bytes / elapsed.as_secs()
285                } else {
286                    0
287                };
288
289                println!("   • Layer {}: {} {:.1}% ({}) - {} - Priority: {}", 
290                        task.layer_index + 1,
291                        task.task_type,
292                        task_progress,
293                        self.format_size(task.layer_size),
294                        self.format_speed(speed),
295                        task.priority);
296            }
297            
298            if progress.active_task_details.len() > 5 {
299                println!("   • ... and {} more tasks", progress.active_task_details.len() - 5);
300            }
301        }
302
303        println!(); // Empty line separator
304    }
305
306    /// Display concurrency adjustment notification
307    pub fn notify_concurrency_adjustment(&self, old_value: usize, new_value: usize, reason: &str) {
308        if !self.quiet {
309            if new_value > old_value {
310                println!("🔼 Concurrency increased: {} → {} ({})", old_value, new_value, reason);
311            } else if new_value < old_value {
312                println!("🔽 Concurrency decreased: {} → {} ({})", old_value, new_value, reason);
313            }
314        }
315    }
316
317    /// Display task start notification
318    pub fn notify_task_start(&self, task_type: &str, layer_index: usize, size: u64, priority: u64) {
319        if self.verbose && !self.quiet {
320            println!("🚀 Starting {} task: Layer {} ({}) - Priority: {}", 
321                    task_type, layer_index + 1, self.format_size(size), priority);
322        }
323    }
324
325    /// Display task completion notification
326    pub fn notify_task_complete(&self, task_type: &str, layer_index: usize, duration: Duration, size: u64) {
327        if self.verbose && !self.quiet {
328            let speed = if duration.as_secs() > 0 {
329                size / duration.as_secs()
330            } else {
331                size
332            };
333            println!("✅ Completed {} task: Layer {} in {} ({})", 
334                    task_type, layer_index + 1, self.format_duration(duration), self.format_speed(speed));
335        }
336    }
337
338    /// Display simple progress information
339    pub fn display_simple_progress(&self, completed: usize, total: usize, message: &str) {
340        if self.quiet {
341            return;
342        }
343
344        // Calculate overall percentage
345        let percentage = if total > 0 {
346            (completed as f64 / total as f64) * 100.0
347        } else {
348            0.0
349        };
350
351        // Create simple progress bar
352        let bar_width = 20;
353        let filled = ((percentage / 100.0) * bar_width as f64) as usize;
354        let empty = bar_width - filled;
355        let bar = format!("[{}{}]", "█".repeat(filled), "░".repeat(empty));
356
357        // Display progress with message
358        println!("⏳ {} {:.1}% | {}/{} {} | {}", 
359                bar, percentage, completed, total, 
360                if total > 1 { "tasks" } else { "task" }, message);
361
362        let _ = io::stdout().flush();
363    }
364
365    // ...existing code...
366}
367
368/// 进度跟踪状态
369#[derive(Debug, Clone)]
370pub struct ProgressState {
371    pub total_tasks: usize,
372    pub completed_tasks: usize,
373    pub active_tasks: usize,
374    pub max_concurrent: usize,
375    pub current_concurrent: usize,
376    pub total_bytes: u64,
377    pub processed_bytes: u64,
378    pub start_time: Instant,
379    pub active_task_details: HashMap<String, TaskProgress>,
380    pub concurrency_adjustments: Vec<ConcurrencyAdjustment>,
381}
382
383/// 单个任务的进度信息
384#[derive(Debug, Clone)]
385pub struct TaskProgress {
386    pub task_id: String,
387    pub task_type: String, // "upload" or "download"
388    pub layer_index: usize,
389    pub layer_size: u64,
390    pub processed_bytes: u64,
391    pub start_time: Instant,
392    pub priority: u64,
393}
394
395/// 并发调整记录
396#[derive(Debug, Clone)]
397pub struct ConcurrencyAdjustment {
398    pub timestamp: Instant,
399    pub old_value: usize,
400    pub new_value: usize,
401    pub reason: String,
402}
403
404impl ProgressState {
405    pub fn new(total_tasks: usize, max_concurrent: usize, total_bytes: u64) -> Self {
406        Self {
407            total_tasks,
408            completed_tasks: 0,
409            active_tasks: 0,
410            max_concurrent,
411            current_concurrent: max_concurrent,
412            total_bytes,
413            processed_bytes: 0,
414            start_time: Instant::now(),
415            active_task_details: HashMap::new(),
416            concurrency_adjustments: Vec::new(),
417        }
418    }
419
420    pub fn add_task(&mut self, task: TaskProgress) {
421        self.active_task_details.insert(task.task_id.clone(), task);
422        self.active_tasks = self.active_task_details.len();
423    }
424
425    pub fn update_task_progress(&mut self, task_id: &str, processed_bytes: u64) {
426        if let Some(task) = self.active_task_details.get_mut(task_id) {
427            let old_processed = task.processed_bytes;
428            task.processed_bytes = processed_bytes;
429            self.processed_bytes += processed_bytes - old_processed;
430        }
431    }
432
433    pub fn complete_task(&mut self, task_id: &str) {
434        if let Some(task) = self.active_task_details.remove(task_id) {
435            self.completed_tasks += 1;
436            self.active_tasks = self.active_task_details.len();
437            // 确保已处理字节数包含完成的任务
438            self.processed_bytes += task.layer_size - task.processed_bytes;
439        }
440    }
441
442    pub fn adjust_concurrency(&mut self, new_concurrent: usize, reason: String) {
443        let adjustment = ConcurrencyAdjustment {
444            timestamp: Instant::now(),
445            old_value: self.current_concurrent,
446            new_value: new_concurrent,
447            reason,
448        };
449        self.concurrency_adjustments.push(adjustment);
450        self.current_concurrent = new_concurrent;
451    }
452
453    pub fn get_progress_percentage(&self) -> f64 {
454        if self.total_bytes == 0 {
455            (self.completed_tasks as f64 / self.total_tasks as f64) * 100.0
456        } else {
457            (self.processed_bytes as f64 / self.total_bytes as f64) * 100.0
458        }
459    }
460
461    pub fn get_estimated_time_remaining(&self) -> Option<Duration> {
462        let elapsed = self.start_time.elapsed();
463        if self.processed_bytes == 0 || elapsed.as_secs() == 0 {
464            return None;
465        }
466
467        let rate = self.processed_bytes as f64 / elapsed.as_secs_f64();
468        let remaining_bytes = self.total_bytes - self.processed_bytes;
469        let estimated_seconds = remaining_bytes as f64 / rate;
470        
471        Some(Duration::from_secs_f64(estimated_seconds))
472    }
473
474    pub fn get_current_speed(&self) -> u64 {
475        let elapsed = self.start_time.elapsed();
476        if elapsed.as_secs() == 0 {
477            0
478        } else {
479            self.processed_bytes / elapsed.as_secs()
480        }
481    }
482}