// logger.go package main import ( "fmt" "io" "log" "os" "runtime" "time" ) type Logger struct { *log.Logger writers []io.Writer level LogLevel } type LogLevel int const ( DEBUG LogLevel = iota INFO WARN ERROR ) var ( instance *Logger // Map for converting string to LogLevel logLevelMap = map[string]LogLevel{ "DEBUG": DEBUG, "INFO": INFO, "WARN": WARN, "ERROR": ERROR, } ) // Initialize creates a new logger instance with specified configuration func Initialize(logPath string, logOutput string) (*Logger, error) { if instance != nil { return instance, nil } var writers []io.Writer // Handle different output configurations switch logOutput { case "stdout": writers = append(writers, os.Stdout) case "file": file, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { return nil, fmt.Errorf("failed to open log file: %v", err) } writers = append(writers, file) case "both": file, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { return nil, fmt.Errorf("failed to open log file: %v", err) } writers = append(writers, file, os.Stdout) default: // Default to stdout if invalid option provided writers = append(writers, os.Stdout) } // Create multi-writer if we have multiple writers var writer io.Writer if len(writers) > 1 { writer = io.MultiWriter(writers...) } else { writer = writers[0] } // Create logger instance logger := &Logger{ Logger: log.New(writer, "", 0), writers: writers, level: INFO, // Default to INFO level } instance = logger return logger, nil } // SetLogLevel sets the logging level from a string func (l *Logger) SetLogLevel(levelStr string) error { level, ok := logLevelMap[levelStr] if !ok { return fmt.Errorf("invalid log level: %s. Must be one of: DEBUG, INFO, WARN, ERROR", levelStr) } l.level = level return nil } // GetLogLevel returns the current logging level as a string func (l *Logger) GetLogLevel() string { for str, level := range logLevelMap { if level == l.level { return str } } return "UNKNOWN" } // GetLogger returns the singleton logger instance func GetLogger() *Logger { return instance } // Close closes all writers that implement io.Closer func (l *Logger) Close() error { var errs []error for _, writer := range l.writers { if closer, ok := writer.(io.Closer); ok { if err := closer.Close(); err != nil { errs = append(errs, err) } } } if len(errs) > 0 { return fmt.Errorf("errors closing writers: %v", errs) } return nil } func (l *Logger) log(level LogLevel, format string, v ...interface{}) { if l == nil { return } if level < l.level { return } // Get caller information _, file, line, _ := runtime.Caller(2) // Create timestamp timestamp := time.Now().Format("2006-01-02 15:04:05") // Create level string levelStr := "INFO" switch level { case DEBUG: levelStr = "DEBUG" case WARN: levelStr = "WARN" case ERROR: levelStr = "ERROR" } // Format message message := fmt.Sprintf(format, v...) // Final log format logLine := fmt.Sprintf("[%s] [%s] [%s:%d] %s\n", timestamp, levelStr, file, line, message, ) l.Logger.Print(logLine) } // Debug logs a debug message func (l *Logger) Debug(format string, v ...interface{}) { l.log(DEBUG, format, v...) } // Info logs an info message func (l *Logger) Info(format string, v ...interface{}) { l.log(INFO, format, v...) } // Warn logs a warning message func (l *Logger) Warn(format string, v ...interface{}) { l.log(WARN, format, v...) } // Error logs an error message func (l *Logger) Error(format string, v ...interface{}) { l.log(ERROR, format, v...) } // HTTPRequest logs an HTTP request func (l *Logger) HTTPRequest(method, path, username string, statusCode int) { l.log(INFO, "HTTP %s %s - User: %s - Status: %d", method, path, username, statusCode, ) } // UserAction logs user actions like login, logout, etc. func (l *Logger) UserAction(username, action string) { l.log(INFO, "User Action - Username: %s - Action: %s", username, action, ) } // DatabaseAction logs database operations func (l *Logger) DatabaseAction(operation, details string) { l.log(INFO, "Database Action - Operation: %s - Details: %s", operation, details, ) }