package log import ( "fmt" "io" "os" "runtime" "sync" "github.com/apex/log" acli "github.com/apex/log/handlers/cli" "github.com/davecgh/go-spew/spew" "github.com/pterm/pterm" ) // Level represents log severity levels. // Lower values are more verbose. type Level int const ( // DebugLevel is for low-level tracing and structure inspection DebugLevel Level = iota // VerboseLevel is for detailed operational info (file listings, etc) VerboseLevel // InfoLevel is for operational summaries (default) InfoLevel // WarnLevel is for warnings WarnLevel // ErrorLevel is for errors ErrorLevel // FatalLevel is for fatal errors FatalLevel ) func (l Level) String() string { switch l { case DebugLevel: return "debug" case VerboseLevel: return "verbose" case InfoLevel: return "info" case WarnLevel: return "warn" case ErrorLevel: return "error" case FatalLevel: return "fatal" default: return "unknown" } } var ( // mu protects the output writers and level mu sync.RWMutex // stdout is the writer for progress output stdout io.Writer = os.Stdout // stderr is the writer for log output stderr io.Writer = os.Stderr // currentLevel is our log level (includes Verbose) currentLevel Level = InfoLevel ) // SetOutput configures the output writers for the log package. // stdout is used for progress output, stderr is used for log messages. func SetOutput(out, err io.Writer) { mu.Lock() defer mu.Unlock() stdout = out stderr = err pterm.SetDefaultOutput(out) } // GetStdout returns the configured stdout writer. func GetStdout() io.Writer { mu.RLock() defer mu.RUnlock() return stdout } // GetStderr returns the configured stderr writer. func GetStderr() io.Writer { mu.RLock() defer mu.RUnlock() return stderr } // DisableStyling turns off colors and styling for terminal output. func DisableStyling() { pterm.DisableColor() pterm.DisableStyling() pterm.Debug.Prefix.Text = "" pterm.Info.Prefix.Text = "" pterm.Success.Prefix.Text = "" pterm.Warning.Prefix.Text = "" pterm.Error.Prefix.Text = "" pterm.Fatal.Prefix.Text = "" } // Init initializes the logger with the CLI handler and default log level. func Init() { mu.RLock() w := stderr mu.RUnlock() log.SetHandler(acli.New(w)) log.SetLevel(log.DebugLevel) // Let apex/log pass everything; we filter ourselves } // isEnabled returns true if messages at the given level should be logged. func isEnabled(l Level) bool { mu.RLock() defer mu.RUnlock() return l >= currentLevel } // Fatalf logs a formatted message at fatal level. func Fatalf(format string, args ...interface{}) { if isEnabled(FatalLevel) { log.Fatalf(format, args...) } } // Fatal logs a message at fatal level. func Fatal(arg string) { if isEnabled(FatalLevel) { log.Fatal(arg) } } // Errorf logs a formatted message at error level. func Errorf(format string, args ...interface{}) { if isEnabled(ErrorLevel) { log.Errorf(format, args...) } } // Error logs a message at error level. func Error(arg string) { if isEnabled(ErrorLevel) { log.Error(arg) } } // Warnf logs a formatted message at warn level. func Warnf(format string, args ...interface{}) { if isEnabled(WarnLevel) { log.Warnf(format, args...) } } // Warn logs a message at warn level. func Warn(arg string) { if isEnabled(WarnLevel) { log.Warn(arg) } } // Infof logs a formatted message at info level. func Infof(format string, args ...interface{}) { if isEnabled(InfoLevel) { log.Infof(format, args...) } } // Info logs a message at info level. func Info(arg string) { if isEnabled(InfoLevel) { log.Info(arg) } } // Verbosef logs a formatted message at verbose level. func Verbosef(format string, args ...interface{}) { if isEnabled(VerboseLevel) { log.Infof(format, args...) } } // Verbose logs a message at verbose level. func Verbose(arg string) { if isEnabled(VerboseLevel) { log.Info(arg) } } // Debugf logs a formatted message at debug level with caller location. func Debugf(format string, args ...interface{}) { if isEnabled(DebugLevel) { DebugReal(fmt.Sprintf(format, args...), 2) } } // Debug logs a message at debug level with caller location. func Debug(arg string) { if isEnabled(DebugLevel) { DebugReal(arg, 2) } } // DebugReal logs at debug level with caller info from the specified stack depth. func DebugReal(arg string, cs int) { if !isEnabled(DebugLevel) { return } _, callerFile, callerLine, ok := runtime.Caller(cs) if !ok { return } tag := fmt.Sprintf("%s:%d: ", callerFile, callerLine) log.Debug(tag + arg) } // Dump logs a spew dump of the arguments at debug level. func Dump(args ...interface{}) { if isEnabled(DebugLevel) { DebugReal(spew.Sdump(args...), 2) } } // EnableDebugLogging sets the log level to debug. func EnableDebugLogging() { SetLevel(DebugLevel) } // VerbosityStepsToLogLevel converts a -v count to a log level. // 0 returns InfoLevel, 1 returns VerboseLevel, 2+ returns DebugLevel. func VerbosityStepsToLogLevel(l int) Level { switch l { case 0: return InfoLevel case 1: return VerboseLevel default: return DebugLevel } } // SetLevelFromVerbosity sets the log level based on -v flag count. func SetLevelFromVerbosity(l int) { SetLevel(VerbosityStepsToLogLevel(l)) } // SetLevel sets the global log level. func SetLevel(l Level) { mu.Lock() defer mu.Unlock() currentLevel = l } // GetLevel returns the current log level. func GetLevel() Level { mu.RLock() defer mu.RUnlock() return currentLevel } // WithError returns a log entry with the error attached. func WithError(e error) *log.Entry { return log.Log.WithError(e) } // Progressf prints a progress message that overwrites the current line. // Use ProgressDone() when progress is complete to move to the next line. func Progressf(format string, args ...interface{}) { pterm.Printf("\r"+format, args...) } // ProgressDone clears the progress line when progress is complete. func ProgressDone() { // Clear the line with spaces and return to beginning pterm.Print("\r\033[K") }