package logger import ( "fmt" "log" "os" "sync" "time" "gorm.io/gorm" ) type Level int const ( LevelDebug Level = iota LevelInfo LevelWarning LevelError LevelFatal ) func (l Level) String() string { return [...]string{"DEBUG", "INFO", "WARNING", "ERROR", "FATAL"}[l] } type logEntry struct { timestamp string level string message string } var ( mu sync.Mutex db *gorm.DB file *os.File console = log.New(os.Stderr, "", 0) dbChan chan logEntry dbChanOnce sync.Once ) // startDBWriter starts a single background goroutine to drain log entries to the database. func startDBWriter() { dbChan = make(chan logEntry, 256) go func() { for entry := range dbChan { row := map[string]any{ "timestamp": entry.timestamp, "level": entry.level, "message": entry.message, } if err := db.Table("log_entries").Create(row).Error; err != nil { mu.Lock() console.Printf("Failed to write log to database: %v", err) mu.Unlock() } } }() } // AddFile attaches or replaces the file backend func AddFile(path string) error { mu.Lock() defer mu.Unlock() if file != nil { file.Close() } f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return err } file = f return nil } // AddDatabase attaches or replaces the database backend func AddDatabase(d *gorm.DB) { mu.Lock() defer mu.Unlock() db = d dbChanOnce.Do(startDBWriter) } // Close shuts down the database writer goroutine and closes the file backend. func Close() error { mu.Lock() defer mu.Unlock() // Stop the DB writer goroutine by closing the channel if dbChan != nil { close(dbChan) dbChan = nil db = nil } if file != nil { err := file.Close() file = nil return err } return nil } func logMsg(level Level, format string, args ...any) { message := fmt.Sprintf(format, args...) timestamp := time.Now().Format("2006-01-02 15:04:05.000") line := fmt.Sprintf("%s [%s] %s", timestamp, level, message) mu.Lock() console.Println(line) if file != nil { fmt.Fprintln(file, line) } if db != nil && dbChan != nil { // both cleared by Close() select { case dbChan <- logEntry{timestamp: timestamp, level: level.String(), message: message}: default: // Channel full, drop the DB write rather than blocking } } mu.Unlock() } func Debug(format string, args ...any) { logMsg(LevelDebug, format, args...) } func Info(format string, args ...any) { logMsg(LevelInfo, format, args...) } func Warning(format string, args ...any) { logMsg(LevelWarning, format, args...) } func Error(format string, args ...any) { logMsg(LevelError, format, args...) } func Fatal(format string, args ...any) { logMsg(LevelFatal, format, args...) time.Sleep(100 * time.Millisecond) os.Exit(1) }