From 790706dbf1e797607dfb6c6717ab863a9e4cb5ca Mon Sep 17 00:00:00 2001 From: Florian Fischer Date: Tue, 19 Aug 2025 15:36:10 +0200 Subject: add simlpe plain text log handler and consider loglevel from the env --- go/client/game.go | 8 ++-- go/log/handler.go | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++--- go/log/log.go | 11 ++++-- 3 files changed, 124 insertions(+), 11 deletions(-) diff --git a/go/client/game.go b/go/client/game.go index caf73933..3dacd209 100644 --- a/go/client/game.go +++ b/go/client/game.go @@ -3,13 +3,14 @@ package main import ( "fmt" "image/color" - "log" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/ebitenutil" "muhq.space/muhqs-game/go/activities" + "muhq.space/muhqs-game/go/assets" "muhq.space/muhqs-game/go/game" + "muhq.space/muhqs-game/go/log" "muhq.space/muhqs-game/go/ui" "muhq.space/muhqs-game/go/utils" ) @@ -544,7 +545,7 @@ func (g *Game) handlePlayerNotifications() { // FIXME } for n.Valid() { - log.Println("Received", n) + log.Debug("Received", "notification", n) switch n.Notification { case game.PriorityNotification: g.hasPriority = true @@ -616,13 +617,14 @@ func (g *Game) progressPlayAction(a *game.PlayAction) { g.addCancelablePrompt(a, fmt.Sprintf("Select targets to play %v", a.Card.Name)) g.handLayer.HighlightCard(a.Card, ui.HighlightSelectionColor) } else { + log.Debug("No targets or no selection allowed", "action", a) g.declareAction(a) } } func (g *Game) handleSelection(obj any, x, y int) { if obj == nil { - log.Printf("No object found at cursor position (%d, %d)\n", x, y) + log.Debug("No object found at cursor", "cursor", game.Position{x, y}) return } diff --git a/go/log/handler.go b/go/log/handler.go index 75231098..05d19fb3 100644 --- a/go/log/handler.go +++ b/go/log/handler.go @@ -2,9 +2,14 @@ package log import ( "context" + "fmt" + "io" "log/slog" "os" + "path" "runtime" + "sync" + "time" "golang.org/x/exp/slices" ) @@ -15,7 +20,7 @@ type gameLogHandler struct { } func NewGameLogHandler(files []string, opts *slog.HandlerOptions) *gameLogHandler { - return &gameLogHandler{h: slog.NewTextHandler(os.Stdout, opts), files: files} + return &gameLogHandler{h: newTextHandler(os.Stdout, opts), files: files} } func (h *gameLogHandler) Enabled(ctx context.Context, level slog.Level) bool { @@ -23,10 +28,13 @@ func (h *gameLogHandler) Enabled(ctx context.Context, level slog.Level) bool { } func (h *gameLogHandler) Handle(ctx context.Context, r slog.Record) error { - frames := runtime.CallersFrames([]uintptr{r.PC}) - frame, _ := frames.Next() - if slices.Contains(h.files, frame.File) { - return nil + if pc, file, _, ok := runtime.Caller(4); ok { + basename := path.Base(file) + if len(h.files) > 0 && !slices.Contains(h.files, basename) { + return nil + } + + r.PC = pc } return h.h.Handle(ctx, r) @@ -39,3 +47,101 @@ func (h *gameLogHandler) WithAttrs(attrs []slog.Attr) slog.Handler { func (h *gameLogHandler) WithGroup(name string) slog.Handler { return &gameLogHandler{h: h.h.WithGroup(name), files: h.files} } + +type TextHandler struct { + opts *slog.HandlerOptions + // TODO: state for WithGroup and WithAttrs + mu *sync.Mutex + out io.Writer +} + +func newTextHandler(out io.Writer, opts *slog.HandlerOptions) *TextHandler { + h := &TextHandler{out: out, mu: &sync.Mutex{}} + if opts != nil { + h.opts = opts + } + if h.opts.Level == nil { + h.opts.Level = slog.LevelInfo + } + return h +} + +func (h *TextHandler) Enabled(ctx context.Context, level slog.Level) bool { + return level >= h.opts.Level.Level() +} + +func (h *TextHandler) WithGroup(name string) slog.Handler { + // TODO: implement. + return h +} + +func (h *TextHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + // TODO: implement. + return h +} + +func (h *TextHandler) Handle(ctx context.Context, r slog.Record) error { + buf := make([]byte, 0, 1024) + if !r.Time.IsZero() { + buf = h.appendAttr(buf, slog.Time(slog.TimeKey, r.Time)) + } + + if r.Level == 99 { + buf = fmt.Append(buf, "GAME ") + } else { + buf = fmt.Appendf(buf, "%s ", r.Level) + } + + fs := runtime.CallersFrames([]uintptr{r.PC}) + f, _ := fs.Next() + basename := path.Base(f.File) + buf = fmt.Appendf(buf, "%s:%d ", basename, f.Line) + + buf = h.appendAttr(buf, slog.String(slog.MessageKey, r.Message)) + r.Attrs(func(a slog.Attr) bool { + buf = h.appendAttr(buf, a) + return true + }) + + // Trim last whitespace and add newline + buf = fmt.Append(buf[:len(buf)-1], "\n") + + h.mu.Lock() + defer h.mu.Unlock() + _, err := h.out.Write(buf) + return err +} + +func (h *TextHandler) appendAttr(buf []byte, a slog.Attr) []byte { + // Resolve the Attr's value before doing anything else. + a.Value = a.Value.Resolve() + // Ignore empty Attrs. + if a.Equal(slog.Attr{}) { + return buf + } + switch a.Value.Kind() { + case slog.KindTime: + buf = fmt.Appendf(buf, "%s", a.Value.Time().Format(time.TimeOnly)) + case slog.KindGroup: + attrs := a.Value.Group() + // Ignore empty groups. + if len(attrs) == 0 { + return buf + } + // If the key is non-empty, write it out and indent the rest of the attrs. + // Otherwise, inline the attrs. + if a.Key != "" { + buf = fmt.Appendf(buf, "%s: ", a.Key) + } + buf = fmt.Append(buf, "[", a.Key) + for _, ga := range attrs { + buf = h.appendAttr(buf, ga) + buf = fmt.Append(buf, ", ", a.Key) + } + buf = fmt.Append(buf[:len(buf)-2], "]", a.Key) + default: + buf = fmt.Appendf(buf, "%s", a.Value) + } + buf = fmt.Appendf(buf, "%*s", 1, "") + return buf +} diff --git a/go/log/log.go b/go/log/log.go index dbe3af66..b9262675 100644 --- a/go/log/log.go +++ b/go/log/log.go @@ -6,6 +6,7 @@ import ( "log" "log/slog" "os" + "strconv" ) var GameLogLevel slog.Level = 99 @@ -21,9 +22,13 @@ func RegisterGameRecordHandler(handler func(any)) { } func init() { - Logger = slog.New(slog.NewTextHandler( - os.Stdout, - &slog.HandlerOptions{AddSource: true})) + logLevel := 0 + if logLevelVar, found := os.LookupEnv("LOGLEVEL"); found { + if envLogLevel, err := strconv.Atoi(logLevelVar); err == nil { + logLevel = envLogLevel + } + } + Logger = slog.New(NewGameLogHandler([]string{}, &slog.HandlerOptions{Level: slog.Level(logLevel)})) slog.SetDefault(Logger) } -- cgit v1.2.3