diff --git a/game/lobby.go b/game/lobby.go index bfb5c47..0c19d93 100644 --- a/game/lobby.go +++ b/game/lobby.go @@ -2,6 +2,7 @@ package game import ( "fmt" + "log/slog" "math/rand" "sync" @@ -138,6 +139,7 @@ func (l *Lobby) CreateRoom(name string) string { Name: name, Status: RoomWaiting, } + slog.Info("room created", "code", code, "name", name) return code } @@ -155,6 +157,7 @@ func (l *Lobby) JoinRoom(code, playerName, fingerprint string) error { return fmt.Errorf("room %s already in progress", code) } room.Players = append(room.Players, LobbyPlayer{Name: playerName, Fingerprint: fingerprint}) + slog.Info("player joined", "room", code, "player", playerName) return nil } @@ -218,6 +221,7 @@ func (l *Lobby) StartRoom(code string) { defer l.mu.Unlock() if room, ok := l.rooms[code]; ok { room.Status = RoomPlaying + slog.Info("game started", "room", code, "players", len(room.Players)) } } diff --git a/game/session.go b/game/session.go index 9082abc..0deb957 100644 --- a/game/session.go +++ b/game/session.go @@ -2,6 +2,7 @@ package game import ( "fmt" + "log/slog" "sync" "time" @@ -143,6 +144,7 @@ func (s *GameSession) combatLoop() { s.mu.Unlock() if gameOver { + slog.Info("game over", "floor", s.state.FloorNum, "victory", s.state.Victory) return } @@ -155,6 +157,7 @@ func (s *GameSession) combatLoop() { if p.Fingerprint != "" && !p.IsOut() { if last, ok := s.lastActivity[p.Fingerprint]; ok { if now.Sub(last) > 60*time.Second { + slog.Warn("player inactive removed", "fingerprint", p.Fingerprint, "name", p.Name) s.addLog(fmt.Sprintf("%s removed (disconnected)", p.Name)) changed = true continue diff --git a/main.go b/main.go index 271b788..d9c5222 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "fmt" "log" + "log/slog" "os" "github.com/tolelom/catacombs/config" @@ -15,6 +16,11 @@ import ( func main() { os.MkdirAll("data", 0755) + logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ + Level: slog.LevelInfo, + })) + slog.SetDefault(logger) + cfg, err := config.Load("config.yaml") if err != nil { if os.IsNotExist(err) { @@ -38,11 +44,11 @@ func main() { // Start web terminal server in background go func() { if err := web.Start(webAddr, cfg.Server.SSHPort); err != nil { - log.Printf("Web server error: %v", err) + slog.Error("web server error", "error", err) } }() - log.Printf("Catacombs server starting — SSH :%d, Web :%d", cfg.Server.SSHPort, cfg.Server.HTTPPort) + slog.Info("server starting", "ssh_port", cfg.Server.SSHPort, "http_port", cfg.Server.HTTPPort) if err := server.Start(sshAddr, lobby, db); err != nil { log.Fatal(err) } diff --git a/server/ssh.go b/server/ssh.go index 0fb3e17..8187dd1 100644 --- a/server/ssh.go +++ b/server/ssh.go @@ -2,7 +2,7 @@ package server import ( "fmt" - "log" + "log/slog" "github.com/charmbracelet/ssh" "github.com/charmbracelet/wish" @@ -31,6 +31,14 @@ func Start(addr string, lobby *game.Lobby, db *store.DB) error { if s.PublicKey() != nil { fingerprint = gossh.FingerprintSHA256(s.PublicKey()) } + + defer func() { + if r := recover(); r != nil { + slog.Error("session panic recovered", "error", r, "fingerprint", fingerprint) + } + }() + + slog.Info("new SSH session", "fingerprint", fingerprint, "width", pty.Window.Width, "height", pty.Window.Height) m := ui.NewModel(pty.Window.Width, pty.Window.Height, fingerprint, lobby, db) return m, []tea.ProgramOption{tea.WithAltScreen()} }), @@ -40,6 +48,6 @@ func Start(addr string, lobby *game.Lobby, db *store.DB) error { return fmt.Errorf("could not create server: %w", err) } - log.Printf("Starting SSH server on %s", addr) + slog.Info("starting SSH server", "addr", addr) return s.ListenAndServe() } diff --git a/web/server.go b/web/server.go index d15f902..e54f910 100644 --- a/web/server.go +++ b/web/server.go @@ -5,7 +5,7 @@ import ( "encoding/json" "fmt" "io" - "log" + "log/slog" "net/http" "sync" @@ -38,14 +38,14 @@ func Start(addr string, sshPort int) error { handleWS(w, r, sshPort) }) - log.Printf("Starting web terminal on %s", addr) + slog.Info("starting web terminal", "addr", addr) return http.ListenAndServe(addr, mux) } func handleWS(w http.ResponseWriter, r *http.Request, sshPort int) { ws, err := upgrader.Upgrade(w, r, nil) if err != nil { - log.Printf("WebSocket upgrade error: %v", err) + slog.Error("WebSocket upgrade error", "error", err) return } defer ws.Close() @@ -62,7 +62,7 @@ func handleWS(w http.ResponseWriter, r *http.Request, sshPort int) { sshAddr := fmt.Sprintf("localhost:%d", sshPort) client, err := ssh.Dial("tcp", sshAddr, sshConfig) if err != nil { - log.Printf("SSH dial error: %v", err) + slog.Error("SSH dial error", "error", err) ws.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("Failed to connect to game server: %v\r\n", err))) return } @@ -70,7 +70,7 @@ func handleWS(w http.ResponseWriter, r *http.Request, sshPort int) { session, err := client.NewSession() if err != nil { - log.Printf("SSH session error: %v", err) + slog.Error("SSH session error", "error", err) return } defer session.Close() @@ -81,24 +81,24 @@ func handleWS(w http.ResponseWriter, r *http.Request, sshPort int) { ssh.TTY_OP_ISPEED: 14400, ssh.TTY_OP_OSPEED: 14400, }); err != nil { - log.Printf("PTY request error: %v", err) + slog.Error("PTY request error", "error", err) return } stdin, err := session.StdinPipe() if err != nil { - log.Printf("stdin pipe error: %v", err) + slog.Error("stdin pipe error", "error", err) return } stdout, err := session.StdoutPipe() if err != nil { - log.Printf("stdout pipe error: %v", err) + slog.Error("stdout pipe error", "error", err) return } if err := session.Shell(); err != nil { - log.Printf("shell error: %v", err) + slog.Error("shell error", "error", err) return }