From f7caf7b29ab88217c573fd5a88edbadd635c45b4 Mon Sep 17 00:00:00 2001 From: Mark Rushakoff Date: Wed, 23 Oct 2024 11:38:11 -0400 Subject: [PATCH 1/4] feat(log): add slog-backed Logger type The standard library's log/slog package was released after the SDK added its standardized Logger interface. It is reasonable to expect new Go projects to depend on log/slog rather than zerolog or any other third party logging implementations. The existing Logger interface maps cleanly to the log/slog API. This change adds a new cosmossdk.io/log/slog package that exports a single Logger type, backed by an *slog.Logger. Currently, we expect the caller to provide a fully configured *log/slog.Logger and pass it to cosmossdk.io/log/slog.FromSlog. There is no reason we can't have a New function to produce a wrapped slog logger, but that seems out of scope of the initial implementation. --- log/README.md | 2 ++ log/go.mod | 4 +++ log/go.sum | 1 + log/slog/logger.go | 47 +++++++++++++++++++++++++++++++ log/slog/logger_test.go | 62 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 116 insertions(+) create mode 100644 log/slog/logger.go create mode 100644 log/slog/logger_test.go diff --git a/log/README.md b/log/README.md index c06f26e933b4..83b7ef0ce680 100644 --- a/log/README.md +++ b/log/README.md @@ -1,3 +1,5 @@ # Log The `cosmossdk.io/log` provides a zerolog logging implementation for the Cosmos SDK and Cosmos SDK modules. + +To use a logger backed by an instance of the standard library's `log/slog` package, use `cosmossdk.io/log/slog`. diff --git a/log/go.mod b/log/go.mod index 292e5d973e6f..1f75339ebc37 100644 --- a/log/go.mod +++ b/log/go.mod @@ -6,16 +6,20 @@ require ( github.com/bytedance/sonic v1.12.3 github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.33.0 + github.com/stretchr/testify v1.8.1 ) require ( github.com/bytedance/sonic/loader v0.2.0 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/sys v0.22.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/log/go.sum b/log/go.sum index b6a141763c97..ead16b768f6e 100644 --- a/log/go.sum +++ b/log/go.sum @@ -45,6 +45,7 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/log/slog/logger.go b/log/slog/logger.go new file mode 100644 index 000000000000..1464ff8990c3 --- /dev/null +++ b/log/slog/logger.go @@ -0,0 +1,47 @@ +// Package slog contains a Logger type that satisfies [cosmossdk.io/log.Logger], +// backed by a standard library [*log/slog.Logger]. +package slog + +import ( + "log/slog" + + "cosmossdk.io/log" +) + +var _ log.Logger = Logger{} + +// Logger satisfies [log.Logger] with logging backed by +// an instance of [*slog.Logger]. +type Logger struct { + log *slog.Logger +} + +// FromSlog returns a Logger backed by an existing slog.Logger instance. +func FromSlog(log *slog.Logger) Logger { + return Logger{log: log} +} + +func (l Logger) Info(msg string, keyVals ...any) { + l.log.Info(msg, keyVals...) +} + +func (l Logger) Warn(msg string, keyVals ...any) { + l.log.Warn(msg, keyVals...) +} + +func (l Logger) Error(msg string, keyVals ...any) { + l.log.Error(msg, keyVals...) +} + +func (l Logger) Debug(msg string, keyVals ...any) { + l.log.Debug(msg, keyVals...) +} + +func (l Logger) With(keyVals ...any) log.Logger { + return Logger{log: l.log.With(keyVals...)} +} + +// Impl returns l's underlying [*slog.Logger]. +func (l Logger) Impl() any { + return l.log +} diff --git a/log/slog/logger_test.go b/log/slog/logger_test.go new file mode 100644 index 000000000000..2f851e0031d6 --- /dev/null +++ b/log/slog/logger_test.go @@ -0,0 +1,62 @@ +package slog_test + +import ( + "bytes" + "encoding/json" + stdslog "log/slog" + "testing" + + "cosmossdk.io/log/slog" + "github.com/stretchr/testify/require" +) + +func TestSlog(t *testing.T) { + var buf bytes.Buffer + h := stdslog.NewJSONHandler(&buf, &stdslog.HandlerOptions{ + Level: stdslog.LevelDebug, + }) + logger := slog.FromSlog(stdslog.New(h)) + + type logLine struct { + Level string `json:"level"` + Msg string `json:"msg"` + Num int `json:"num"` + } + + var line logLine + + logger.Debug("Message one", "num", 1) + require.NoError(t, json.Unmarshal(buf.Bytes(), &line)) + require.Equal(t, logLine{ + Level: stdslog.LevelDebug.String(), + Msg: "Message one", + Num: 1, + }, line) + + buf.Reset() + logger.Info("Message two", "num", 2) + require.NoError(t, json.Unmarshal(buf.Bytes(), &line)) + require.Equal(t, logLine{ + Level: stdslog.LevelInfo.String(), + Msg: "Message two", + Num: 2, + }, line) + + buf.Reset() + logger.Warn("Message three", "num", 3) + require.NoError(t, json.Unmarshal(buf.Bytes(), &line)) + require.Equal(t, logLine{ + Level: stdslog.LevelWarn.String(), + Msg: "Message three", + Num: 3, + }, line) + + buf.Reset() + logger.Error("Message four", "num", 4) + require.NoError(t, json.Unmarshal(buf.Bytes(), &line)) + require.Equal(t, logLine{ + Level: stdslog.LevelError.String(), + Msg: "Message four", + Num: 4, + }, line) +} From cf20016a2e3dabc15f19a8b8eb4d42234de6a2db Mon Sep 17 00:00:00 2001 From: Mark Rushakoff Date: Wed, 23 Oct 2024 11:46:01 -0400 Subject: [PATCH 2/4] chore: update changelog --- log/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/log/CHANGELOG.md b/log/CHANGELOG.md index 72b4e6d1adf7..f3ddac1f50b8 100644 --- a/log/CHANGELOG.md +++ b/log/CHANGELOG.md @@ -25,6 +25,7 @@ Each entry must include the Github issue reference in the following format: ### Improvements * [#22233](https://github.com/cosmos/cosmos-sdk/pull/22233) Use sonic json library for faster json handling +* [#22347](https://github.com/cosmos/cosmos-sdk/pull/22347) Add cosmossdk.io/log/slog to allow using a standard library log/slog-backed logger. ## [v1.4.1](https://github.com/cosmos/cosmos-sdk/releases/tag/log/v1.4.1) - 2024-08-16 From a078c3951e7aeef0f31985b0b09a6fe964d56aee Mon Sep 17 00:00:00 2001 From: Mark Rushakoff Date: Wed, 23 Oct 2024 11:56:39 -0400 Subject: [PATCH 3/4] test: use standard library testing package --- log/go.mod | 4 ---- log/go.sum | 1 - log/slog/logger_test.go | 41 ++++++++++++++++++++++++++++------------- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/log/go.mod b/log/go.mod index 1f75339ebc37..292e5d973e6f 100644 --- a/log/go.mod +++ b/log/go.mod @@ -6,20 +6,16 @@ require ( github.com/bytedance/sonic v1.12.3 github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.33.0 - github.com/stretchr/testify v1.8.1 ) require ( github.com/bytedance/sonic/loader v0.2.0 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/sys v0.22.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/log/go.sum b/log/go.sum index ead16b768f6e..b6a141763c97 100644 --- a/log/go.sum +++ b/log/go.sum @@ -45,7 +45,6 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/log/slog/logger_test.go b/log/slog/logger_test.go index 2f851e0031d6..b4e435f5751c 100644 --- a/log/slog/logger_test.go +++ b/log/slog/logger_test.go @@ -7,7 +7,6 @@ import ( "testing" "cosmossdk.io/log/slog" - "github.com/stretchr/testify/require" ) func TestSlog(t *testing.T) { @@ -26,37 +25,53 @@ func TestSlog(t *testing.T) { var line logLine logger.Debug("Message one", "num", 1) - require.NoError(t, json.Unmarshal(buf.Bytes(), &line)) - require.Equal(t, logLine{ + if err := json.Unmarshal(buf.Bytes(), &line); err != nil { + t.Fatal(err) + } + if want := (logLine{ Level: stdslog.LevelDebug.String(), Msg: "Message one", Num: 1, - }, line) + }); want != line { + t.Fatalf("unexpected log record: want %v, got %v", want, line) + } buf.Reset() logger.Info("Message two", "num", 2) - require.NoError(t, json.Unmarshal(buf.Bytes(), &line)) - require.Equal(t, logLine{ + if err := json.Unmarshal(buf.Bytes(), &line); err != nil { + t.Fatal(err) + } + if want := (logLine{ Level: stdslog.LevelInfo.String(), Msg: "Message two", Num: 2, - }, line) + }); want != line { + t.Fatalf("unexpected log record: want %v, got %v", want, line) + } buf.Reset() logger.Warn("Message three", "num", 3) - require.NoError(t, json.Unmarshal(buf.Bytes(), &line)) - require.Equal(t, logLine{ + if err := json.Unmarshal(buf.Bytes(), &line); err != nil { + t.Fatal(err) + } + if want := (logLine{ Level: stdslog.LevelWarn.String(), Msg: "Message three", Num: 3, - }, line) + }); want != line { + t.Fatalf("unexpected log record: want %v, got %v", want, line) + } buf.Reset() logger.Error("Message four", "num", 4) - require.NoError(t, json.Unmarshal(buf.Bytes(), &line)) - require.Equal(t, logLine{ + if err := json.Unmarshal(buf.Bytes(), &line); err != nil { + t.Fatal(err) + } + if want := (logLine{ Level: stdslog.LevelError.String(), Msg: "Message four", Num: 4, - }, line) + }); want != line { + t.Fatalf("unexpected log record: want %v, got %v", want, line) + } } From 8f1f86e9274ca7b8db000fe2f31046c941f918c5 Mon Sep 17 00:00:00 2001 From: Mark Rushakoff Date: Thu, 24 Oct 2024 10:10:57 -0400 Subject: [PATCH 4/4] chore: rename FromSlog to NewCustomLogger This is more in line with the existing zerolog-focused API. Also update a couple more comments, and add a test for the With method. --- log/README.md | 2 +- log/slog/logger.go | 7 +++++-- log/slog/logger_test.go | 17 ++++++++++++++++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/log/README.md b/log/README.md index 83b7ef0ce680..2dd94366a29b 100644 --- a/log/README.md +++ b/log/README.md @@ -2,4 +2,4 @@ The `cosmossdk.io/log` provides a zerolog logging implementation for the Cosmos SDK and Cosmos SDK modules. -To use a logger backed by an instance of the standard library's `log/slog` package, use `cosmossdk.io/log/slog`. +To use a logger wrapping an instance of the standard library's `log/slog` package, use `cosmossdk.io/log/slog`. diff --git a/log/slog/logger.go b/log/slog/logger.go index 1464ff8990c3..4b6c30ec4b97 100644 --- a/log/slog/logger.go +++ b/log/slog/logger.go @@ -16,8 +16,11 @@ type Logger struct { log *slog.Logger } -// FromSlog returns a Logger backed by an existing slog.Logger instance. -func FromSlog(log *slog.Logger) Logger { +// NewCustomLogger returns a Logger backed by an existing slog.Logger instance. +// All logging methods are called directly on the *slog.Logger; +// therefore it is the caller's responsibility to configure message filtering, +// level filtering, output format, and so on. +func NewCustomLogger(log *slog.Logger) Logger { return Logger{log: log} } diff --git a/log/slog/logger_test.go b/log/slog/logger_test.go index b4e435f5751c..4a7a9d836eac 100644 --- a/log/slog/logger_test.go +++ b/log/slog/logger_test.go @@ -14,7 +14,7 @@ func TestSlog(t *testing.T) { h := stdslog.NewJSONHandler(&buf, &stdslog.HandlerOptions{ Level: stdslog.LevelDebug, }) - logger := slog.FromSlog(stdslog.New(h)) + logger := slog.NewCustomLogger(stdslog.New(h)) type logLine struct { Level string `json:"level"` @@ -74,4 +74,19 @@ func TestSlog(t *testing.T) { }); want != line { t.Fatalf("unexpected log record: want %v, got %v", want, line) } + + wLogger := logger.With("num", 5) + buf.Reset() + wLogger.Info("Using .With") + + if err := json.Unmarshal(buf.Bytes(), &line); err != nil { + t.Fatal(err) + } + if want := (logLine{ + Level: stdslog.LevelInfo.String(), + Msg: "Using .With", + Num: 5, + }); want != line { + t.Fatalf("unexpected log record: want %v, got %v", want, line) + } }