| // Copyright 2023 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package log |
| |
| import ( |
| "bytes" |
| "context" |
| "fmt" |
| "io" |
| "sync" |
| |
| "github.com/jba/slog/withsupport" |
| "golang.org/x/exp/slog" |
| ) |
| |
| // LineHandler is a slog.Handler that writes log events one per line |
| // in an easy-to-read format: |
| // |
| // time level message label1=value1 label2=value2 ... |
| type LineHandler struct { |
| mu sync.Mutex |
| w io.Writer |
| gora *withsupport.GroupOrAttrs |
| } |
| |
| func NewLineHandler(w io.Writer) *LineHandler { |
| return &LineHandler{w: w} |
| } |
| |
| func (h *LineHandler) Enabled(ctx context.Context, level slog.Level) bool { |
| return true |
| } |
| |
| func (h *LineHandler) WithGroup(name string) slog.Handler { |
| return &LineHandler{w: h.w, gora: h.gora.WithGroup(name)} |
| } |
| func (h *LineHandler) WithAttrs(as []slog.Attr) slog.Handler { |
| return &LineHandler{w: h.w, gora: h.gora.WithAttrs(as)} |
| } |
| |
| func (h *LineHandler) Handle(ctx context.Context, r slog.Record) error { |
| var buf bytes.Buffer |
| if !r.Time.IsZero() { |
| fmt.Fprintf(&buf, "%s ", r.Time.Format("2006/01/02 15:04:05")) |
| } |
| fmt.Fprintf(&buf, "%-5s %s", r.Level, r.Message) |
| |
| prefix := "" |
| for _, ga := range h.gora.Collect() { |
| if ga.Group != "" { |
| prefix += ga.Group + "." |
| } else { |
| for _, a := range ga.Attrs { |
| writeAttr(&buf, prefix, a) |
| } |
| } |
| } |
| r.Attrs(func(a slog.Attr) { writeAttr(&buf, prefix, a) }) |
| buf.WriteByte('\n') |
| h.mu.Lock() |
| defer h.mu.Unlock() |
| _, err := h.w.Write(buf.Bytes()) |
| return err |
| } |
| |
| func writeAttr(w io.Writer, prefix string, a slog.Attr) { |
| if a.Value.Kind() == slog.KindGroup { |
| if a.Key != "" { |
| prefix = a.Key + "." |
| } |
| for _, g := range a.Value.Group() { |
| writeAttr(w, prefix, g) |
| } |
| } else if a.Key != "" { |
| fmt.Fprintf(w, " %s%s=", prefix, a.Key) |
| if a.Value.Kind() == slog.KindString { |
| fmt.Fprintf(w, "%q", a.Value) |
| } else { |
| fmt.Fprintf(w, "%v", a.Value) |
| } |
| } |
| } |