// internal/logging/logging.go package logging import ( "fmt" "io" "log" "os" "path/filepath" "runtime" "sync" ) var ( // Global logger instance logger *Logger once sync.Once ) // LogLevel represents the severity of a log message type LogLevel int const ( DEBUG LogLevel = iota INFO WARN ERROR FATAL ) // Logger provides logging functionality type Logger struct { logLevel LogLevel output io.Writer stdLog *log.Logger errLog *log.Logger mu sync.Mutex } // Singleton func InitLogger(level LogLevel, logFile string) { once.Do(func() { var output io.Writer = os.Stdout if logFile != "" { logDir := filepath.Dir(logFile) if err := os.MkdirAll(logDir, 0755); err != nil { fmt.Fprintf(os.Stderr, "Failed to create log directory: %v\n", err) os.Exit(1) } file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { fmt.Fprintf(os.Stderr, "Failed to open log file: %v\n", err) os.Exit(1) } output = io.MultiWriter(os.Stdout, file) } logger = &Logger{ logLevel: level, output: output, stdLog: log.New(output, "", log.LstdFlags), errLog: log.New(output, "ERROR: ", log.LstdFlags), } }) } // Singleton func GetLogger() *Logger { if logger == nil { InitLogger(INFO, "") } return logger } func (l *Logger) SetLogLevel(level LogLevel) { l.mu.Lock() defer l.mu.Unlock() l.logLevel = level } func (l *Logger) log(level LogLevel, format string, v ...interface{}) { if level < l.logLevel { return } l.mu.Lock() defer l.mu.Unlock() // Get caller information _, file, line, ok := runtime.Caller(2) if !ok { file = "unknown" line = 0 } // Format message with source location message := fmt.Sprintf("%s:%d: %s", filepath.Base(file), line, fmt.Sprintf(format, v...)) if level >= ERROR { l.errLog.Println(message) } else { l.stdLog.Println(message) } } // 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...) } // Fatal logs a fatal message and exits the program func (l *Logger) Fatal(format string, v ...interface{}) { l.log(FATAL, format, v...) os.Exit(1) } // ErrorWithStack logs an error with its stack trace func (l *Logger) ErrorWithStack(err error, format string, v ...interface{}) { if err == nil { return } message := fmt.Sprintf(format, v...) buf := make([]byte, 1024) n := runtime.Stack(buf, false) l.Error("%s: %v\n%s", message, err, buf[:n]) }