feat: initial goget release — modern IPv6-first download utility
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
//go:build linux || freebsd
|
||||
// +build linux freebsd
|
||||
|
||||
// Package log provides a structured logger with text and JSON output modes.
|
||||
// It replaces ad-hoc fmt.Fprintf and cli.Print* calls across the codebase.
|
||||
package log
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Level represents the severity of a log message.
|
||||
type Level int
|
||||
|
||||
const (
|
||||
ErrorLevel Level = iota
|
||||
WarnLevel
|
||||
InfoLevel
|
||||
VerboseLevel
|
||||
DebugLevel
|
||||
)
|
||||
|
||||
func (l Level) String() string {
|
||||
switch l {
|
||||
case ErrorLevel:
|
||||
return "error"
|
||||
case WarnLevel:
|
||||
return "warn"
|
||||
case InfoLevel:
|
||||
return "info"
|
||||
case VerboseLevel:
|
||||
return "verbose"
|
||||
case DebugLevel:
|
||||
return "debug"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// Logger writes structured log messages.
|
||||
type Logger struct {
|
||||
mu sync.Mutex
|
||||
w io.Writer
|
||||
level Level
|
||||
color bool
|
||||
json bool
|
||||
}
|
||||
|
||||
// Option configures a Logger.
|
||||
type Option func(*Logger)
|
||||
|
||||
// WithLevel sets the minimum log level. Messages below this level are
|
||||
// silently discarded.
|
||||
func WithLevel(l Level) Option {
|
||||
return func(log *Logger) { log.level = l }
|
||||
}
|
||||
|
||||
// WithColor enables or disables ANSI color output in text mode.
|
||||
func WithColor(c bool) Option {
|
||||
return func(log *Logger) { log.color = c }
|
||||
}
|
||||
|
||||
// WithJSON enables JSON output mode. When enabled, each message is written as
|
||||
// a JSON object on a single line.
|
||||
func WithJSON(j bool) Option {
|
||||
return func(log *Logger) { log.json = j }
|
||||
}
|
||||
|
||||
// New creates a new Logger that writes to w.
|
||||
// Defaults: InfoLevel, color enabled, text mode.
|
||||
func New(w io.Writer, opts ...Option) *Logger {
|
||||
l := &Logger{w: w, level: InfoLevel, color: true}
|
||||
for _, o := range opts {
|
||||
o(l)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// Error logs a message at error level.
|
||||
func (l *Logger) Error(msg string) { l.log(ErrorLevel, msg, nil) }
|
||||
|
||||
// Errorf logs a formatted message at error level.
|
||||
func (l *Logger) Errorf(format string, args ...interface{}) {
|
||||
l.log(ErrorLevel, fmt.Sprintf(format, args...), nil)
|
||||
}
|
||||
|
||||
// Warn logs a message at warning level.
|
||||
func (l *Logger) Warn(msg string) { l.log(WarnLevel, msg, nil) }
|
||||
|
||||
// Warnf logs a formatted message at warning level.
|
||||
func (l *Logger) Warnf(format string, args ...interface{}) {
|
||||
l.log(WarnLevel, fmt.Sprintf(format, args...), nil)
|
||||
}
|
||||
|
||||
// Info logs a message at info level.
|
||||
func (l *Logger) Info(msg string) { l.log(InfoLevel, msg, nil) }
|
||||
|
||||
// Infof logs a formatted message at info level.
|
||||
func (l *Logger) Infof(format string, args ...interface{}) {
|
||||
l.log(InfoLevel, fmt.Sprintf(format, args...), nil)
|
||||
}
|
||||
|
||||
// Verbose logs a message at verbose level. Only shown when the logger is
|
||||
// configured at VerboseLevel or below.
|
||||
func (l *Logger) Verbose(msg string) { l.log(VerboseLevel, msg, nil) }
|
||||
|
||||
// Verbosef logs a formatted message at verbose level.
|
||||
func (l *Logger) Verbosef(format string, args ...interface{}) {
|
||||
l.log(VerboseLevel, fmt.Sprintf(format, args...), nil)
|
||||
}
|
||||
|
||||
// Debug logs a message at debug level. Only shown when the logger is
|
||||
// configured at DebugLevel.
|
||||
func (l *Logger) Debug(msg string) { l.log(DebugLevel, msg, nil) }
|
||||
|
||||
// Debugf logs a formatted message at debug level.
|
||||
func (l *Logger) Debugf(format string, args ...interface{}) {
|
||||
l.log(DebugLevel, fmt.Sprintf(format, args...), nil)
|
||||
}
|
||||
|
||||
// Success logs a success message at info level with a checkmark prefix.
|
||||
// In JSON mode, it adds a "status":"success" field.
|
||||
func (l *Logger) Success(msg string) {
|
||||
l.log(InfoLevel, msg, map[string]interface{}{"status": "success"})
|
||||
}
|
||||
|
||||
// Successf logs a formatted success message.
|
||||
func (l *Logger) Successf(format string, args ...interface{}) {
|
||||
l.Success(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (l *Logger) log(level Level, msg string, fields map[string]interface{}) {
|
||||
if level > l.level {
|
||||
return
|
||||
}
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
if l.json {
|
||||
l.logJSON(level, msg, fields)
|
||||
} else {
|
||||
l.logText(level, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) logText(level Level, msg string) {
|
||||
var prefix, suffix string
|
||||
if l.color {
|
||||
suffix = colorReset
|
||||
switch level {
|
||||
case ErrorLevel:
|
||||
prefix = colorRed + colorBold
|
||||
case WarnLevel:
|
||||
prefix = colorYellow + colorBold
|
||||
case InfoLevel:
|
||||
prefix = colorCyan + colorBold
|
||||
}
|
||||
}
|
||||
|
||||
icon := levelIcon(level)
|
||||
if prefix != "" {
|
||||
fmt.Fprintf(l.w, "%s%s%s %s\n", prefix, icon, suffix, msg)
|
||||
} else {
|
||||
fmt.Fprintf(l.w, "%s %s\n", icon, msg)
|
||||
}
|
||||
}
|
||||
|
||||
type jsonEntry struct {
|
||||
Level string `json:"level"`
|
||||
Msg string `json:"msg"`
|
||||
Fields map[string]interface{} `json:"fields,omitempty"`
|
||||
}
|
||||
|
||||
func (l *Logger) logJSON(level Level, msg string, fields map[string]interface{}) {
|
||||
entry := jsonEntry{
|
||||
Level: level.String(),
|
||||
Msg: msg,
|
||||
}
|
||||
if fields != nil {
|
||||
entry.Fields = fields
|
||||
}
|
||||
// Best-effort encoding; errors are silently dropped to avoid log loops.
|
||||
data, _ := json.Marshal(entry)
|
||||
fmt.Fprintln(l.w, string(data))
|
||||
}
|
||||
|
||||
func levelIcon(level Level) string {
|
||||
switch level {
|
||||
case ErrorLevel:
|
||||
return "✗ Error:"
|
||||
case WarnLevel:
|
||||
return "⚠ Warning:"
|
||||
case InfoLevel, VerboseLevel, DebugLevel:
|
||||
return "ℹ"
|
||||
default:
|
||||
return "•"
|
||||
}
|
||||
}
|
||||
|
||||
// ANSI escape codes.
|
||||
const (
|
||||
colorReset = "\033[0m"
|
||||
colorRed = "\033[31m"
|
||||
colorYellow = "\033[33m"
|
||||
colorCyan = "\033[36m"
|
||||
colorBold = "\033[1m"
|
||||
)
|
||||
|
||||
// DefaultLogger is the global logger used by package-level convenience
|
||||
// functions. It writes to stderr at InfoLevel in text mode.
|
||||
var DefaultLogger = New(os.Stderr)
|
||||
|
||||
// Package-level convenience functions that delegate to DefaultLogger.
|
||||
func Error(msg string) { DefaultLogger.Error(msg) }
|
||||
func Errorf(f string, a ...interface{}) { DefaultLogger.Errorf(f, a...) }
|
||||
func Warn(msg string) { DefaultLogger.Warn(msg) }
|
||||
func Warnf(f string, a ...interface{}) { DefaultLogger.Warnf(f, a...) }
|
||||
func Info(msg string) { DefaultLogger.Info(msg) }
|
||||
func Infof(f string, a ...interface{}) { DefaultLogger.Infof(f, a...) }
|
||||
func Verbose(msg string) { DefaultLogger.Verbose(msg) }
|
||||
func Verbosef(f string, a ...interface{}) { DefaultLogger.Verbosef(f, a...) }
|
||||
func Debug(msg string) { DefaultLogger.Debug(msg) }
|
||||
func Debugf(f string, a ...interface{}) { DefaultLogger.Debugf(f, a...) }
|
||||
func Success(msg string) { DefaultLogger.Success(msg) }
|
||||
func Successf(f string, a ...interface{}) { DefaultLogger.Successf(f, a...) }
|
||||
Reference in New Issue
Block a user