41bafa57cc
- app.md - applog.go - frontend/package.json - frontend/package.json.md5 - frontend/vite.config.ts - go.mod - main.go - matrix_service.go - sqlite_driver.go - .wails_dev.log - ... Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
124 lines
2.9 KiB
Go
124 lines
2.9 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
)
|
|
|
|
// Logger writes structured logs to both stderr (dev) and a rotating file under the user
|
|
// config dir. Acceso seguro entre goroutines via sync.Mutex.
|
|
type Logger struct {
|
|
mu sync.Mutex
|
|
file *os.File
|
|
slogger *slog.Logger
|
|
logPath string
|
|
}
|
|
|
|
var globalLogger *Logger
|
|
|
|
func InitLogger() (*Logger, error) {
|
|
cfgDir, err := os.UserConfigDir()
|
|
if err != nil {
|
|
cfgDir = filepath.Join(os.Getenv("HOME"), ".config")
|
|
}
|
|
dir := filepath.Join(cfgDir, "matrix_client_pc")
|
|
if err := os.MkdirAll(dir, 0o700); err != nil {
|
|
return nil, fmt.Errorf("mkdir log dir: %w", err)
|
|
}
|
|
logPath := filepath.Join(dir, "app.log")
|
|
f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open log file: %w", err)
|
|
}
|
|
|
|
// File only — Wails GUI apps on Windows have closed stderr handle, which
|
|
// breaks MultiWriter (one failing writer aborts the chain in some Go versions).
|
|
handler := slog.NewTextHandler(f, &slog.HandlerOptions{
|
|
Level: slog.LevelDebug,
|
|
})
|
|
l := &Logger{
|
|
file: f,
|
|
slogger: slog.New(handler),
|
|
logPath: logPath,
|
|
}
|
|
globalLogger = l
|
|
l.Info("logger initialized", "path", logPath)
|
|
return l, nil
|
|
}
|
|
|
|
func (l *Logger) Close() error {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
if l.file != nil {
|
|
_ = l.file.Sync()
|
|
return l.file.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *Logger) Path() string { return l.logPath }
|
|
|
|
func (l *Logger) Debug(msg string, args ...any) { l.slogger.Debug(msg, args...) }
|
|
func (l *Logger) Info(msg string, args ...any) { l.slogger.Info(msg, args...) }
|
|
func (l *Logger) Warn(msg string, args ...any) { l.slogger.Warn(msg, args...) }
|
|
func (l *Logger) Error(msg string, args ...any) { l.slogger.Error(msg, args...) }
|
|
|
|
// Package-level helpers — no-op if InitLogger not called yet (defensive for tests).
|
|
func logDebug(msg string, args ...any) {
|
|
if globalLogger != nil {
|
|
globalLogger.Debug(msg, args...)
|
|
}
|
|
}
|
|
func logInfo(msg string, args ...any) {
|
|
if globalLogger != nil {
|
|
globalLogger.Info(msg, args...)
|
|
}
|
|
}
|
|
func logWarn(msg string, args ...any) {
|
|
if globalLogger != nil {
|
|
globalLogger.Warn(msg, args...)
|
|
}
|
|
}
|
|
func logError(msg string, args ...any) {
|
|
if globalLogger != nil {
|
|
globalLogger.Error(msg, args...)
|
|
}
|
|
}
|
|
|
|
// TailLog returns the last N lines of the log file.
|
|
func TailLog(n int) ([]string, error) {
|
|
if globalLogger == nil {
|
|
return nil, fmt.Errorf("logger not initialized")
|
|
}
|
|
b, err := os.ReadFile(globalLogger.logPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read log: %w", err)
|
|
}
|
|
// Split + return last N.
|
|
lines := splitLines(string(b))
|
|
if len(lines) <= n {
|
|
return lines, nil
|
|
}
|
|
return lines[len(lines)-n:], nil
|
|
}
|
|
|
|
func splitLines(s string) []string {
|
|
out := []string{}
|
|
cur := ""
|
|
for _, r := range s {
|
|
if r == '\n' {
|
|
out = append(out, cur)
|
|
cur = ""
|
|
continue
|
|
}
|
|
cur += string(r)
|
|
}
|
|
if cur != "" {
|
|
out = append(out, cur)
|
|
}
|
|
return out
|
|
}
|