up
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user