139 lines
2.7 KiB
Go
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)
|
|
}
|