Initial release: RadiantWave v1.0
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user