blob: bbe4a02f5410ec8a14ce0dd3fe3ac202d0e7f2a5 [file] [log] [blame]
// 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)
}
}
}