feat: add structured logging with log/slog and panic recovery
Replace log.Printf/Println with slog.Info/Error/Warn across the codebase. Initialize slog with JSON handler in main.go. Add panic recovery defer in SSH session handler. Add structured game event logging (room created, player joined, game started, game over, player inactive removed). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
10
main.go
10
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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user