package logger import ( "fmt" "io" "log" "os" "path/filepath" "sync" "time" ) // Log levels const ( LevelInfo = "INFO" LevelWarn = "WARNING" LevelError = "ERROR" LevelDebug = "DEBUG" ) // Logger provides dual-output logging to stdout and a file type Logger struct { infoLog *log.Logger warnLog *log.Logger errorLog *log.Logger debugLog *log.Logger debug bool mu sync.Mutex } var defaultLogger *Logger func init() { defaultLogger = NewLogger(false, "") } // SetDefault sets the default logger instance func SetDefault(l *Logger) { defaultLogger = l } // Default returns the current default logger func Default() *Logger { return defaultLogger } // NewLogger creates a new dual-output logger. // If logPath is empty, only stdout is used. func NewLogger(debug bool, logPath string) *Logger { flags := log.Ldate | log.Ltime | log.Lmicroseconds var infoWriter, warnWriter, errorWriter, debugWriter io.Writer if logPath != "" { // Ensure directory exists dir := filepath.Dir(logPath) if err := os.MkdirAll(dir, 0755); err == nil { file, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err == nil { // Multi-writer: stdout + file infoWriter = io.MultiWriter(os.Stdout, file) warnWriter = io.MultiWriter(os.Stdout, file) errorWriter = io.MultiWriter(os.Stderr, file) debugWriter = io.MultiWriter(os.Stdout, file) fmt.Fprintf(os.Stdout, "%s [%s] LOGGER: log file opened: %s\n", time.Now().Format("2006/01/02 15:04:05.000"), LevelInfo, logPath) } else { fmt.Fprintf(os.Stderr, "%s [%s] LOGGER: failed to open log file %s: %v, using stdout only\n", time.Now().Format("2006/01/02 15:04:05.000"), LevelWarn, logPath, err) infoWriter = os.Stdout warnWriter = os.Stdout errorWriter = os.Stderr debugWriter = os.Stdout } } else { fmt.Fprintf(os.Stderr, "%s [%s] LOGGER: failed to create log dir %s: %v, using stdout only\n", time.Now().Format("2006/01/02 15:04:05.000"), LevelWarn, dir, err) infoWriter = os.Stdout warnWriter = os.Stdout errorWriter = os.Stderr debugWriter = os.Stdout } } else { infoWriter = os.Stdout warnWriter = os.Stdout errorWriter = os.Stderr debugWriter = os.Stdout } return &Logger{ infoLog: log.New(infoWriter, "", flags), warnLog: log.New(warnWriter, "", flags), errorLog: log.New(errorWriter, "", flags), debugLog: log.New(debugWriter, "", flags), debug: debug, } } // Info logs an INFO message func (l *Logger) Info(format string, args ...interface{}) { l.mu.Lock() defer l.mu.Unlock() l.infoLog.Printf("[%s] %s", LevelInfo, fmt.Sprintf(format, args...)) } // Warn logs a WARNING message func (l *Logger) Warn(format string, args ...interface{}) { l.mu.Lock() defer l.mu.Unlock() l.warnLog.Printf("[%s] %s", LevelWarn, fmt.Sprintf(format, args...)) } // Error logs an ERROR message func (l *Logger) Error(format string, args ...interface{}) { l.mu.Lock() defer l.mu.Unlock() l.errorLog.Printf("[%s] %s", LevelError, fmt.Sprintf(format, args...)) } // Debug logs a DEBUG message (only if debug mode is enabled) func (l *Logger) Debug(format string, args ...interface{}) { if !l.debug { return } l.mu.Lock() defer l.mu.Unlock() l.debugLog.Printf("[%s] %s", LevelDebug, fmt.Sprintf(format, args...)) } // Info logs an INFO message using the default logger func Info(format string, args ...interface{}) { defaultLogger.Info(format, args...) } // Warn logs a WARNING message using the default logger func Warn(format string, args ...interface{}) { defaultLogger.Warn(format, args...) } // Error logs an ERROR message using the default logger func Error(format string, args ...interface{}) { defaultLogger.Error(format, args...) } // Debug logs a DEBUG message using the default logger func Debug(format string, args ...interface{}) { defaultLogger.Debug(format, args...) } // SetDebug enables or disables debug logging func (l *Logger) SetDebug(debug bool) { l.debug = debug }