package logger import ( "io" "log" "os" "path/filepath" "sync" "gopkg.in/natefinch/lumberjack.v2" ) var ( defaultLogger *Logger once sync.Once ) // Logger wraps the standard log.Logger with file and stdout output type Logger struct { *log.Logger fileWriter io.WriteCloser } // Init initializes the default logger with both file and stdout output func Init(logDir, logFileName string, maxSizeMB int, maxBackups int, maxAgeDays int) error { var err error once.Do(func() { defaultLogger, err = New(logDir, logFileName, maxSizeMB, maxBackups, maxAgeDays) 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 } // New creates a new logger that writes to both stdout and a log file func New(logDir, logFileName string, maxSizeMB int, maxBackups int, maxAgeDays int) (*Logger, error) { // Ensure log directory exists if err := os.MkdirAll(logDir, 0755); err != nil { return nil, err } logPath := filepath.Join(logDir, logFileName) // Create file writer with rotation fileWriter := &lumberjack.Logger{ Filename: logPath, MaxSize: maxSizeMB, // megabytes MaxBackups: maxBackups, // number of backup files MaxAge: maxAgeDays, // days Compress: true, // compress old log files } // 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 } // Printf logs a formatted message func Printf(format string, v ...interface{}) { if defaultLogger != nil { defaultLogger.Printf(format, v...) } else { log.Printf(format, v...) } } // Print logs a message func Print(v ...interface{}) { if defaultLogger != nil { defaultLogger.Print(v...) } else { log.Print(v...) } } // Println logs a message with newline func Println(v ...interface{}) { if defaultLogger != nil { defaultLogger.Println(v...) } else { log.Println(v...) } } // Fatal logs a message and exits func Fatal(v ...interface{}) { if defaultLogger != nil { defaultLogger.Fatal(v...) } else { log.Fatal(v...) } } // Fatalf logs a formatted message and exits func Fatalf(format string, v ...interface{}) { if defaultLogger != nil { defaultLogger.Fatalf(format, v...) } else { log.Fatalf(format, v...) } }