Files
radiantwave-legacy/internal/logger/logger.go
T

139 lines
2.7 KiB
Go

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)
}