FloorVisualizer/internal/logger/logger.go

140 lines
2.8 KiB
Go
Raw Permalink Normal View History

package logger
import (
"fmt"
"io"
"log"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
)
type Level int
const (
DEBUG Level = iota
INFO
WARN
ERROR
)
const maxLogAge = 30 * 24 * time.Hour
var levelNames = map[Level]string{DEBUG: "DEBUG", INFO: "INFO", WARN: "WARN", ERROR: "ERROR"}
var (
mu sync.Mutex
std *log.Logger
logDir string
logLevel Level = INFO
logFile *os.File
todayDate string
)
// Init sets up logging with daily rotation to dir/app-YYYY-MM-DD.log.
func Init(dir, level string) {
logDir = dir
os.MkdirAll(dir, 0755)
switch strings.ToUpper(level) {
case "DEBUG":
logLevel = DEBUG
case "WARN":
logLevel = WARN
case "ERROR":
logLevel = ERROR
default:
logLevel = INFO
}
openToday()
go dailyCleanup()
Info("Logger initialized, level=%s, dir=%s", level, dir)
}
func openToday() {
if logFile != nil {
logFile.Close()
}
todayDate = time.Now().Format("2006-01-02")
f, err := os.OpenFile(filepath.Join(logDir, "app-"+todayDate+".log"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
log.Printf("Failed to open log file: %v, logging to stdout only", err)
std = log.New(os.Stdout, "", 0)
logFile = nil
return
}
logFile = f
multi := io.MultiWriter(os.Stdout, f)
std = log.New(multi, "", 0)
}
func dailyCleanup() {
for {
time.Sleep(1 * time.Hour)
// Delete old logs
cutoff := time.Now().Add(-maxLogAge)
entries, _ := os.ReadDir(logDir)
for _, e := range entries {
if strings.HasPrefix(e.Name(), "app-") && strings.HasSuffix(e.Name(), ".log") {
dateStr := strings.TrimPrefix(strings.TrimSuffix(e.Name(), ".log"), "app-")
if t, err := time.Parse("2006-01-02", dateStr); err == nil && t.Before(cutoff) {
os.Remove(filepath.Join(logDir, e.Name()))
}
}
}
}
}
func Close() {
if logFile != nil {
logFile.Close()
}
}
func rotateIfNeeded() {
d := time.Now().Format("2006-01-02")
if d != todayDate {
mu.Lock()
if d != todayDate {
openToday()
}
mu.Unlock()
}
}
func output(level Level, format string, args ...any) {
if level < logLevel {
return
}
rotateIfNeeded()
msg := fmt.Sprintf(format, args...)
now := time.Now().Format("2006-01-02 15:04:05.000")
_, file, line, _ := runtime.Caller(2)
short := file[strings.LastIndex(file, "/")+1:]
lineStr := fmt.Sprintf("%s [%s] %s:%d %s", now, levelNames[level], short, line, msg)
mu.Lock()
if std != nil {
std.Println(lineStr)
}
mu.Unlock()
}
func Debug(format string, args ...any) { output(DEBUG, format, args...) }
func Info(format string, args ...any) { output(INFO, format, args...) }
func Warn(format string, args ...any) { output(WARN, format, args...) }
func Error(format string, args ...any) { output(ERROR, format, args...) }
func StdLogger() *log.Logger {
if std != nil {
return std
}
return log.New(os.Stdout, "", 0)
}