package logging import ( "context" "fmt" "io" "log/slog" "slices" "sync" ) // ANSI color codes const ( dim = "\033[2m" red = "\033[31m" yellow = "\033[33m" cyan = "\033[36m" reset = "\033[0m" ) // ConsoleHandler formats log output for human readability on terminals. // It produces compact, color-coded lines: // // 20:15:54 INF daemon started addr=:5055 // 20:15:54 ERR backup failed component=backup error="connection refused" type ConsoleHandler struct { w io.Writer level slog.Leveler attrs []slog.Attr mu *sync.Mutex } // NewConsoleHandler creates a handler that writes human-friendly colored logs. func NewConsoleHandler(w io.Writer, opts *slog.HandlerOptions) *ConsoleHandler { level := slog.LevelInfo if opts != nil && opts.Level != nil { level = opts.Level.Level() } return &ConsoleHandler{ w: w, level: level, mu: &sync.Mutex{}, } } func (h *ConsoleHandler) Enabled(_ context.Context, level slog.Level) bool { return level >= h.level.Level() } func (h *ConsoleHandler) Handle(_ context.Context, r slog.Record) error { // Time buf := []byte(dim + r.Time.Format("15:04:05") + reset + " ") // Level badge switch { case r.Level >= slog.LevelError: buf = append(buf, red+"ERR"+reset+" "...) case r.Level >= slog.LevelWarn: buf = append(buf, yellow+"WRN"+reset+" "...) default: buf = append(buf, cyan+"INF"+reset+" "...) } // Message buf = append(buf, r.Message...) // Pre-set attrs (from slog.With) for _, a := range h.attrs { buf = appendAttr(buf, a) } // Inline attrs r.Attrs(func(a slog.Attr) bool { buf = appendAttr(buf, a) return true }) buf = append(buf, '\n') h.mu.Lock() defer h.mu.Unlock() _, err := h.w.Write(buf) return err } func (h *ConsoleHandler) WithAttrs(attrs []slog.Attr) slog.Handler { return &ConsoleHandler{ w: h.w, level: h.level, attrs: append(slices.Clone(h.attrs), attrs...), mu: h.mu, } } func (h *ConsoleHandler) WithGroup(name string) slog.Handler { // Groups are rare in this codebase; treat as a prefixed attr set return &ConsoleHandler{ w: h.w, level: h.level, attrs: append(slices.Clone(h.attrs), slog.String("group", name)), mu: h.mu, } } func appendAttr(buf []byte, a slog.Attr) []byte { if a.Equal(slog.Attr{}) { return buf } v := a.Value.Resolve() buf = append(buf, ' ') buf = append(buf, dim...) buf = append(buf, a.Key...) buf = append(buf, '=') buf = append(buf, reset...) s := v.String() if needsQuote(s) { buf = append(buf, fmt.Sprintf("%q", s)...) } else { buf = append(buf, s...) } return buf } func needsQuote(s string) bool { if s == "" { return true } for _, c := range s { if c <= ' ' || c == '"' || c == '\\' { return true } } return false } // Verify interface compliance at compile time. var _ slog.Handler = (*ConsoleHandler)(nil)