Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 154 additions & 0 deletions pkg/shp/logger/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package logger

import (
"fmt"
"io"
"os"

"k8s.io/cli-runtime/pkg/genericclioptions"
)

// Level represents the logging level
type Level int

const (
DebugLevel Level = iota
InfoLevel
WarnLevel
ErrorLevel
)

type Logger struct {
ioStreams *genericclioptions.IOStreams
level Level
verbose bool
quiet bool
color bool
}

// NewLogger creates a new logger instance
func NewLogger(ioStreams *genericclioptions.IOStreams) *Logger {
return &Logger{
ioStreams: ioStreams,
level: InfoLevel,
color: isTerminal(ioStreams.Out),
}
}

func (l *Logger) SetLevel(level Level) {
l.level = level
}

// SetVerbose enables verbose mode
func (l *Logger) SetVerbose(verbose bool) {
l.verbose = verbose
if verbose {
l.level = DebugLevel
}
}

// SetQuiet enables quiet mode
func (l *Logger) SetQuiet(quiet bool) {
l.quiet = quiet
}

func (l *Logger) Info(msg string, args ...interface{}) {
if l.level <= InfoLevel && !l.quiet {
l.logWithLevel("INFO", blueColor, msg, args...)
}
}

func (l *Logger) Warn(msg string, args ...interface{}) {
if l.level <= WarnLevel {
l.logWithLevel("WARNING", yellowColor, msg, args...)
}
}

func (l *Logger) Error(msg string, args ...interface{}) {
if l.level <= ErrorLevel {
l.logWithLevel("ERROR", redColor, msg, args...)
}
}

func (l *Logger) Debug(msg string, args ...interface{}) {
if l.level <= DebugLevel {
l.logWithLevel("DEBUG", grayColor, msg, args...)
}
}

func (l *Logger) Success(msg string, args ...interface{}) {
if !l.quiet {
l.logWithSymbol("✓", greenColor, msg, args...)
}
}

func (l *Logger) Progress(msg string, args ...interface{}) {
if !l.quiet {
l.logWithLevel("INFO", blueColor, msg, args...)
}
}

func (l *Logger) Output(msg string, args ...interface{}) {
if !l.quiet {
formattedMsg := fmt.Sprintf(msg, args...)
fmt.Fprintln(l.ioStreams.Out, formattedMsg)
}
}

func (l *Logger) Outputf(msg string, args ...interface{}) {
if !l.quiet {
fmt.Fprintf(l.ioStreams.Out, msg, args...)
}
}

// errors shouldn't be suppressed in quite mode
func (l *Logger) OutputErr(msg string, args ...interface{}) {
formattedMsg := fmt.Sprintf(msg, args...)
fmt.Fprintln(l.ioStreams.ErrOut, formattedMsg)
}

func (l *Logger) OutputErrf(msg string, args ...interface{}) {
fmt.Fprintf(l.ioStreams.ErrOut, msg, args...)
}

// logWithLevel logs with a log level prefix and color
func (l *Logger) logWithLevel(level string, color string, msg string, args ...interface{}) {
formattedMsg := fmt.Sprintf(msg, args...)
if l.color {
fmt.Fprintf(l.ioStreams.Out, "%s%s:%s %s\n", color, level, resetColor, formattedMsg)
} else {
fmt.Fprintf(l.ioStreams.Out, "%s: %s\n", level, formattedMsg)
}
}

// logWithSymbol logs with a symbol prefix and color
func (l *Logger) logWithSymbol(symbol string, color string, msg string, args ...interface{}) {
formattedMsg := fmt.Sprintf(msg, args...)
if l.color {
fmt.Fprintf(l.ioStreams.Out, "%s%s%s %s\n", color, symbol, resetColor, formattedMsg)
} else {
fmt.Fprintf(l.ioStreams.Out, "%s %s\n", symbol, formattedMsg)
}
}

const (
resetColor = "\033[0m"
redColor = "\033[31m"
greenColor = "\033[32m"
yellowColor = "\033[33m"
blueColor = "\033[34m"
grayColor = "\033[90m"
)

func isTerminal(w io.Writer) bool {
// Check if stdout is a terminal
if f, ok := w.(*os.File); ok {
return isTerminalFile(f)
}
return false
}

func isTerminalFile(f *os.File) bool {
stat, _ := f.Stat()
return (stat.Mode() & os.ModeCharDevice) != 0
}