From 6a24ee39beb1b07e1ce07a03dfc59f878285a66b Mon Sep 17 00:00:00 2001 From: Alexander Renz Date: Wed, 14 Jan 2026 16:13:00 +0100 Subject: [PATCH] v3.42.32: Add fatih/color for cross-platform terminal colors - Windows-compatible colors via native console API - Color helper functions: Success(), Error(), Warning(), Info() - Text styling: Header(), Dim(), Bold(), Green(), Red(), Yellow(), Cyan() - Logger CleanFormatter uses fatih/color instead of raw ANSI - All progress indicators use colored [OK]/[FAIL] status - Automatic color detection for non-TTY environments --- CHANGELOG.md | 17 +++++ bin/README.md | 6 +- go.mod | 2 + go.sum | 6 ++ internal/logger/colors.go | 118 ++++++++++++++++++++++++++++++++++ internal/logger/logger.go | 40 ++++++++---- internal/progress/progress.go | 50 ++++++++++---- main.go | 2 +- 8 files changed, 212 insertions(+), 29 deletions(-) create mode 100644 internal/logger/colors.go diff --git a/CHANGELOG.md b/CHANGELOG.md index aaf44b9..72122cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ All notable changes to dbbackup will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.42.32] - 2026-01-14 "Cross-Platform Colors" + +### Added - fatih/color for Cross-Platform Terminal Colors +- **Windows-compatible colors** - Native Windows console API support +- **Color helper functions** in `logger` package: + - `Success()`, `Error()`, `Warning()`, `Info()` - Status messages with icons + - `Header()`, `Dim()`, `Bold()` - Text styling + - `Green()`, `Red()`, `Yellow()`, `Cyan()` - Colored text + - `StatusLine()`, `TableRow()` - Formatted output + - `DisableColors()`, `EnableColors()` - Runtime control +- **Consistent color scheme** across all log levels + +### Changed +- Logger `CleanFormatter` now uses fatih/color instead of raw ANSI codes +- All progress indicators use fatih/color for `[OK]`/`[FAIL]` status +- Automatic color detection (disabled for non-TTY) + ## [3.42.31] - 2026-01-14 "Visual Progress Bars" ### Added - schollz/progressbar for Enhanced Progress Display diff --git a/bin/README.md b/bin/README.md index 1c29f37..673b8c2 100644 --- a/bin/README.md +++ b/bin/README.md @@ -3,9 +3,9 @@ This directory contains pre-compiled binaries for the DB Backup Tool across multiple platforms and architectures. ## Build Information -- **Version**: 3.42.30 -- **Build Time**: 2026-01-14_14:59:20_UTC -- **Git Commit**: 7b4ab76 +- **Version**: 3.42.31 +- **Build Time**: 2026-01-14_15:07:12_UTC +- **Git Commit**: dc6dfd8 ## Recent Updates (v1.1.0) - ✅ Fixed TUI progress display with line-by-line output diff --git a/go.mod b/go.mod index b2af32d..d916893 100755 --- a/go.mod +++ b/go.mod @@ -66,6 +66,7 @@ require ( github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect + github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-jose/go-jose/v4 v4.1.2 // indirect github.com/go-logr/logr v1.4.3 // indirect @@ -83,6 +84,7 @@ require ( github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect diff --git a/go.sum b/go.sum index 170764a..a3e6c8b 100755 --- a/go.sum +++ b/go.sum @@ -119,6 +119,8 @@ github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfU github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI= @@ -169,6 +171,9 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= @@ -259,6 +264,7 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/logger/colors.go b/internal/logger/colors.go new file mode 100644 index 0000000..964a2c5 --- /dev/null +++ b/internal/logger/colors.go @@ -0,0 +1,118 @@ +package logger + +import ( + "fmt" + "os" + + "github.com/fatih/color" +) + +// CLI output helpers using fatih/color for cross-platform support + +// Success prints a success message with green checkmark +func Success(format string, args ...interface{}) { + msg := fmt.Sprintf(format, args...) + SuccessColor.Fprint(os.Stdout, "✓ ") + fmt.Println(msg) +} + +// Error prints an error message with red X +func Error(format string, args ...interface{}) { + msg := fmt.Sprintf(format, args...) + ErrorColor.Fprint(os.Stderr, "✗ ") + fmt.Fprintln(os.Stderr, msg) +} + +// Warning prints a warning message with yellow exclamation +func Warning(format string, args ...interface{}) { + msg := fmt.Sprintf(format, args...) + WarnColor.Fprint(os.Stdout, "⚠ ") + fmt.Println(msg) +} + +// Info prints an info message with blue arrow +func Info(format string, args ...interface{}) { + msg := fmt.Sprintf(format, args...) + InfoColor.Fprint(os.Stdout, "→ ") + fmt.Println(msg) +} + +// Header prints a bold header +func Header(format string, args ...interface{}) { + msg := fmt.Sprintf(format, args...) + HighlightColor.Println(msg) +} + +// Dim prints dimmed/secondary text +func Dim(format string, args ...interface{}) { + msg := fmt.Sprintf(format, args...) + DimColor.Println(msg) +} + +// Bold returns bold text +func Bold(text string) string { + return color.New(color.Bold).Sprint(text) +} + +// Green returns green text +func Green(text string) string { + return SuccessColor.Sprint(text) +} + +// Red returns red text +func Red(text string) string { + return ErrorColor.Sprint(text) +} + +// Yellow returns yellow text +func Yellow(text string) string { + return WarnColor.Sprint(text) +} + +// Cyan returns cyan text +func Cyan(text string) string { + return InfoColor.Sprint(text) +} + +// StatusLine prints a key-value status line +func StatusLine(key, value string) { + DimColor.Printf(" %s: ", key) + fmt.Println(value) +} + +// ProgressStatus prints operation status with timing +func ProgressStatus(operation string, status string, isSuccess bool) { + if isSuccess { + SuccessColor.Print("[OK] ") + } else { + ErrorColor.Print("[FAIL] ") + } + fmt.Printf("%s: %s\n", operation, status) +} + +// Table prints a simple formatted table row +func TableRow(cols ...string) { + for i, col := range cols { + if i == 0 { + InfoColor.Printf("%-20s", col) + } else { + fmt.Printf("%-15s", col) + } + } + fmt.Println() +} + +// DisableColors disables all color output (for non-TTY or --no-color flag) +func DisableColors() { + color.NoColor = true +} + +// EnableColors enables color output +func EnableColors() { + color.NoColor = false +} + +// IsColorEnabled returns whether colors are enabled +func IsColorEnabled() bool { + return !color.NoColor +} diff --git a/internal/logger/logger.go b/internal/logger/logger.go index e5b4e05..fed5b03 100755 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -7,9 +7,29 @@ import ( "strings" "time" + "github.com/fatih/color" "github.com/sirupsen/logrus" ) +// Color printers for consistent output across the application +var ( + // Status colors + SuccessColor = color.New(color.FgGreen, color.Bold) + ErrorColor = color.New(color.FgRed, color.Bold) + WarnColor = color.New(color.FgYellow, color.Bold) + InfoColor = color.New(color.FgCyan) + DebugColor = color.New(color.FgWhite) + + // Highlight colors + HighlightColor = color.New(color.FgMagenta, color.Bold) + DimColor = color.New(color.FgHiBlack) + + // Data colors + NumberColor = color.New(color.FgYellow) + PathColor = color.New(color.FgBlue, color.Underline) + TimeColor = color.New(color.FgCyan) +) + // Logger defines the interface for logging type Logger interface { Debug(msg string, args ...any) @@ -226,34 +246,32 @@ type CleanFormatter struct{} func (f *CleanFormatter) Format(entry *logrus.Entry) ([]byte, error) { timestamp := entry.Time.Format("2006-01-02T15:04:05") - // Color codes for different log levels - var levelColor, levelText string + // Get level color and text using fatih/color + var levelPrinter *color.Color + var levelText string switch entry.Level { case logrus.DebugLevel: - levelColor = "\033[36m" // Cyan + levelPrinter = DebugColor levelText = "DEBUG" case logrus.InfoLevel: - levelColor = "\033[32m" // Green + levelPrinter = SuccessColor levelText = "INFO " case logrus.WarnLevel: - levelColor = "\033[33m" // Yellow + levelPrinter = WarnColor levelText = "WARN " case logrus.ErrorLevel: - levelColor = "\033[31m" // Red + levelPrinter = ErrorColor levelText = "ERROR" default: - levelColor = "\033[0m" // Reset + levelPrinter = InfoColor levelText = "INFO " } - resetColor := "\033[0m" // Build the message with perfectly aligned columns var output strings.Builder // Column 1: Level (with color, fixed width 5 chars) - output.WriteString(levelColor) - output.WriteString(levelText) - output.WriteString(resetColor) + output.WriteString(levelPrinter.Sprint(levelText)) output.WriteString(" ") // Column 2: Timestamp (fixed format) diff --git a/internal/progress/progress.go b/internal/progress/progress.go index 387a285..c83094f 100755 --- a/internal/progress/progress.go +++ b/internal/progress/progress.go @@ -7,9 +7,17 @@ import ( "strings" "time" + "github.com/fatih/color" "github.com/schollz/progressbar/v3" ) +// Color printers for progress indicators +var ( + okColor = color.New(color.FgGreen, color.Bold) + failColor = color.New(color.FgRed, color.Bold) + warnColor = color.New(color.FgYellow, color.Bold) +) + // Indicator represents a progress indicator interface type Indicator interface { Start(message string) @@ -94,13 +102,15 @@ func (s *Spinner) Update(message string) { // Complete stops the spinner with a success message func (s *Spinner) Complete(message string) { s.Stop() - fmt.Fprintf(s.writer, "\n[OK] %s\n", message) + okColor.Fprint(s.writer, "[OK] ") + fmt.Fprintln(s.writer, message) } // Fail stops the spinner with a failure message func (s *Spinner) Fail(message string) { s.Stop() - fmt.Fprintf(s.writer, "\n[FAIL] %s\n", message) + failColor.Fprint(s.writer, "[FAIL] ") + fmt.Fprintln(s.writer, message) } // Stop stops the spinner @@ -169,13 +179,15 @@ func (d *Dots) Update(message string) { // Complete stops the dots with a success message func (d *Dots) Complete(message string) { d.Stop() - fmt.Fprintf(d.writer, " [OK] %s\n", message) + okColor.Fprint(d.writer, " [OK] ") + fmt.Fprintln(d.writer, message) } // Fail stops the dots with a failure message func (d *Dots) Fail(message string) { d.Stop() - fmt.Fprintf(d.writer, " [FAIL] %s\n", message) + failColor.Fprint(d.writer, " [FAIL] ") + fmt.Fprintln(d.writer, message) } // Stop stops the dots indicator @@ -241,14 +253,16 @@ func (p *ProgressBar) Complete(message string) { p.current = p.total p.message = message p.render() - fmt.Fprintf(p.writer, " [OK] %s\n", message) + okColor.Fprint(p.writer, " [OK] ") + fmt.Fprintln(p.writer, message) p.Stop() } // Fail stops the progress bar with failure func (p *ProgressBar) Fail(message string) { p.render() - fmt.Fprintf(p.writer, " [FAIL] %s\n", message) + failColor.Fprint(p.writer, " [FAIL] ") + fmt.Fprintln(p.writer, message) p.Stop() } @@ -300,12 +314,14 @@ func (s *Static) Update(message string) { // Complete shows completion message func (s *Static) Complete(message string) { - fmt.Fprintf(s.writer, " [OK] %s\n", message) + okColor.Fprint(s.writer, " [OK] ") + fmt.Fprintln(s.writer, message) } // Fail shows failure message func (s *Static) Fail(message string) { - fmt.Fprintf(s.writer, " [FAIL] %s\n", message) + failColor.Fprint(s.writer, " [FAIL] ") + fmt.Fprintln(s.writer, message) } // Stop does nothing for static indicator @@ -382,12 +398,14 @@ func (l *LineByLine) SetEstimator(estimator *ETAEstimator) { // Complete shows completion message func (l *LineByLine) Complete(message string) { - fmt.Fprintf(l.writer, "[OK] %s\n\n", message) + okColor.Fprint(l.writer, "[OK] ") + fmt.Fprintf(l.writer, "%s\n\n", message) } // Fail shows failure message func (l *LineByLine) Fail(message string) { - fmt.Fprintf(l.writer, "[FAIL] %s\n\n", message) + failColor.Fprint(l.writer, "[FAIL] ") + fmt.Fprintf(l.writer, "%s\n\n", message) } // Stop does nothing for line-by-line (no cleanup needed) @@ -410,13 +428,15 @@ func (l *Light) Update(message string) { func (l *Light) Complete(message string) { if !l.silent { - fmt.Fprintf(l.writer, "[OK] %s\n", message) + okColor.Fprint(l.writer, "[OK] ") + fmt.Fprintln(l.writer, message) } } func (l *Light) Fail(message string) { if !l.silent { - fmt.Fprintf(l.writer, "[FAIL] %s\n", message) + failColor.Fprint(l.writer, "[FAIL] ") + fmt.Fprintln(l.writer, message) } } @@ -594,13 +614,15 @@ func (s *SchollzBar) ChangeMax64(max int64) { // Complete finishes with success (Indicator interface) func (s *SchollzBar) Complete(message string) { _ = s.bar.Finish() - fmt.Printf("\n[green][OK][reset] %s\n", message) + okColor.Print("[OK] ") + fmt.Println(message) } // Fail finishes with failure (Indicator interface) func (s *SchollzBar) Fail(message string) { _ = s.bar.Clear() - fmt.Printf("\n[red][FAIL][reset] %s\n", message) + failColor.Print("[FAIL] ") + fmt.Println(message) } // Stop stops the progress bar (Indicator interface) diff --git a/main.go b/main.go index 69ec90a..90cf1d0 100755 --- a/main.go +++ b/main.go @@ -16,7 +16,7 @@ import ( // Build information (set by ldflags) var ( - version = "3.42.31" + version = "3.42.32" buildTime = "unknown" gitCommit = "unknown" )