loggers/pretty.go

136 lines
3.0 KiB
Go
Raw Normal View History

2024-07-20 23:27:46 +00:00
package loggers
import (
"context"
"io"
"log/slog"
"sync"
"time"
"go.elara.ws/loggers/internal/buffer"
"go.elara.ws/loggers/internal/colors"
)
// Pretty is an slog handler with a human-readable output
type Pretty struct {
mtx *sync.Mutex
goa []groupOrAttr
// Colorize indicates whether colors will be used
// in log output.
Colorize bool
// Out is where log output will be written.
Out io.Writer
Options
}
// NewPretty creates and returns a new [Pretty] handler.
// If opts doesn't specify a time format, [time.Kitchen] will be used.
func NewPretty(wr io.Writer, opts Options) *Pretty {
if opts.TimeFormat == "" {
opts.TimeFormat = time.Kitchen
}
return &Pretty{
mtx: &sync.Mutex{},
Colorize: opts.ForceColors || isTerm(wr),
Out: wr,
Options: opts,
}
}
// Enabled reports whether the handler handles records at the given level.
// The handler ignores records whose level is lower.
func (p *Pretty) Enabled(_ context.Context, level slog.Level) bool {
return level >= p.Level
}
// WithGroup returns a new [Pretty] handler with the given group name.
func (p *Pretty) WithGroup(name string) slog.Handler {
return &Pretty{
mtx: p.mtx,
Out: p.Out,
Options: p.Options,
Colorize: p.Colorize,
goa: append(p.goa, groupOrAttr{group: name}),
}
}
// WithAttrs returns a new [Pretty] handler whose attributes
// consists of p's attributes followed by attrs.
func (p *Pretty) WithAttrs(attrs []slog.Attr) slog.Handler {
goa := make([]groupOrAttr, len(attrs))
for i, attr := range attrs {
goa[i] = groupOrAttr{attr: attr}
}
return &Pretty{
mtx: p.mtx,
Out: p.Out,
Options: p.Options,
Colorize: p.Colorize,
goa: append(p.goa, goa...),
}
}
// Handle formats the given [log/slog.Record] as a human-readable string on a single line.
func (p *Pretty) Handle(_ context.Context, rec slog.Record) error {
buf := buffer.Alloc()
defer buffer.Free(buf)
colors.WriteString(p.Colorize, buf, colors.Grey, rec.Time.Format(p.TimeFormat))
buf.WriteByte(' ')
switch rec.Level {
case slog.LevelInfo:
colors.WriteString(p.Colorize, buf, colors.Green, "INF")
case slog.LevelError:
colors.WriteString(p.Colorize, buf, colors.Red, "ERR")
case slog.LevelWarn:
colors.WriteString(p.Colorize, buf, colors.Yellow, "WRN")
case slog.LevelDebug:
colors.WriteString(p.Colorize, buf, colors.Magenta, "DBG")
}
buf.WriteByte(' ')
buf.WriteString(rec.Message)
lastGroup := ""
for _, goa := range p.goa {
switch {
case goa.group == "":
if lastGroup != "" {
goa.attr.Key = lastGroup + goa.attr.Key
}
buf.WriteByte(' ')
writeAttr(p.Colorize, buf, goa.attr)
default:
lastGroup += goa.group + "."
}
}
if rec.NumAttrs() > 0 {
rec.Attrs(func(attr slog.Attr) bool {
if lastGroup != "" {
attr.Key = lastGroup + attr.Key
}
buf.WriteByte(' ')
writeAttr(p.Colorize, buf, attr)
return true
})
}
if p.ShowCaller {
writeCaller(p.Colorize, buf, rec.PC)
}
buf.WriteByte('\n')
p.mtx.Lock()
defer p.mtx.Unlock()
_, err := buf.WriteTo(p.Out)
return err
}