From ca6b98b57d73d81a52beff7e3c6b04b6d414a9f2 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 5 Apr 2024 21:15:24 -0400 Subject: [PATCH] sort of complete version of the basic pfxlog on top of log/slog --- examples/pfxlog-example-simple/main.go | 2 +- .../pfxlog-example-simple/other/component.go | 7 +- formatter.go | 94 ------------------ go.mod | 9 +- go.sum | 28 ++---- handler.go | 20 ++-- options.go | 3 + pfxlog.go | 96 ++++++++++++++++++- 8 files changed, 123 insertions(+), 136 deletions(-) delete mode 100644 formatter.go diff --git a/examples/pfxlog-example-simple/main.go b/examples/pfxlog-example-simple/main.go index d0f2982..a774f79 100644 --- a/examples/pfxlog-example-simple/main.go +++ b/examples/pfxlog-example-simple/main.go @@ -30,7 +30,7 @@ func main() { } func counter(number int, notify chan int) { - log := pfxlog.ContextLogger(fmt.Sprintf("#%d", number)) + log := pfxlog.Channel(fmt.Sprintf("#%d", number)) for i := 0; i < 5; i++ { pfxlog.Infof("visited %d.", i) diff --git a/examples/pfxlog-example-simple/other/component.go b/examples/pfxlog-example-simple/other/component.go index fd74226..ac5b829 100644 --- a/examples/pfxlog-example-simple/other/component.go +++ b/examples/pfxlog-example-simple/other/component.go @@ -2,12 +2,15 @@ package other import ( "github.com/michaelquigley/pfxlog" + "log/slog" ) type Component struct{} func (c *Component) Hello() { + pfxlog.Debug("debugging") pfxlog.Infof("this is #%d", 6) - pfxlog.Logger().Info("oh, wow!") - pfxlog.Logger().Error("uh...") + pfxlog.Info("oh, wow!") + slog.With("severity", "severe").Warn("oh, no!") + pfxlog.Error("uh...") } diff --git a/formatter.go b/formatter.go deleted file mode 100644 index dc0b851..0000000 --- a/formatter.go +++ /dev/null @@ -1,94 +0,0 @@ -package pfxlog - -import ( - "fmt" - "github.com/sirupsen/logrus" - "strings" - "time" -) - -type formatter struct { - options *Options -} - -func NewFormatter(options *Options) logrus.Formatter { - return &formatter{options} -} - -func (f *formatter) Format(entry *logrus.Entry) ([]byte, error) { - var timeLabel string - if f.options.AbsoluteTime { - timeLabel = "[" + time.Now().Format(f.options.TimestampFormat) + "]" - } else { - seconds := time.Since(f.options.StartTimestamp).Seconds() - timeLabel = fmt.Sprintf("[%8.3f]", seconds) - } - var level string - switch entry.Level { - case logrus.ErrorLevel: - level = f.options.ErrorLabel - case logrus.WarnLevel: - level = f.options.WarningLabel - case logrus.InfoLevel: - level = f.options.InfoLabel - case logrus.DebugLevel: - level = f.options.DebugLabel - } - trimmedFunction := "" - if entry.Caller != nil { - trimmedFunction = strings.TrimPrefix(entry.Caller.Function, f.options.TrimPrefix) - } else if fn, found := entry.Data["func"]; found { - if fns, ok := fn.(string); ok { - trimmedFunction = strings.TrimPrefix(fns, f.options.TrimPrefix) - delete(entry.Data, "func") - delete(entry.Data, "file") - } - } - if v, found := entry.Data["_channels"]; found { - trimmedFunction += " |" - for i, channel := range v.([]string) { - if i > 0 { - trimmedFunction += ", " - } - trimmedFunction += channel - } - trimmedFunction += "|" - } - if context, found := entry.Data["_context"]; found { - trimmedFunction += " [" + context.(string) + "]" - } - message := entry.Message - if withFields(entry.Data) { - fields := "{" - field := 0 - for k, v := range entry.Data { - if k != "_context" && k != "_channels" { - if field > 0 { - fields += " " - } - field++ - fields += fmt.Sprintf("%s=[%v]", k, v) - } - } - fields += "} " - message = f.options.FieldsColor + fields + f.options.DefaultFgColor + message - } - return []byte(fmt.Sprintf("%s %s %s: %s\n", - f.options.TimestampColor+timeLabel+f.options.DefaultFgColor, - level, - f.options.FunctionColor+trimmedFunction+f.options.DefaultFgColor, - message), - ), - nil -} - -func withFields(data map[string]interface{}) bool { - gt := 0 - if _, contextFound := data["_context"]; contextFound { - gt++ - } - if _, channelsFound := data["_channels"]; channelsFound { - gt++ - } - return len(data) > gt -} diff --git a/go.mod b/go.mod index bed70db..fb5bb0f 100644 --- a/go.mod +++ b/go.mod @@ -4,15 +4,12 @@ go 1.22 require ( github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b - github.com/sirupsen/logrus v1.8.1 - golang.org/x/crypto v0.1.0 + golang.org/x/crypto v0.22.0 ) require ( github.com/mattn/go-colorable v0.1.1 // indirect github.com/mattn/go-isatty v0.0.7 // indirect - github.com/stretchr/testify v1.7.0 // indirect - golang.org/x/sys v0.1.0 // indirect - golang.org/x/term v0.1.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + golang.org/x/sys v0.19.0 // indirect + golang.org/x/term v0.19.0 // indirect ) diff --git a/go.sum b/go.sum index e078743..4215195 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,3 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -8,23 +5,10 @@ github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= diff --git a/handler.go b/handler.go index bc3539d..696cf9f 100644 --- a/handler.go +++ b/handler.go @@ -27,6 +27,8 @@ func (h *PrettyHandler) Enabled(_ context.Context, level slog.Level) bool { } func (h *PrettyHandler) Handle(_ context.Context, r slog.Record) error { + var out strings.Builder + var timeLabel string if h.options.AbsoluteTime { timeLabel = "[" + time.Now().Format(h.options.TimestampFormat) + "]" @@ -34,6 +36,7 @@ func (h *PrettyHandler) Handle(_ context.Context, r slog.Record) error { seconds := time.Since(h.options.StartTimestamp).Seconds() timeLabel = fmt.Sprintf("[%8.3f]", seconds) } + out.WriteString(h.options.TimestampColor + timeLabel + h.options.DefaultFgColor) var level string switch r.Level { @@ -46,6 +49,7 @@ func (h *PrettyHandler) Handle(_ context.Context, r slog.Record) error { case slog.LevelDebug: level = h.options.DebugLabel } + out.WriteString(" " + level) fs := runtime.CallersFrames([]uintptr{r.PC}) f, _ := fs.Next() @@ -53,14 +57,15 @@ func (h *PrettyHandler) Handle(_ context.Context, r slog.Record) error { if h.options.TrimPrefix != "" { functionStr = strings.TrimPrefix(functionStr, h.options.TrimPrefix) } + out.WriteString(" " + h.options.FunctionColor + functionStr + h.options.DefaultFgColor) r.AddAttrs(h.attrs...) fieldsMap := make(map[string]interface{}, r.NumAttrs()) r.Attrs(func(a slog.Attr) bool { - if a.Key != "_context" { + if a.Key != ChannelKey { fieldsMap[a.Key] = a.Value.Any() } else { - functionStr += " |" + fmt.Sprintf("%v", a.Value) + "|" + out.WriteString(h.options.ChannelColor + " |" + a.Value.String() + "|" + h.options.DefaultFgColor) } return true }) @@ -68,17 +73,14 @@ func (h *PrettyHandler) Handle(_ context.Context, r slog.Record) error { if err != nil { return err } - fieldsStr := "" if len(fieldsBytes) > 2 { - fieldsStr = h.options.FieldsColor + string(fieldsBytes) + h.options.DefaultFgColor + out.WriteString(" " + h.options.FieldsColor + string(fieldsBytes) + h.options.DefaultFgColor) } + out.WriteString(" " + r.Message) + h.lock.Lock() - fmt.Println(h.options.TimestampColor+timeLabel+h.options.DefaultFgColor, - level, - h.options.FunctionColor+functionStr+h.options.DefaultFgColor, - fieldsStr, - r.Message) + fmt.Println(out.String()) h.lock.Unlock() return nil diff --git a/options.go b/options.go index 43023b6..abb9dd4 100644 --- a/options.go +++ b/options.go @@ -21,6 +21,7 @@ type Options struct { TimestampColor string FunctionColor string + ChannelColor string FieldsColor string DefaultFgColor string @@ -82,6 +83,7 @@ func (options *Options) Color() *Options { options.TimestampColor = ansi.Blue options.FunctionColor = ansi.Cyan + options.ChannelColor = ansi.LightBlue options.FieldsColor = ansi.LightCyan options.DefaultFgColor = ansi.DefaultFG @@ -96,6 +98,7 @@ func (options *Options) NoColor() *Options { options.TimestampColor = "" options.FunctionColor = "" + options.ChannelColor = "" options.FieldsColor = "" options.DefaultFgColor = "" diff --git a/pfxlog.go b/pfxlog.go index 279f1c4..a0486ba 100755 --- a/pfxlog.go +++ b/pfxlog.go @@ -10,6 +10,8 @@ import ( "time" ) +const ChannelKey = "_channel" + func GlobalInit(level slog.Level, options *Options) { if defaultEnv("PFXLOG_NO_JSON", false) || terminal.IsTerminal(int(os.Stdout.Fd())) { logger := slog.New(NewPrettyHandler(level, options)) @@ -25,8 +27,24 @@ func Logger() Builder { return Builder{slog.Default()} } -func ContextLogger(context string) Builder { - return Builder{slog.Default().With(slog.String("_context", context))} +func Debug(args ...interface{}) { + if !slog.Default().Enabled(context.Background(), slog.LevelDebug) { + return + } + var pcs [1]uintptr + runtime.Callers(2, pcs[:]) // skip [Callers, Infof] + r := slog.NewRecord(time.Now(), slog.LevelDebug, fmt.Sprint(args...), pcs[0]) + _ = slog.Default().Handler().Handle(context.Background(), r) +} + +func Debugf(format string, args ...interface{}) { + if !slog.Default().Enabled(context.Background(), slog.LevelDebug) { + return + } + var pcs [1]uintptr + runtime.Callers(2, pcs[:]) // skip [Callers, Infof] + r := slog.NewRecord(time.Now(), slog.LevelDebug, fmt.Sprintf(format, args...), pcs[0]) + _ = slog.Default().Handler().Handle(context.Background(), r) } func Info(args ...interface{}) { @@ -49,10 +67,64 @@ func Infof(format string, args ...interface{}) { _ = slog.Default().Handler().Handle(context.Background(), r) } +func Warn(args ...interface{}) { + if !slog.Default().Enabled(context.Background(), slog.LevelWarn) { + return + } + var pcs [1]uintptr + runtime.Callers(2, pcs[:]) // skip [Callers, Infof] + r := slog.NewRecord(time.Now(), slog.LevelWarn, fmt.Sprint(args...), pcs[0]) + _ = slog.Default().Handler().Handle(context.Background(), r) +} + +func Warnf(format string, args ...interface{}) { + if !slog.Default().Enabled(context.Background(), slog.LevelWarn) { + return + } + var pcs [1]uintptr + runtime.Callers(2, pcs[:]) // skip [Callers, Infof] + r := slog.NewRecord(time.Now(), slog.LevelWarn, fmt.Sprintf(format, args...), pcs[0]) + _ = slog.Default().Handler().Handle(context.Background(), r) +} + +func Error(args ...interface{}) { + if !slog.Default().Enabled(context.Background(), slog.LevelError) { + return + } + var pcs [1]uintptr + runtime.Callers(2, pcs[:]) // skip [Callers, Infof] + r := slog.NewRecord(time.Now(), slog.LevelError, fmt.Sprint(args...), pcs[0]) + _ = slog.Default().Handler().Handle(context.Background(), r) +} + +func Errorf(format string, args ...interface{}) { + if !slog.Default().Enabled(context.Background(), slog.LevelError) { + return + } + var pcs [1]uintptr + runtime.Callers(2, pcs[:]) // skip [Callers, Infof] + r := slog.NewRecord(time.Now(), slog.LevelError, fmt.Sprintf(format, args...), pcs[0]) + _ = slog.Default().Handler().Handle(context.Background(), r) +} + type Builder struct { *slog.Logger } +func Channel(channel string) Builder { + return Builder{slog.Default().With(slog.String(ChannelKey, channel))} +} + +func (b Builder) Debugf(format string, args ...interface{}) { + if !b.Enabled(context.Background(), slog.LevelDebug) { + return + } + var pcs [1]uintptr + runtime.Callers(2, pcs[:]) // skip [Callers, Infof] + r := slog.NewRecord(time.Now(), slog.LevelDebug, fmt.Sprintf(format, args...), pcs[0]) + _ = b.Handler().Handle(context.Background(), r) +} + func (b Builder) Infof(format string, args ...interface{}) { if !b.Enabled(context.Background(), slog.LevelInfo) { return @@ -63,4 +135,24 @@ func (b Builder) Infof(format string, args ...interface{}) { _ = b.Handler().Handle(context.Background(), r) } +func (b Builder) Warnf(format string, args ...interface{}) { + if !b.Enabled(context.Background(), slog.LevelWarn) { + return + } + var pcs [1]uintptr + runtime.Callers(2, pcs[:]) // skip [Callers, Infof] + r := slog.NewRecord(time.Now(), slog.LevelWarn, fmt.Sprintf(format, args...), pcs[0]) + _ = b.Handler().Handle(context.Background(), r) +} + +func (b Builder) Errorf(format string, args ...interface{}) { + if !b.Enabled(context.Background(), slog.LevelError) { + return + } + var pcs [1]uintptr + runtime.Callers(2, pcs[:]) // skip [Callers, Infof] + r := slog.NewRecord(time.Now(), slog.LevelError, fmt.Sprintf(format, args...), pcs[0]) + _ = b.Handler().Handle(context.Background(), r) +} + var globalOptions *Options