diff --git a/main/main.go b/main/main.go index fabfc8770ac6ddf70976629defbd38e844ce6b4f..f5df3700adc6c625aa91a2e668e60754528a04ac 100644 --- a/main/main.go +++ b/main/main.go @@ -11,11 +11,10 @@ import ( ) func main() { - slog.SetDefault(slog.New(slogor.NewHandler(os.Stderr, slogor.Options{ - TimeFormat: time.Stamp, - Level: slog.LevelDebug, - ShowSource: false, - }))) + slog.SetDefault(slog.New(slogor.NewHandler(os.Stderr, + slogor.SetTimeFormat(time.Stamp), + slogor.SetLevel(slog.LevelDebug), + ))) slog.Info("I'm an information message, everything's fine") slog.Warn("I'm a warning, that's ok.") @@ -23,28 +22,26 @@ func main() { slog.Debug("Useful debug message.") fmt.Println("") - slog.SetDefault(slog.New(slogor.NewHandler(os.Stderr, slogor.Options{ - TimeFormat: time.Kitchen, - Level: slog.LevelDebug, - ShowSource: false, - }))) + slog.SetDefault(slog.New(slogor.NewHandler(os.Stderr, + slogor.SetTimeFormat(time.Kitchen), + slogor.SetLevel(slog.LevelDebug), + ))) slog.Info("Example with kitchen time.") slog.Debug("Example with kitchen time.") fmt.Println("") - slog.SetDefault(slog.New(slogor.NewHandler(os.Stderr, slogor.Options{ - TimeFormat: time.RFC3339Nano, - Level: slog.LevelDebug, - ShowSource: true, - }))) + slog.SetDefault(slog.New(slogor.NewHandler(os.Stderr, + slogor.SetTimeFormat(time.RFC3339Nano), + slogor.SetLevel(slog.LevelDebug), + slogor.ShowSource(), + ))) slog.Info("Example with RFC 3339 time and source path") fmt.Println("") - slog.SetDefault(slog.New(slogor.NewHandler(os.Stderr, slogor.Options{ - TimeFormat: time.Stamp, - Level: slog.LevelDebug, - ShowSource: false, - }))) + slog.SetDefault(slog.New(slogor.NewHandler(os.Stderr, + slogor.SetTimeFormat(time.Stamp), + slogor.SetLevel(slog.LevelDebug), + ))) slog.Error("Error with args", slogor.Err(errors.New("i'm an error"))) slog.Warn("Warn with args", slog.Int("the_answer", 42)) diff --git a/option.go b/option.go new file mode 100644 index 0000000000000000000000000000000000000000..181a56f26237b67f68e2b5468eb3f21fb6a69a82 --- /dev/null +++ b/option.go @@ -0,0 +1,73 @@ +package slogor + +import "log/slog" + +type OptionFn func(*options) + +// Options defines the options for configuring the Handler. +type options struct { + // level is the minimum log level to handle. + level slog.Leveler + // timeFormat specifies the time format for log records. + // Empty string will remove the time in records. + timeFormat string + // showSource indicates whether to display the source of log records. + showSource bool + // addColorToBuf conditionally add color to the output buffer. + addColorToBuf func([]byte, string) []byte + // strLvl allow custom string to log level conversion. + strLvl map[slog.Level]string +} + +type MapOfLevel = map[slog.Level]string + +// Map for STD log level conversion. +var mapOfLevel = MapOfLevel{ + slog.LevelDebug: slog.LevelDebug.Level().String() + " ", + slog.LevelInfo: slog.LevelInfo.Level().String(), + slog.LevelWarn: slog.LevelWarn.Level().String() + " ", + slog.LevelError: slog.LevelError.Level().String(), +} + +// SetLevelStr set the handler "level to string" map. +// The default one is used if none specified. +func SetLevelStr(strLvl MapOfLevel) OptionFn { + return func(options *options) { options.strLvl = strLvl } +} + +// SetLevel set the minimum log level to handle. +// By default, level INFO is set. +func SetLevel(lvl slog.Level) OptionFn { return func(options *options) { options.level = lvl } } + +// SetTimeFormat specifies the time format for the log records. +// By default, nothing is reported. +func SetTimeFormat(fmt string) OptionFn { return func(options *options) { options.timeFormat = fmt } } + +// ShowSource indicates whether to display the source of the log records. +// By default, nothing is reported. +func ShowSource() OptionFn { return func(options *options) { options.showSource = true } } + +// DisableColor. +func DisableColor() OptionFn { + return func(options *options) { + options.addColorToBuf = func(buf []byte, color string) []byte { return buf } + } +} + +func getDefaultoption() *options { + return &options{ + strLvl: mapOfLevel, + level: slog.LevelInfo, + addColorToBuf: func(buf []byte, color string) []byte { + return append(buf, color...) + }, + } +} + +func (o *options) consumeFnOpt(fns ...OptionFn) *options { + for _, fn := range fns { + fn(o) + } + + return o +} diff --git a/slogor.go b/slogor.go index af448ae5aaca24a5dcd7a7c05ef1126d090e833a..46acab8ef560ecff55ae7b9c0bafcefecf6ff785 100644 --- a/slogor.go +++ b/slogor.go @@ -13,19 +13,6 @@ import ( "sync" ) -// Options defines the options for configuring the Handler. -type Options struct { - // Level is the minimum log level to handle. - Level slog.Leveler - // TimeFormat specifies the time format for log records. - // Empty string will remove the time in records. - TimeFormat string - // ShowSource indicates whether to display the source of log records. - ShowSource bool - // Disable colored output. - NoColor bool -} - type GroupOrAttrs struct { attr slog.Attr group string @@ -33,47 +20,38 @@ type GroupOrAttrs struct { // Handler is a slog.Handler that writes Records to an io.Writer as a sequence of colorful time, message, and pairs separated by spaces and followed by a newline. type Handler struct { - Writer io.Writer // Writer is the destination for the log records. - Mutex *sync.Mutex // Mutex for handling concurrent access to the handler. - Options Options // Options is the configuration for the log handler. - addColorToBuf func([]byte, string) []byte // addColorToBuf conditionally add color to the output buffer. - goa []GroupOrAttrs // goa for handling group or attributes. + Writer io.Writer // Writer is the destination for the log records. + Mutex *sync.Mutex // Mutex for handling concurrent access to the handler. + options options // Options is the configuration for the log handler. + goa []GroupOrAttrs // goa for handling group or attributes. } // NewHandler creates a Handler that writes to w with the provided options. -func NewHandler(writer io.Writer, options Options) *Handler { - fn := func(buf []byte, color string) []byte { - return append(buf, color...) - } - - if options.NoColor { - fn = func(buf []byte, color string) []byte { return buf } - } +func NewHandler(writer io.Writer, fns ...OptionFn) *Handler { + opt := getDefaultoption().consumeFnOpt(fns...) // Return a new Handler with the provided writer and options. return &Handler{ - Mutex: &sync.Mutex{}, - Writer: writer, - Options: options, - goa: []GroupOrAttrs{}, - addColorToBuf: fn, + Mutex: &sync.Mutex{}, + Writer: writer, + options: *opt, + goa: []GroupOrAttrs{}, } } // Enabled reports whether the handler handles records at the given level. // The handler ignores records whose level is lower. func (h *Handler) Enabled(_ context.Context, level slog.Level) bool { - return level >= h.Options.Level.Level() + return level >= h.options.level.Level() } // WithGroup returns a new handler with the new group attached to it. func (h *Handler) WithGroup(group string) slog.Handler { return &Handler{ - Mutex: h.Mutex, // we share the mutex from the parent handler - Writer: h.Writer, - Options: h.Options, - goa: append(h.goa, GroupOrAttrs{group: group}), - addColorToBuf: h.addColorToBuf, + Mutex: h.Mutex, // we share the mutex from the parent handler + Writer: h.Writer, + options: h.options, + goa: append(h.goa, GroupOrAttrs{group: group}), } } @@ -86,15 +64,14 @@ func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler { } return &Handler{ - Mutex: h.Mutex, // we share the mutex from the parent handler - Writer: h.Writer, - Options: h.Options, - goa: append(h.goa, newAttrs...), - addColorToBuf: h.addColorToBuf, + Mutex: h.Mutex, // we share the mutex from the parent handler + Writer: h.Writer, + Options: h.Options, + goa: append(h.goa, newAttrs...), } } -// Handle processes the log record and writes it to the writer with appropriate formatting. +// Handle processes the log record and writes it to the writer with appropriat formatting. func (h *Handler) Handle(_ context.Context, record slog.Record) error { bufp := allocBuf() // Allocate a buffer for writing the log record. buf := *bufp @@ -105,41 +82,21 @@ func (h *Handler) Handle(_ context.Context, record slog.Record) error { }() // Write time if the record has a valid time and TimeFormat is specified. - if h.Options.TimeFormat != "" && !record.Time.IsZero() { + if h.options.timeFormat != "" && !record.Time.IsZero() { // Format and append time information to the buffer. - buf = h.addColorToBuf(buf, faint) - buf = append(buf, record.Time.Format(h.Options.TimeFormat)...) - buf = h.addColorToBuf(buf, normalIntensity) + buf = h.options.addColorToBuf(buf, faint) + buf = append(buf, record.Time.Format(h.options.timeFormat)...) + buf = h.options.addColorToBuf(buf, normalIntensity) buf = append(buf, " "...) } - // Write level with appropriate formatting and color. - // Also append right padding depending on the log level. - switch record.Level { - case slog.LevelInfo: - buf = h.addColorToBuf(buf, fgGreen) - buf = append(buf, record.Level.String()...) - buf = append(buf, " "...) - case slog.LevelError: - buf = h.addColorToBuf(buf, fgRed) - buf = append(buf, record.Level.String()...) - case slog.LevelWarn: - buf = h.addColorToBuf(buf, fgYellow) - buf = append(buf, record.Level.String()...) - buf = append(buf, " "...) - case slog.LevelDebug: - buf = h.addColorToBuf(buf, fgMagenta) - buf = append(buf, record.Level.String()...) - } - - buf = append(buf, reset...) - buf = append(buf, " "...) + buf = h.addLogLevel(buf, record) var senti error // If configured, write the source file and line information. - for h.Options.ShowSource { - buf = h.addColorToBuf(buf, fgBlue) + for h.options.showSource { + buf = h.options.addColorToBuf(buf, fgBlue) buf = append(buf, underline...) frame, _ := runtime.CallersFrames([]uintptr{record.PC}).Next() @@ -191,7 +148,7 @@ func (h *Handler) Handle(_ context.Context, record slog.Record) error { attr.Key = lastGroup + attr.Key } - buf = appendAttr(buf, attr, h.addColorToBuf) + buf = appendAttr(buf, attr, h.options.addColorToBuf) } } @@ -201,7 +158,7 @@ func (h *Handler) Handle(_ context.Context, record slog.Record) error { if lastGroup != "" { attr.Key = lastGroup + attr.Key } - buf = appendAttr(buf, attr, h.addColorToBuf) + buf = appendAttr(buf, attr, h.options.addColorToBuf) return true }) @@ -223,6 +180,31 @@ func (h *Handler) Handle(_ context.Context, record slog.Record) error { return senti } +func (h *Handler) addLogLevel(buf []byte, record slog.Record) []byte { + // Write level with appropriate formatting and color. + // Also append right padding depending on the log level. + switch record.Level { + case slog.LevelInfo: + buf = h.options.addColorToBuf(buf, fgGreen) + case slog.LevelError: + buf = h.options.addColorToBuf(buf, fgRed) + case slog.LevelWarn: + buf = h.options.addColorToBuf(buf, fgYellow) + case slog.LevelDebug: + buf = h.options.addColorToBuf(buf, fgMagenta) + } + + if str, ok := h.options.strLvl[record.Level]; ok { + buf = append(buf, str...) + } else { + buf = append(buf, "-----"...) + } + + buf = append(buf, reset...) + + return append(buf, " "...) +} + // appendAttr appends the attribute to the buffer. func appendAttr(buf []byte, attr slog.Attr, addColor func([]byte, string) []byte) []byte { // Resolve the Attr's value before doing anything else.