From 732739c478ced60e62bfda48140ad220c2ed6642 Mon Sep 17 00:00:00 2001 From: Tom <73077675+tmzane@users.noreply.github.com> Date: Sun, 24 Sep 2023 19:11:05 +0300 Subject: [PATCH 1/5] feat: generate custom `Logger` instead of wrapping `slog.Logger` --- example/example.go | 28 ++++++++++++++++++++++--- sloggen_test.go | 2 +- template.go.tmpl | 51 +++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 74 insertions(+), 7 deletions(-) diff --git a/example/example.go b/example/example.go index e306eb4..7dcbc3d 100644 --- a/example/example.go +++ b/example/example.go @@ -57,7 +57,29 @@ func ReplaceAttr(_ []string, attr slog.Attr) slog.Attr { return attr } -type Logger struct{ Logger *slog.Logger } +type Logger struct{ handler slog.Handler } + +func New(h slog.Handler) *Logger { return &Logger{handler: h} } + +func (l *Logger) Handler() slog.Handler { return l.handler } + +func (l *Logger) Enabled(ctx context.Context, level slog.Level) bool { + return l.handler.Enabled(ctx, level) +} + +func (l *Logger) With(attrs ...slog.Attr) *Logger { + if len(attrs) == 0 { + return l + } + return &Logger{handler: l.handler.WithAttrs(attrs)} +} + +func (l *Logger) WithGroup(name string) *Logger { + if name == "" { + return l + } + return &Logger{handler: l.handler.WithGroup(name)} +} func (l *Logger) Trace(ctx context.Context, msg string, attrs ...slog.Attr) { l.log(ctx, LevelTrace, msg, attrs) @@ -80,12 +102,12 @@ func (l *Logger) Error(ctx context.Context, msg string, attrs ...slog.Attr) { } func (l *Logger) log(ctx context.Context, level slog.Level, msg string, attrs []slog.Attr) { - if !l.Logger.Enabled(ctx, level) { + if !l.handler.Enabled(ctx, level) { return } var pcs [1]uintptr runtime.Callers(3, pcs[:]) r := slog.NewRecord(time.Now(), level, msg, pcs[0]) r.AddAttrs(attrs...) - _ = l.Logger.Handler().Handle(ctx, r) + _ = l.handler.Handle(ctx, r) } diff --git a/sloggen_test.go b/sloggen_test.go index ec5a519..1533014 100644 --- a/sloggen_test.go +++ b/sloggen_test.go @@ -127,7 +127,7 @@ func TestLogger(t *testing.T) { }, }) - logger := example.Logger{Logger: slog.New(handler)} + logger := example.New(handler) logger.Info(context.Background(), "test") assert.Equal[E](t, buf.String(), "level=INFO source=sloggen_test.go:131 msg=test\n") } diff --git a/template.go.tmpl b/template.go.tmpl index d09bc8d..6574c9f 100644 --- a/template.go.tmpl +++ b/template.go.tmpl @@ -45,20 +45,65 @@ func ReplaceAttr(_ []string, attr slog.Attr) slog.Attr { {{end}} {{if $l := $.Logger}} -type Logger struct{ Logger *slog.Logger } +type Logger struct{ handler slog.Handler } + +func New(h slog.Handler) *Logger { return &Logger{handler: h} } + +func (l *Logger) Handler() slog.Handler { return l.handler } + +func (l *Logger) Enabled(ctx context.Context, level slog.Level) bool { + return l.handler.Enabled(ctx, level) +} + +func (l *Logger) With({{if $l.AttrAPI}}attrs ...slog.Attr{{else}}args ...any{{end}}) *Logger { + if len({{if $l.AttrAPI}}attrs{{else}}args{{end}}) == 0 { + return l + } + return &Logger{handler: l.handler.WithAttrs({{if $l.AttrAPI}}attrs{{else}}args2attrs(args){{end}})} +} + +func (l *Logger) WithGroup(name string) *Logger { + if name == "" { + return l + } + return &Logger{handler: l.handler.WithGroup(name)} +} + {{range $_, $name := $l.Levels}} func (l *Logger) {{title $name}}({{if $l.Context}}ctx context.Context, {{end}}msg string, {{if $l.AttrAPI}}attrs ...slog.Attr{{else}}args ...any{{end}}) { l.log({{if $l.Context}}ctx{{else}}context.Background(){{end}}, {{if eq (len $.Levels) 0}}slog.{{end}}Level{{title $name}}, msg, {{if $l.AttrAPI}}attrs{{else}}args{{end}}) } {{end}} func (l *Logger) log(ctx context.Context, level slog.Level, msg string, {{if $l.AttrAPI}}attrs []slog.Attr{{else}}args []any{{end}}) { - if !l.Logger.Enabled(ctx, level) { + if !l.handler.Enabled(ctx, level) { return } var pcs [1]uintptr runtime.Callers(3, pcs[:]) r := slog.NewRecord(time.Now(), level, msg, pcs[0]) r.Add{{if $l.AttrAPI}}Attrs(attrs...){{else}}(args...){{end}} - _ = l.Logger.Handler().Handle(ctx, r) + _ = l.handler.Handle(ctx, r) } + +{{if not $l.AttrAPI}} +{{/* based on argsToAttrSlice() and argsToAttr() from log/slog sources. */}} +func args2attrs(args []any) []slog.Attr { + var attrs []slog.Attr + for len(args) > 0 { + switch x := args[0].(type) { + case string: + if len(args) == 1 { + attrs, args = append(attrs, slog.String("!BADKEY", x)), nil + } else { + attrs, args = append(attrs, slog.Any(x, args[1])), args[2:] + } + case slog.Attr: + attrs, args = append(attrs, x), args[1:] + default: + attrs, args = append(attrs, slog.Any("!BADKEY", x)), args[1:] + } + } + return attrs +} +{{end}} {{end}} From d3e3b8efd919f22bb6ed877614afb57f295f5d1f Mon Sep 17 00:00:00 2001 From: Tom <73077675+tmzane@users.noreply.github.com> Date: Sun, 24 Sep 2023 20:41:12 +0300 Subject: [PATCH 2/5] add the `Log` method --- example/example.go | 4 ++++ template.go.tmpl | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/example/example.go b/example/example.go index 7dcbc3d..4067ea4 100644 --- a/example/example.go +++ b/example/example.go @@ -81,6 +81,10 @@ func (l *Logger) WithGroup(name string) *Logger { return &Logger{handler: l.handler.WithGroup(name)} } +func (l *Logger) Log(ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr) { + l.log(ctx, level, msg, attrs) +} + func (l *Logger) Trace(ctx context.Context, msg string, attrs ...slog.Attr) { l.log(ctx, LevelTrace, msg, attrs) } diff --git a/template.go.tmpl b/template.go.tmpl index 6574c9f..f350fc2 100644 --- a/template.go.tmpl +++ b/template.go.tmpl @@ -69,6 +69,10 @@ func (l *Logger) WithGroup(name string) *Logger { return &Logger{handler: l.handler.WithGroup(name)} } +func (l *Logger) Log({{if $l.Context}}ctx context.Context, {{end}}level slog.Level, msg string, {{if $l.AttrAPI}}attrs ...slog.Attr{{else}}args ...any{{end}}) { + l.log({{if $l.Context}}ctx{{else}}context.Background(){{end}}, level, msg, {{if $l.AttrAPI}}attrs{{else}}args{{end}}) +} + {{range $_, $name := $l.Levels}} func (l *Logger) {{title $name}}({{if $l.Context}}ctx context.Context, {{end}}msg string, {{if $l.AttrAPI}}attrs ...slog.Attr{{else}}args ...any{{end}}) { l.log({{if $l.Context}}ctx{{else}}context.Background(){{end}}, {{if eq (len $.Levels) 0}}slog.{{end}}Level{{title $name}}, msg, {{if $l.AttrAPI}}attrs{{else}}args{{end}}) From 6a54808e89877d4633776a90b640f4a42dc8eee7 Mon Sep 17 00:00:00 2001 From: Tom <73077675+tmzane@users.noreply.github.com> Date: Sun, 24 Sep 2023 20:43:57 +0300 Subject: [PATCH 3/5] fmt --- template.go.tmpl | 1 + 1 file changed, 1 insertion(+) diff --git a/template.go.tmpl b/template.go.tmpl index f350fc2..fc8ead7 100644 --- a/template.go.tmpl +++ b/template.go.tmpl @@ -78,6 +78,7 @@ func (l *Logger) {{title $name}}({{if $l.Context}}ctx context.Context, {{end}}ms l.log({{if $l.Context}}ctx{{else}}context.Background(){{end}}, {{if eq (len $.Levels) 0}}slog.{{end}}Level{{title $name}}, msg, {{if $l.AttrAPI}}attrs{{else}}args{{end}}) } {{end}} + func (l *Logger) log(ctx context.Context, level slog.Level, msg string, {{if $l.AttrAPI}}attrs []slog.Attr{{else}}args []any{{end}}) { if !l.handler.Enabled(ctx, level) { return From 89d3a0a61d0559e75d91b64b48efc98ce322491f Mon Sep 17 00:00:00 2001 From: Tom <73077675+tmzane@users.noreply.github.com> Date: Mon, 25 Sep 2023 00:36:08 +0300 Subject: [PATCH 4/5] fewer levels in example --- .slog.example.yml | 5 +---- example/example.go | 41 +++++++---------------------------------- sloggen_test.go | 25 ++++++++++++++----------- 3 files changed, 22 insertions(+), 49 deletions(-) diff --git a/.slog.example.yml b/.slog.example.yml index 38e539b..f494fa5 100644 --- a/.slog.example.yml +++ b/.slog.example.yml @@ -12,11 +12,8 @@ imports: # format: # default: [] levels: - - trace: -8 - - debug: -4 - info: 0 - - warn: 4 - - error: 8 + - alert: 1 # the list of keys to generate constants for. # default: [] diff --git a/example/example.go b/example/example.go index 4067ea4..6fe428b 100644 --- a/example/example.go +++ b/example/example.go @@ -9,11 +9,8 @@ import "runtime" import "strings" import "time" -const LevelTrace = slog.Level(-8) -const LevelDebug = slog.Level(-4) const LevelInfo = slog.Level(0) -const LevelWarn = slog.Level(4) -const LevelError = slog.Level(8) +const LevelAlert = slog.Level(1) const RequestId = "request_id" @@ -23,16 +20,10 @@ func UserId(value int) slog.Attr { return slog.Int("user_id", value) } func ParseLevel(s string) (slog.Level, error) { switch strings.ToUpper(s) { - case "TRACE": - return LevelTrace, nil - case "DEBUG": - return LevelDebug, nil case "INFO": return LevelInfo, nil - case "WARN": - return LevelWarn, nil - case "ERROR": - return LevelError, nil + case "ALERT": + return LevelAlert, nil default: return 0, fmt.Errorf("slog: level string %q: unknown name", s) } @@ -43,16 +34,10 @@ func ReplaceAttr(_ []string, attr slog.Attr) slog.Attr { return attr } switch attr.Value.Any().(slog.Level) { - case LevelTrace: - attr.Value = slog.StringValue("TRACE") - case LevelDebug: - attr.Value = slog.StringValue("DEBUG") case LevelInfo: attr.Value = slog.StringValue("INFO") - case LevelWarn: - attr.Value = slog.StringValue("WARN") - case LevelError: - attr.Value = slog.StringValue("ERROR") + case LevelAlert: + attr.Value = slog.StringValue("ALERT") } return attr } @@ -85,24 +70,12 @@ func (l *Logger) Log(ctx context.Context, level slog.Level, msg string, attrs .. l.log(ctx, level, msg, attrs) } -func (l *Logger) Trace(ctx context.Context, msg string, attrs ...slog.Attr) { - l.log(ctx, LevelTrace, msg, attrs) -} - -func (l *Logger) Debug(ctx context.Context, msg string, attrs ...slog.Attr) { - l.log(ctx, LevelDebug, msg, attrs) -} - func (l *Logger) Info(ctx context.Context, msg string, attrs ...slog.Attr) { l.log(ctx, LevelInfo, msg, attrs) } -func (l *Logger) Warn(ctx context.Context, msg string, attrs ...slog.Attr) { - l.log(ctx, LevelWarn, msg, attrs) -} - -func (l *Logger) Error(ctx context.Context, msg string, attrs ...slog.Attr) { - l.log(ctx, LevelError, msg, attrs) +func (l *Logger) Alert(ctx context.Context, msg string, attrs ...slog.Attr) { + l.log(ctx, LevelAlert, msg, attrs) } func (l *Logger) log(ctx context.Context, level slog.Level, msg string, attrs []slog.Attr) { diff --git a/sloggen_test.go b/sloggen_test.go index 1533014..5ceabfd 100644 --- a/sloggen_test.go +++ b/sloggen_test.go @@ -18,7 +18,7 @@ import ( var cfg = config{ Pkg: "test", Imports: []string{"fmt", "log/slog", "strings", "time"}, - Levels: map[int]string{-8: "custom"}, + Levels: map[int]string{1: "custom"}, Consts: []string{"foo"}, Attrs: map[string]string{ "bar": "time.Time", @@ -32,7 +32,7 @@ pkg: test imports: - time levels: - - custom: -8 + - custom: 1 consts: - foo attrs: @@ -55,7 +55,7 @@ import "log/slog" import "strings" import "time" -const LevelCustom = slog.Level(-8) +const LevelCustom = slog.Level(1) const Foo = "foo" @@ -89,15 +89,14 @@ func ReplaceAttr(_ []string, attr slog.Attr) slog.Attr { } func TestParseLevel(t *testing.T) { - level, err := example.ParseLevel("TRACE") + level, err := example.ParseLevel("ALERT") assert.NoErr[F](t, err) - assert.Equal[E](t, level, example.LevelTrace) + assert.Equal[E](t, level, example.LevelAlert) } func TestReplaceAttr(t *testing.T) { var buf bytes.Buffer handler := slog.NewTextHandler(&buf, &slog.HandlerOptions{ - Level: example.LevelTrace, ReplaceAttr: func(groups []string, attr slog.Attr) slog.Attr { if attr.Key == slog.TimeKey { return slog.Attr{} @@ -107,8 +106,8 @@ func TestReplaceAttr(t *testing.T) { }) logger := slog.New(handler) - logger.Log(context.Background(), example.LevelTrace, "test") - assert.Equal[E](t, buf.String(), "level=TRACE msg=test\n") + logger.Log(context.Background(), example.LevelAlert, "test") + assert.Equal[E](t, buf.String(), "level=ALERT msg=test\n") } func TestLogger(t *testing.T) { @@ -123,11 +122,15 @@ func TestLogger(t *testing.T) { src := attr.Value.Any().(*slog.Source) src.File = filepath.Base(src.File) } - return attr + return example.ReplaceAttr(groups, attr) }, }) logger := example.New(handler) - logger.Info(context.Background(), "test") - assert.Equal[E](t, buf.String(), "level=INFO source=sloggen_test.go:131 msg=test\n") + logger.Info(context.Background(), "test1") + logger.Alert(context.Background(), "test2") + assert.Equal[E](t, "\n"+buf.String(), ` +level=INFO source=sloggen_test.go:130 msg=test1 +level=ALERT source=sloggen_test.go:131 msg=test2 +`) } From 593eda935bec1c4c78714f378079c545676878d9 Mon Sep 17 00:00:00 2001 From: Tom <73077675+tmzane@users.noreply.github.com> Date: Mon, 25 Sep 2023 20:48:55 +0300 Subject: [PATCH 5/5] test example --- sloggen_test.go | 66 ++++++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 36 deletions(-) diff --git a/sloggen_test.go b/sloggen_test.go index 5ceabfd..ce99f15 100644 --- a/sloggen_test.go +++ b/sloggen_test.go @@ -88,49 +88,43 @@ func ReplaceAttr(_ []string, attr slog.Attr) slog.Attr { assert.Equal[E](t, buf.String(), src) } -func TestParseLevel(t *testing.T) { - level, err := example.ParseLevel("ALERT") - assert.NoErr[F](t, err) - assert.Equal[E](t, level, example.LevelAlert) -} +func TestExample(t *testing.T) { + replaceAttr := func(groups []string, attr slog.Attr) slog.Attr { + if attr.Key == slog.TimeKey { + return slog.Attr{} + } + if attr.Key == slog.SourceKey { + src := attr.Value.Any().(*slog.Source) + src.File = filepath.Base(src.File) + } + return example.ReplaceAttr(groups, attr) + } -func TestReplaceAttr(t *testing.T) { var buf bytes.Buffer handler := slog.NewTextHandler(&buf, &slog.HandlerOptions{ - ReplaceAttr: func(groups []string, attr slog.Attr) slog.Attr { - if attr.Key == slog.TimeKey { - return slog.Attr{} - } - return example.ReplaceAttr(groups, attr) - }, + AddSource: true, + Level: example.LevelInfo, + ReplaceAttr: replaceAttr, }) - logger := slog.New(handler) - logger.Log(context.Background(), example.LevelAlert, "test") - assert.Equal[E](t, buf.String(), "level=ALERT msg=test\n") -} + logger := example.New(handler). + WithGroup("group"). + With(slog.String("key", "value")) -func TestLogger(t *testing.T) { - var buf bytes.Buffer - handler := slog.NewTextHandler(&buf, &slog.HandlerOptions{ - AddSource: true, - ReplaceAttr: func(groups []string, attr slog.Attr) slog.Attr { - if attr.Key == slog.TimeKey { - return slog.Attr{} - } - if attr.Key == slog.SourceKey { - src := attr.Value.Any().(*slog.Source) - src.File = filepath.Base(src.File) - } - return example.ReplaceAttr(groups, attr) - }, - }) + level, err := example.ParseLevel("ALERT") + assert.NoErr[F](t, err) + assert.Equal[E](t, level, example.LevelAlert) + + ctx := context.Background() + enabled := logger.Enabled(ctx, level) + assert.Equal[E](t, enabled, true) - logger := example.New(handler) - logger.Info(context.Background(), "test1") - logger.Alert(context.Background(), "test2") + logger.Info(ctx, "foo") + logger.Alert(ctx, "bar") + logger.Log(ctx, level, "baz") assert.Equal[E](t, "\n"+buf.String(), ` -level=INFO source=sloggen_test.go:130 msg=test1 -level=ALERT source=sloggen_test.go:131 msg=test2 +level=INFO source=sloggen_test.go:122 msg=foo group.key=value +level=ALERT source=sloggen_test.go:123 msg=bar group.key=value +level=ALERT source=sloggen_test.go:124 msg=baz group.key=value `) }