From 9116b0f907b2536dcd9aea12a6d7e2db513eb6b1 Mon Sep 17 00:00:00 2001 From: nxtcoder17 Date: Sat, 19 Oct 2024 21:59:59 +0530 Subject: [PATCH] feat: themes support for logger - migrated to `github.com/phuslu/log` from `github.com/charmbracelet/log`, not much of a reason just for trying out custom formatting --- pkg/logging/logger.go | 123 +++++++++++++++++++++++++----------------- pkg/logging/theme.go | 99 ++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+), 49 deletions(-) create mode 100644 pkg/logging/theme.go diff --git a/pkg/logging/logger.go b/pkg/logging/logger.go index 8b2453d..bf33044 100644 --- a/pkg/logging/logger.go +++ b/pkg/logging/logger.go @@ -1,81 +1,106 @@ package logging import ( + "fmt" "io" "log/slog" "os" - "github.com/charmbracelet/lipgloss" - "github.com/charmbracelet/log" + "github.com/phuslu/log" ) -type SlogOptions struct { - Writer io.Writer +type Options struct { Prefix string + Writer io.Writer + + Theme *Theme ShowTimestamp bool ShowCaller bool ShowDebugLogs bool + ShowLogLevel bool SetAsDefaultLogger bool -} -func NewSlogLogger(opts SlogOptions) *slog.Logger { - // INFO: force colored output, otherwise honor the env-var `CLICOLOR_FORCE` - if _, ok := os.LookupEnv("CLICOLOR_FORCE"); !ok { - os.Setenv("CLICOLOR_FORCE", "1") - } + // PickPrefix func() string + SlogKeyAsPrefix string + // constants + keyValueSeparator string +} + +func (opts Options) WithDefaults() Options { if opts.Writer == nil { opts.Writer = os.Stderr } - level := log.InfoLevel - if opts.ShowDebugLogs { - level = log.DebugLevel + if opts.Theme == nil { + opts.Theme = DefaultTheme() } - logger := log.NewWithOptions(opts.Writer, log.Options{ - ReportCaller: opts.ShowCaller, - ReportTimestamp: opts.ShowTimestamp, - Prefix: opts.Prefix, - Level: level, - }) - - styles := log.DefaultStyles() - // styles.Caller = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Dark: "#5b717f", Light: "#36cbfa"}).Faint(true) - styles.Caller = lipgloss.NewStyle().Foreground(lipgloss.Color("#878a8a")) - - styles.Levels[log.ErrorLevel] = lipgloss.NewStyle(). - SetString("ERROR"). - Padding(0, 1, 0, 1). - // Background(lipgloss.Color("204")). - Foreground(lipgloss.Color("202")) - - styles.Levels[log.DebugLevel] = styles.Levels[log.DebugLevel].Foreground(lipgloss.Color("#5b717f")) - - styles.Levels[log.InfoLevel] = styles.Levels[log.InfoLevel].Foreground(lipgloss.Color("#36cbfa")) - - // BUG: due to a bug in termenv, adaptive colors don't work within tmux - // it always selects the dark variant - - // styles.Levels[log.InfoLevel] = styles.Levels[log.InfoLevel].Foreground(lipgloss.AdaptiveColor{ - // Light: string(lipgloss.Color("#36cbfa")), - // Dark: string(lipgloss.Color("#608798")), - // }) + if opts.keyValueSeparator == "" { + opts.keyValueSeparator = "=" + } - styles.Key = lipgloss.NewStyle().Foreground(lipgloss.Color("#36cbfa")).Bold(true) + return opts +} - logger.SetStyles(styles) +func New(opts Options) *slog.Logger { + opts = opts.WithDefaults() - // output := termenv.NewOutput(os.Stdout, termenv.WithProfile(termenv.TrueColor)) - // logger.Info("theme", "fg", output.ForegroundColor(), "bg", output.BackgroundColor(), "has-dark", output.HasDarkBackground()) + writePrefix := func(w io.Writer, args *log.FormatterArgs) { + if opts.Prefix != "" { + fmt.Fprintf(w, "%s ", opts.Theme.TaskPrefixStyle.Render(opts.Prefix)) + } - l := slog.New(logger) + for i := range args.KeyValues { + if args.KeyValues[i].Key == opts.SlogKeyAsPrefix { + fmt.Fprintf(w, "%s ", opts.Theme.LogLevelStyles[ParseLogLevel(args.Level)].Render(fmt.Sprintf("[%s]", args.KeyValues[i].Value))) + break + } + } + } + l := log.Logger{ + Level: func() log.Level { + if opts.ShowDebugLogs { + return log.DebugLevel + } + return log.InfoLevel + }(), + Caller: func() int { + if opts.ShowCaller { + return 1 + } + return 0 + }(), + // Context: []byte{}, + Writer: &log.ConsoleWriter{ + ColorOutput: false, + QuoteString: true, + EndWithMessage: true, + Formatter: func(w io.Writer, args *log.FormatterArgs) (int, error) { + writePrefix(w, args) + + if opts.ShowLogLevel { + fmt.Fprintf(w, "%s ", opts.Theme.LogLevelStyles[ParseLogLevel(args.Level)].Render(args.Level)) + } + + fmt.Fprint(w, opts.Theme.MessageStyle.Render(args.Message)) + for i := range args.KeyValues { + if args.KeyValues[i].Key == opts.SlogKeyAsPrefix { + continue + } + fmt.Fprintf(w, " %s%s%v", opts.Theme.SlogKeyStyle.Render(args.KeyValues[i].Key), opts.Theme.SlogKeyStyle.Faint(true).Render(opts.keyValueSeparator), opts.Theme.MessageStyle.Render(args.KeyValues[i].Value)) + } + + return fmt.Fprintf(w, "\n") + }, + }, + } + sl := l.Slog() if opts.SetAsDefaultLogger { - slog.SetDefault(l) + slog.SetDefault(sl) } - - return l + return sl } diff --git a/pkg/logging/theme.go b/pkg/logging/theme.go new file mode 100644 index 0000000..efdd2d3 --- /dev/null +++ b/pkg/logging/theme.go @@ -0,0 +1,99 @@ +package logging + +import ( + "os" + + "github.com/charmbracelet/lipgloss" +) + +type LogLevelStyle int + +const ( + LogLevelTrace LogLevelStyle = 0 + LogLevelDebug LogLevelStyle = 1 + LogLevelInfo LogLevelStyle = 2 + LogLevelWarn LogLevelStyle = 3 + LogLevelError LogLevelStyle = 4 + + LogLevelUnknown LogLevelStyle = 5 +) + +func ParseLogLevel(level string) int { + switch level { + case "trace": + return 0 + case "debug": + return 1 + case "info": + return 2 + case "warn": + return 3 + case "error": + return 4 + default: + return 5 + } +} + +type Theme struct { + TaskPrefixStyle lipgloss.Style + MessageStyle lipgloss.Style + SlogKeyStyle lipgloss.Style + + // Styles are in order, TRACE,DEBUG,INFO,WARN,ERROR + LogLevelStyles [6]lipgloss.Style + + LogLevelDebugStyle lipgloss.Style + LogLevelInfoStyle lipgloss.Style + LogLevelErrorStyle lipgloss.Style +} + +func DefaultTheme() *Theme { + if os.Getenv("RUNFILE_THEME") == "light" { + fg := lipgloss.Color("#4a84ad") + bg := lipgloss.Color("#f4f7fb") + + style := lipgloss.NewStyle().Background(bg).Foreground(fg) + + return &Theme{ + TaskPrefixStyle: style, + SlogKeyStyle: style.Bold(true), + MessageStyle: style.UnsetBackground().Foreground(lipgloss.Color("#6d787d")), + LogLevelStyles: [6]lipgloss.Style{ + style.Foreground(lipgloss.Color("#bdbfbe")).Faint(true), // TRACE + style.Foreground(lipgloss.Color("#bdbfbe")).Faint(true), // DEBUG + style.UnsetBackground().Foreground(lipgloss.Color("#099dd6")).Faint(true), // INFO + style.UnsetBackground().Foreground(lipgloss.Color("#d6d609")).Faint(true), // WARN + style.UnsetBackground().Foreground(lipgloss.Color("#c76975")), // ERROR + style.UnsetBackground().Foreground(lipgloss.Color("#d6d609")).Bold(true), // UNKNOWN + }, + } + } + + fg := lipgloss.Color("#9addfc") + // bg := lipgloss.Color("#172830") + + style := lipgloss.NewStyle().Foreground(fg) + + return &Theme{ + TaskPrefixStyle: style.Faint(true), + // SlogKeyStyle: style.UnsetBackground().Foreground(lipgloss.Color("#8f8ad4")), + // SlogKeyStyle: style.UnsetBackground().Foreground(lipgloss.Color("#75b9ba")), + // MessageStyle: style.UnsetBackground().Foreground(lipgloss.Color("#e6e8ed")).Faint(true), + // MessageStyle: style.UnsetBackground().Foreground(lipgloss.Color("#e6e8ed")).Faint(true), + MessageStyle: style.UnsetBackground().Foreground(lipgloss.Color("#bdbfbe")), + // SlogKeyStyle: style.UnsetBackground().Foreground(lipgloss.Color("#2fbaf5")), + SlogKeyStyle: style.UnsetBackground().Foreground(lipgloss.Color("#85d3d4")), + LogLevelDebugStyle: style.Foreground(lipgloss.Color("#bdbfbe")).Faint(true), + LogLevelInfoStyle: style.Foreground(lipgloss.Color("#099dd6")), + LogLevelErrorStyle: style.Foreground(lipgloss.Color("#c76975")), + LogLevelStyles: [6]lipgloss.Style{ + style.Foreground(lipgloss.Color("#bdbfbe")).Faint(true), // TRACE + style.Foreground(lipgloss.Color("#bdbfbe")).Faint(true), // DEBUG + style.Foreground(lipgloss.Color("#099dd6")), // INFO + style.UnsetBackground().Foreground(lipgloss.Color("#d6d609")).Faint(true), // WARN + style.Foreground(lipgloss.Color("#c76975")), // ERROR + style.UnsetBackground().Foreground(lipgloss.Color("#d6d609")).Bold(true), // UNKNOWN + }, + } +}