feat: persist last user + diagnostics + logging + icon + defensive whoami
Backend: - last_user.go: writes/reads <UserConfigDir>/matrix_client_pc/last_user.txt. Login persists; Logout clears. - GetLastUserID bind replaces fragile localStorage in App.tsx. - GetDiagnostics bind: live snapshot (started, client_ready, crypto_init, sync_active, rooms_count, encrypted_rooms, dms_count, last_error). - applog.go: slog to stderr + <UserConfigDir>/matrix_client_pc/app.log. GetLogTail + GetLogPath binds. - matrix_service.go logs throughout Login/Start. After MatrixClientInit, if client.DeviceID empty -> retry whoami + persist back (defensive). - main.go inits logger before wails.Run, OnShutdown logs close. Frontend: - App.tsx awaits GetLastUserID() instead of localStorage. - HomeScreen.tsx Health modal (green stethoscope button) with HealthRow status dots — comprobar chats. - Auto-relogin on token-rejected error in Start(). Icon: - appicon.ico (Phosphor chat-circle + #7c3aed) generated via generate_app_icon. - build/windows/icon.ico replaced (Wails embeds via windres). - build/appicon.png regenerated from ico (256x256). Refs: issues 0147 + 0148 + 0150 (partial). Fixes M_UNKNOWN_TOKEN auto-recovery.
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"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)
|
||||
}
|
||||
|
||||
multi := io.MultiWriter(os.Stderr, f)
|
||||
handler := slog.NewTextHandler(multi, &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
|
||||
}
|
||||
Reference in New Issue
Block a user