Files
2026-05-28 21:32:46 +02:00

104 lines
2.9 KiB
Go

// Package applog routes the app's diagnostic output to a rotating log
// file inside the user's data dir. Wails builds with the Windows GUI
// subsystem by default — fmt.Println output is dropped, so launching
// from cmd never showed anything. The file gives us a reliable place to
// inspect what the UDP listener / cluster / CAT layer is doing.
package applog
import (
"fmt"
"io"
"log"
"os"
"path/filepath"
"sync"
"time"
)
var (
mu sync.Mutex
file *os.File
path string
)
// Init opens (creates) the log file in dataDir. On rotation we truncate
// at startup if the file is too big; for now it's a single file, no
// rolling — the volume is low (a few KB per session).
func Init(dataDir string) (string, error) {
mu.Lock()
defer mu.Unlock()
if file != nil {
return path, nil
}
if dataDir == "" {
return "", fmt.Errorf("empty data dir")
}
if err := os.MkdirAll(dataDir, 0o755); err != nil {
return "", err
}
logPath := filepath.Join(dataDir, "opslog.log")
// One-shot rename for users coming from the HamLog era.
if _, err := os.Stat(logPath); os.IsNotExist(err) {
oldLog := filepath.Join(dataDir, "hamlog.log")
if _, err := os.Stat(oldLog); err == nil {
_ = os.Rename(oldLog, logPath)
}
}
// Truncate if the file grew past ~5MB so we don't accumulate logs
// forever. We keep one file — simple and adequate for diagnostics.
if fi, err := os.Stat(logPath); err == nil && fi.Size() > 5*1024*1024 {
_ = os.Remove(logPath)
}
f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
if err != nil {
return "", err
}
file = f
path = logPath
// Redirect log.Print* and the standard logger to the file too, so
// any third-party output stays consistent.
log.SetOutput(io.MultiWriter(file, os.Stderr))
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
fmt.Fprintf(file, "\n────── OpsLog start %s ──────\n", time.Now().Format(time.RFC3339))
return logPath, nil
}
// Printf writes a formatted line with a timestamp. Caller's format may
// or may not end with a newline — we strip a trailing one before adding
// our own, so log entries always look like "HH:MM:SS.mmm msg\n".
func Printf(format string, args ...any) {
mu.Lock()
defer mu.Unlock()
stamp := time.Now().Format("15:04:05.000")
msg := fmt.Sprintf(format, args...)
for len(msg) > 0 && (msg[len(msg)-1] == '\n' || msg[len(msg)-1] == '\r') {
msg = msg[:len(msg)-1]
}
if file != nil {
fmt.Fprintf(file, "%s %s\n", stamp, msg)
}
// Also dump to stderr in case the binary was launched with a console
// attached (wails dev, custom build).
fmt.Fprintf(os.Stderr, "%s %s\n", stamp, msg)
}
// Path returns where the file is so the UI can surface it.
func Path() string {
mu.Lock()
defer mu.Unlock()
return path
}
// Close flushes and releases the handle. Called from shutdown.
func Close() {
mu.Lock()
defer mu.Unlock()
if file != nil {
_ = file.Close()
file = nil
}
}