Files
scriptos 1e51010f10
Build Test Docker Image / docker-test (pull_request) Successful in 1m39s
Run Tests / test (pull_request) Successful in 5m4s
Log-Format auf [INFO]/[WARN]/[ERROR]/[DEBUG]-Tags umgestellt, Logging erweitert
2026-03-29 21:44:25 +02:00

116 lines
2.5 KiB
Go

package main
import (
"context"
"fmt"
"io"
"log/slog"
"strings"
"sync"
"time"
)
// bracketHandler ist ein slog.Handler, der Log-Nachrichten im Format
// "2006/01/02 15:04:05 [LEVEL] message key=value" ausgibt.
// Die Level-Tags [INFO], [WARN], [ERROR] und [DEBUG] ermöglichen eine
// schnelle visuelle Zuordnung in Container-Logs.
type bracketHandler struct {
level slog.Level
out io.Writer
mu *sync.Mutex
attrs []slog.Attr
}
func newBracketHandler(out io.Writer, level slog.Level) *bracketHandler {
return &bracketHandler{
level: level,
out: out,
mu: &sync.Mutex{},
}
}
func (h *bracketHandler) Enabled(_ context.Context, level slog.Level) bool {
return level >= h.level
}
func (h *bracketHandler) Handle(_ context.Context, r slog.Record) error {
var levelTag string
switch {
case r.Level >= slog.LevelError:
levelTag = "[ERROR]"
case r.Level >= slog.LevelWarn:
levelTag = "[WARN]"
case r.Level >= slog.LevelInfo:
levelTag = "[INFO]"
default:
levelTag = "[DEBUG]"
}
var b strings.Builder
b.WriteString(r.Time.Format("2006/01/02 15:04:05"))
b.WriteByte(' ')
b.WriteString(levelTag)
b.WriteByte(' ')
b.WriteString(r.Message)
writeAttr := func(a slog.Attr) bool {
a.Value = a.Value.Resolve()
if a.Equal(slog.Attr{}) {
return true
}
b.WriteByte(' ')
b.WriteString(a.Key)
b.WriteByte('=')
b.WriteString(formatAttrValue(a.Value))
return true
}
for _, a := range h.attrs {
writeAttr(a)
}
r.Attrs(writeAttr)
b.WriteByte('\n')
h.mu.Lock()
defer h.mu.Unlock()
_, err := h.out.Write([]byte(b.String()))
return err
}
func (h *bracketHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
newAttrs := make([]slog.Attr, len(h.attrs)+len(attrs))
copy(newAttrs, h.attrs)
copy(newAttrs[len(h.attrs):], attrs)
return &bracketHandler{
level: h.level,
out: h.out,
mu: h.mu,
attrs: newAttrs,
}
}
func (h *bracketHandler) WithGroup(_ string) slog.Handler {
// Gruppen werden für dieses Projekt nicht benötigt.
return h
}
// formatAttrValue formatiert einen slog.Value als String.
// String-Werte mit Leerzeichen oder Sonderzeichen werden in Anführungszeichen gesetzt.
func formatAttrValue(v slog.Value) string {
switch v.Kind() {
case slog.KindString:
s := v.String()
if s == "" || strings.ContainsAny(s, " \t\n\"\\") {
return fmt.Sprintf("%q", s)
}
return s
case slog.KindTime:
return v.Time().Format(time.RFC3339)
case slog.KindDuration:
return v.Duration().String()
default:
return fmt.Sprintf("%v", v.Any())
}
}