mirror of
https://codeberg.org/scip/yadu.git
synced 2025-12-16 20:21:00 +01:00
initial commit
This commit is contained in:
25
README.md
Normal file
25
README.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# YamlDumpHandler - a human readable yaml based slog.Handler
|
||||||
|
|
||||||
|
Example output:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
2024-01-18T02:57.41 CET INFO: info
|
||||||
|
enemy:
|
||||||
|
alive: true
|
||||||
|
health: 10
|
||||||
|
name: Bodo
|
||||||
|
ammo:
|
||||||
|
- forweapon: Railgun
|
||||||
|
impact: 100
|
||||||
|
cost: 100000
|
||||||
|
range: 400
|
||||||
|
spawn: 199
|
||||||
|
2024-01-18T02:57.41 CET INFO: connecting
|
||||||
|
enemies: 100
|
||||||
|
players: 2
|
||||||
|
world: 600x800
|
||||||
|
2024-01-18T02:57.41 CET DEBUG: debug text
|
||||||
|
2024-01-18T02:57.41 CET ERROR: error
|
||||||
|
```
|
||||||
|
|
||||||
|
See `example.go` for usage.
|
||||||
50
example.go
Normal file
50
example.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type body string
|
||||||
|
|
||||||
|
type Ammo struct {
|
||||||
|
Forweapon string
|
||||||
|
Impact int
|
||||||
|
Cost int
|
||||||
|
Range int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Enemy struct {
|
||||||
|
Alive bool
|
||||||
|
Health int
|
||||||
|
Name string
|
||||||
|
Body body `yaml:"-"`
|
||||||
|
Ammo []Ammo
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeTime(_ []string, a slog.Attr) slog.Attr {
|
||||||
|
if a.Key == slog.TimeKey {
|
||||||
|
return slog.Attr{}
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
opts := &YamlDumpHandlerOptions{
|
||||||
|
Level: slog.LevelDebug,
|
||||||
|
//ReplaceAttr: removeTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := slog.New(NewYamlDumpHandler(os.Stdout, opts))
|
||||||
|
|
||||||
|
slog.SetDefault(logger)
|
||||||
|
|
||||||
|
e := &Enemy{Alive: true, Health: 10, Name: "Bodo", Body: "body\nbody\n",
|
||||||
|
Ammo: []Ammo{{Forweapon: "Railgun", Range: 400, Impact: 100, Cost: 100000}},
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("info", "enemy", e, "spawn", 199)
|
||||||
|
slog.Info("connecting", "enemies", 100, "players", 2, "world", "600x800")
|
||||||
|
slog.Debug("debug text")
|
||||||
|
slog.Error("error")
|
||||||
|
}
|
||||||
182
handler.go
Normal file
182
handler.go
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"log/slog"
|
||||||
|
"regexp"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultTimeFormat = "2006-01-02T03:04.05 MST"
|
||||||
|
|
||||||
|
type YamlDumpHandler struct {
|
||||||
|
l *log.Logger
|
||||||
|
writer io.Writer
|
||||||
|
mu *sync.Mutex
|
||||||
|
level slog.Leveler
|
||||||
|
groups []string
|
||||||
|
attrs string
|
||||||
|
timeFormat string
|
||||||
|
replaceAttr func(groups []string, a slog.Attr) slog.Attr
|
||||||
|
addSource bool
|
||||||
|
indenter *regexp.Regexp
|
||||||
|
}
|
||||||
|
|
||||||
|
type YamlDumpHandlerOptions struct {
|
||||||
|
Level slog.Leveler
|
||||||
|
ReplaceAttr func(groups []string, a slog.Attr) slog.Attr
|
||||||
|
TimeFormat string
|
||||||
|
AddSource bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *YamlDumpHandler) Handle(ctx context.Context, r slog.Record) error {
|
||||||
|
level := r.Level.String() + ":"
|
||||||
|
|
||||||
|
switch r.Level {
|
||||||
|
case slog.LevelDebug:
|
||||||
|
level = color.MagentaString(level)
|
||||||
|
case slog.LevelInfo:
|
||||||
|
level = color.BlueString(level)
|
||||||
|
case slog.LevelWarn:
|
||||||
|
level = color.YellowString(level)
|
||||||
|
case slog.LevelError:
|
||||||
|
level = color.RedString(level)
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := make(map[string]interface{}, r.NumAttrs())
|
||||||
|
r.Attrs(func(a slog.Attr) bool {
|
||||||
|
fields[a.Key] = a.Value.Any()
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
tree := h.attrs
|
||||||
|
|
||||||
|
if len(fields) > 0 {
|
||||||
|
bytetree, err := yaml.Marshal(&fields)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tree = h.Postprocess(bytetree)
|
||||||
|
}
|
||||||
|
|
||||||
|
timeStr := ""
|
||||||
|
timeAttr := slog.Time(slog.TimeKey, r.Time)
|
||||||
|
|
||||||
|
if h.replaceAttr != nil {
|
||||||
|
timeAttr = h.replaceAttr(nil, timeAttr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.Time.IsZero() && !timeAttr.Equal(slog.Attr{}) {
|
||||||
|
timeStr = r.Time.Format(h.timeFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := color.CyanString(r.Message)
|
||||||
|
|
||||||
|
buf := bytes.Buffer{}
|
||||||
|
|
||||||
|
if len(timeStr) > 0 {
|
||||||
|
buf.WriteString(timeStr)
|
||||||
|
buf.WriteString(" ")
|
||||||
|
}
|
||||||
|
buf.WriteString(level)
|
||||||
|
buf.WriteString(" ")
|
||||||
|
buf.WriteString(msg)
|
||||||
|
buf.WriteString(" ")
|
||||||
|
buf.WriteString(color.WhiteString(tree))
|
||||||
|
buf.WriteString("\n")
|
||||||
|
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
_, err := h.writer.Write(buf.Bytes())
|
||||||
|
|
||||||
|
// h.l.Println(timeStr, level, msg, color.WhiteString(tree))
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *YamlDumpHandler) Postprocess(yamlstr []byte) string {
|
||||||
|
return "\n " + strings.TrimSpace(h.indenter.ReplaceAllString(string(yamlstr), " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewYamlDumpHandler(out io.Writer, opts *YamlDumpHandlerOptions) *YamlDumpHandler {
|
||||||
|
if opts == nil {
|
||||||
|
opts = &YamlDumpHandlerOptions{}
|
||||||
|
}
|
||||||
|
|
||||||
|
h := &YamlDumpHandler{
|
||||||
|
writer: out,
|
||||||
|
mu: &sync.Mutex{},
|
||||||
|
level: opts.Level,
|
||||||
|
timeFormat: opts.TimeFormat,
|
||||||
|
replaceAttr: opts.ReplaceAttr,
|
||||||
|
addSource: opts.AddSource,
|
||||||
|
indenter: regexp.MustCompile(`(?m)^`),
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.timeFormat == "" {
|
||||||
|
h.timeFormat = defaultTimeFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enabled indicates whether the receiver logs at the given level.
|
||||||
|
func (h *YamlDumpHandler) Enabled(_ context.Context, l slog.Level) bool {
|
||||||
|
return l >= h.level.Level()
|
||||||
|
}
|
||||||
|
|
||||||
|
// attributes plus attrs.
|
||||||
|
func (h *YamlDumpHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||||
|
if len(attrs) == 0 {
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := make(map[string]interface{}, len(attrs))
|
||||||
|
for _, a := range attrs {
|
||||||
|
fields[a.Key] = a.Value.Any()
|
||||||
|
}
|
||||||
|
|
||||||
|
bytetree, err := yaml.Marshal(&fields)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 := h.clone()
|
||||||
|
|
||||||
|
h2.attrs += string(bytetree)
|
||||||
|
return h2
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithGroup returns a new [log/slog.Handler] with name appended to the
|
||||||
|
// receiver's groups.
|
||||||
|
func (h *YamlDumpHandler) WithGroup(name string) slog.Handler {
|
||||||
|
if name == "" {
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
h2 := h.clone()
|
||||||
|
h2.groups = append(h2.groups, name)
|
||||||
|
return h2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *YamlDumpHandler) clone() *YamlDumpHandler {
|
||||||
|
return &YamlDumpHandler{
|
||||||
|
writer: h.writer,
|
||||||
|
mu: h.mu,
|
||||||
|
level: h.level,
|
||||||
|
groups: slices.Clip(h.groups),
|
||||||
|
attrs: h.attrs,
|
||||||
|
timeFormat: h.timeFormat,
|
||||||
|
replaceAttr: h.replaceAttr,
|
||||||
|
addSource: h.addSource,
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user