package handler import ( "context" "fmt" "io" "log/slog" "sync" "github.com/databricks/cli/libs/log" ) // friendlyHandler implements a custom [slog.Handler] that writes // human readable (and colorized) log lines to a terminal. // // The implementation is based on the guide at: // https://github.com/golang/example/blob/master/slog-handler-guide/README.md type friendlyHandler struct { opts Options mu *sync.Mutex out io.Writer // List of colors to use for formatting. ttyColors // Cache (colorized) level strings. levelTrace string levelDebug string levelInfo string levelWarn string levelError string } func NewFriendlyHandler(out io.Writer, opts *Options) slog.Handler { h := &friendlyHandler{out: out, mu: &sync.Mutex{}} if opts != nil { h.opts = *opts } if h.opts.Level == nil { h.opts.Level = slog.LevelInfo } h.ttyColors = newColors(opts.Color) // Cache (colorized) level strings. // The colors to use for each level are configured in `colors.go`. h.levelTrace = h.sprintf(ttyColorLevelTrace, "%s", "Trace: ") h.levelDebug = h.sprintf(ttyColorLevelDebug, "%s", "Debug: ") h.levelInfo = h.sprintf(ttyColorLevelInfo, "%s", "Info: ") h.levelWarn = h.sprintf(ttyColorLevelWarn, "%s", "Warn: ") h.levelError = h.sprintf(ttyColorLevelError, "%s", "Error: ") return h } func (h *friendlyHandler) sprint(color ttyColor, args ...any) string { return h.ttyColors[color].Sprint(args...) } func (h *friendlyHandler) sprintf(color ttyColor, format string, args ...any) string { return h.ttyColors[color].Sprintf(format, args...) } func (h *friendlyHandler) coloredLevel(r slog.Record) string { switch r.Level { case log.LevelTrace: return h.levelTrace case log.LevelDebug: return h.levelDebug case log.LevelInfo: return h.levelInfo case log.LevelWarn: return h.levelWarn case log.LevelError: return h.levelError } return "" } // Enabled implements slog.Handler. func (h *friendlyHandler) Enabled(ctx context.Context, level slog.Level) bool { return level >= h.opts.Level.Level() } // Handle implements slog.Handler. func (h *friendlyHandler) Handle(ctx context.Context, r slog.Record) error { out := fmt.Sprintf("%s%s\n", h.coloredLevel(r), h.sprint(ttyColorMessage, r.Message)) h.mu.Lock() defer h.mu.Unlock() _, err := h.out.Write([]byte(out)) return err } // WithGroup implements slog.Handler. func (h *friendlyHandler) WithGroup(name string) slog.Handler { return h } // WithAttrs implements slog.Handler. func (h *friendlyHandler) WithAttrs(attrs []slog.Attr) slog.Handler { return h }