package logging import ( "context" "fmt" "io" "log/slog" "strings" "sync" "time" ) func Setup(format string, w io.Writer, level slog.Level) { var handler slog.Handler switch format { case "json": handler = slog.NewJSONHandler(w, &slog.HandlerOptions{Level: level}) default: handler = newHumanHandler(w, level) } slog.SetDefault(slog.New(handler)) } // humanHandler emits " : k=v k=v\n". type humanHandler struct { mu *sync.Mutex w io.Writer level slog.Level attrs []slog.Attr group string } func newHumanHandler(w io.Writer, level slog.Level) *humanHandler { return &humanHandler{mu: &sync.Mutex{}, w: w, level: level} } func (h *humanHandler) Enabled(_ context.Context, l slog.Level) bool { return l >= h.level } func (h *humanHandler) Handle(_ context.Context, r slog.Record) error { var b strings.Builder b.WriteString(r.Time.UTC().Format(time.RFC3339)) b.WriteByte(' ') b.WriteString(shortLevel(r.Level)) b.WriteString(": ") b.WriteString(r.Message) for _, a := range h.attrs { writeAttr(&b, h.group, a) } r.Attrs(func(a slog.Attr) bool { writeAttr(&b, h.group, a) return true }) b.WriteByte('\n') h.mu.Lock() defer h.mu.Unlock() _, err := io.WriteString(h.w, b.String()) return err } func (h *humanHandler) WithAttrs(attrs []slog.Attr) slog.Handler { out := *h out.attrs = append(append([]slog.Attr{}, h.attrs...), attrs...) return &out } func (h *humanHandler) WithGroup(name string) slog.Handler { out := *h if h.group == "" { out.group = name } else { out.group = h.group + "." + name } return &out } func shortLevel(l slog.Level) string { switch { case l < slog.LevelInfo: return "DBG" case l < slog.LevelWarn: return "INF" case l < slog.LevelError: return "WRN" default: return "ERR" } } func writeAttr(b *strings.Builder, group string, a slog.Attr) { if a.Equal(slog.Attr{}) { return } b.WriteByte(' ') if group != "" { b.WriteString(group) b.WriteByte('.') } b.WriteString(a.Key) b.WriteByte('=') v := a.Value.String() if strings.ContainsAny(v, " \t\"") { fmt.Fprintf(b, "%q", v) } else { b.WriteString(v) } }