diff options
Diffstat (limited to 'go/log/handler.go')
| -rw-r--r-- | go/log/handler.go | 116 |
1 files changed, 111 insertions, 5 deletions
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 +} |
