package logger import ( "fmt" "io" "log" "os" "path/filepath" "sync" ) // Level represents log severity type Level int const ( LevelDebug Level = iota LevelInfo LevelWarn LevelError ) var levelNames = map[Level]string{ LevelDebug: "DEBUG", LevelInfo: "INFO", LevelWarn: "WARN", LevelError: "ERROR", } // ParseLevel parses a level string into a Level func ParseLevel(s string) Level { switch s { case "debug", "DEBUG": return LevelDebug case "info", "INFO": return LevelInfo case "warn", "WARN", "warning", "WARNING": return LevelWarn case "error", "ERROR": return LevelError default: return LevelInfo } } var ( defaultLogger *Logger once sync.Once currentLevel Level = LevelInfo ) // Logger wraps the standard log.Logger with optional file output and levels type Logger struct { *log.Logger fileWriter io.WriteCloser } // SetLevel sets the global log level func SetLevel(level Level) { currentLevel = level } // GetLevel returns the current log level func GetLevel() Level { return currentLevel } // InitStdout initializes the logger to only write to stdout func InitStdout() { once.Do(func() { log.SetOutput(os.Stdout) log.SetFlags(log.LstdFlags | log.Lshortfile) defaultLogger = &Logger{ Logger: log.Default(), fileWriter: nil, } }) } // InitWithFile initializes the logger with both file and stdout output // The file is truncated on each start func InitWithFile(logPath string) error { var err error once.Do(func() { defaultLogger, err = NewWithFile(logPath) if err != nil { return } // Replace standard log output with the multi-writer multiWriter := io.MultiWriter(os.Stdout, defaultLogger.fileWriter) log.SetOutput(multiWriter) log.SetFlags(log.LstdFlags | log.Lshortfile) }) return err } // NewWithFile creates a new logger that writes to both stdout and a log file // The file is truncated on each start func NewWithFile(logPath string) (*Logger, error) { // Ensure log directory exists logDir := filepath.Dir(logPath) if err := os.MkdirAll(logDir, 0755); err != nil { return nil, err } // Create/truncate the log file fileWriter, err := os.Create(logPath) if err != nil { return nil, err } // Create multi-writer that writes to both stdout and file multiWriter := io.MultiWriter(os.Stdout, fileWriter) // Create logger with standard flags logger := log.New(multiWriter, "", log.LstdFlags|log.Lshortfile) return &Logger{ Logger: logger, fileWriter: fileWriter, }, nil } // Close closes the file writer func (l *Logger) Close() error { if l.fileWriter != nil { return l.fileWriter.Close() } return nil } // GetDefault returns the default logger instance func GetDefault() *Logger { return defaultLogger } // logf logs a formatted message at the given level func logf(level Level, format string, v ...interface{}) { if level < currentLevel { return } prefix := fmt.Sprintf("[%s] ", levelNames[level]) msg := fmt.Sprintf(format, v...) log.Print(prefix + msg) } // logln logs a message at the given level func logln(level Level, v ...interface{}) { if level < currentLevel { return } prefix := fmt.Sprintf("[%s] ", levelNames[level]) msg := fmt.Sprint(v...) log.Print(prefix + msg) } // Debug logs a debug message func Debug(v ...interface{}) { logln(LevelDebug, v...) } // Debugf logs a formatted debug message func Debugf(format string, v ...interface{}) { logf(LevelDebug, format, v...) } // Info logs an info message func Info(v ...interface{}) { logln(LevelInfo, v...) } // Infof logs a formatted info message func Infof(format string, v ...interface{}) { logf(LevelInfo, format, v...) } // Warn logs a warning message func Warn(v ...interface{}) { logln(LevelWarn, v...) } // Warnf logs a formatted warning message func Warnf(format string, v ...interface{}) { logf(LevelWarn, format, v...) } // Error logs an error message func Error(v ...interface{}) { logln(LevelError, v...) } // Errorf logs a formatted error message func Errorf(format string, v ...interface{}) { logf(LevelError, format, v...) } // Fatal logs an error message and exits func Fatal(v ...interface{}) { logln(LevelError, v...) os.Exit(1) } // Fatalf logs a formatted error message and exits func Fatalf(format string, v ...interface{}) { logf(LevelError, format, v...) os.Exit(1) } // --- Backwards compatibility (maps to Info level) --- // Printf logs a formatted message at Info level func Printf(format string, v ...interface{}) { logf(LevelInfo, format, v...) } // Print logs a message at Info level func Print(v ...interface{}) { logln(LevelInfo, v...) } // Println logs a message at Info level func Println(v ...interface{}) { logln(LevelInfo, v...) }