Update Database

Database is more usable now.
All pages have been updated to use the database, and share an application configuration.
Page Stack has been removed, no more pages hanging around in memory. Replaced with a 'Router' which uses an Enum to determine which page should be displayed, destroys the old page and initializes the new page all in one go. No more push, pop, rollback, etc. No more holding instances of pages.
Logger has been updated with proper timestamps and sorting now works as expected."
This commit is contained in:
NeilClack
2026-03-23 15:02:07 -05:00
parent f8fbfb721d
commit 17c088cc2f
28 changed files with 537 additions and 2276 deletions
+22 -59
View File
@@ -4,10 +4,10 @@ import (
"fmt"
"log"
"os"
"sync"
"time"
"gorm.io/gorm"
"rhemawavetech.com/rhemawave/internal/db"
)
type Level int
@@ -24,78 +24,34 @@ 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
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)
_, 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)
console.Println("Adding database to logger")
DB = d
}
// Close shuts down the database writer goroutine and closes the file backend.
// Close shuts down 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
@@ -106,24 +62,31 @@ func Close() error {
func logMsg(level Level, format string, args ...any) {
message := fmt.Sprintf(format, args...)
timestamp := time.Now().Format("2006-01-02 15:04:05.000")
now := time.Now()
timestamp := now.Format("2006-01-02 15:04:05.000")
line := fmt.Sprintf("%s [%s] %s", timestamp, level, message)
mu.Lock()
// 1. The standard log package is internally thread-safe
console.Println(line)
// 2. Write to file
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
// 3. Write to DB using GORM. It handles its own connection pooling and locking.
if DB != nil {
var entry db.Log
entry.Timestamp = now.UnixMilli()
entry.Level = level.String()
entry.Message = message
// Use Save() to insert the new record.
result := DB.Save(&entry)
if result.Error != nil {
console.Printf("Failed to write log to database: %v\n", result.Error)
}
}
mu.Unlock()
}
func Debug(format string, args ...any) { logMsg(LevelDebug, format, args...) }