From 1b6f4d6a63728342d63c793462bdae2a2faae77a Mon Sep 17 00:00:00 2001 From: Madhur Date: Mon, 2 Dec 2024 21:55:36 +0530 Subject: [PATCH 01/17] replacing logrus with slog v1 --- cmd/proxy/actions/app.go | 4 +- cmd/proxy/actions/app_proxy.go | 2 +- cmd/proxy/actions/basicauth_test.go | 5 +- cmd/proxy/main.go | 31 ++++------ pkg/download/protocol.go | 5 +- pkg/download/protocol_test.go | 4 ++ pkg/errors/errors.go | 19 +++--- pkg/log/entry.go | 30 ++++++---- pkg/log/format.go | 92 +++++++++++++++-------------- pkg/log/log.go | 49 +++++++++++---- pkg/log/log_context.go | 5 +- pkg/log/log_test.go | 40 ++++++------- pkg/middleware/log_entry.go | 11 ++-- pkg/middleware/log_entry_test.go | 28 ++++++--- 14 files changed, 183 insertions(+), 142 deletions(-) diff --git a/cmd/proxy/actions/app.go b/cmd/proxy/actions/app.go index acc686710..d2ee7b03b 100644 --- a/cmd/proxy/actions/app.go +++ b/cmd/proxy/actions/app.go @@ -77,7 +77,7 @@ func App(logger *log.Logger, conf *config.Config) (http.Handler, error) { conf.GoEnv, ) if err != nil { - logger.Info(err) + logger.Info(err.Error()) } else { defer flushTraces() } @@ -87,7 +87,7 @@ func App(logger *log.Logger, conf *config.Config) (http.Handler, error) { // was specified by the user. flushStats, err := observ.RegisterStatsExporter(r, conf.StatsExporter, Service) if err != nil { - logger.Info(err) + logger.Info(err.Error()) } else { defer flushStats() } diff --git a/cmd/proxy/actions/app_proxy.go b/cmd/proxy/actions/app_proxy.go index 86c2a2478..47ad60ac8 100644 --- a/cmd/proxy/actions/app_proxy.go +++ b/cmd/proxy/actions/app_proxy.go @@ -134,7 +134,7 @@ type athensLoggerForRedis struct { } func (l *athensLoggerForRedis) Printf(ctx context.Context, format string, v ...any) { - l.logger.WithContext(ctx).Printf(format, v...) + l.logger.WithContext(ctx).Infof(format, v...) } func getSingleFlight(l *log.Logger, c *config.Config, s storage.Backend, checker storage.Checker) (stash.Wrapper, error) { diff --git a/cmd/proxy/actions/basicauth_test.go b/cmd/proxy/actions/basicauth_test.go index 54e72e026..57515c8c5 100644 --- a/cmd/proxy/actions/basicauth_test.go +++ b/cmd/proxy/actions/basicauth_test.go @@ -8,8 +8,9 @@ import ( "strings" "testing" + "log/slog" + "github.com/gomods/athens/pkg/log" - "github.com/sirupsen/logrus" ) var basicAuthTests = [...]struct { @@ -70,7 +71,7 @@ func TestBasicAuth(t *testing.T) { w := httptest.NewRecorder() r := httptest.NewRequest(http.MethodGet, tc.path, nil) r.SetBasicAuth(tc.user, tc.pass) - lggr := log.New("none", logrus.DebugLevel, "") + lggr := log.New("none", slog.LevelDebug, "") buf := &bytes.Buffer{} lggr.Out = buf ctx := log.SetEntryInContext(context.Background(), lggr) diff --git a/cmd/proxy/main.go b/cmd/proxy/main.go index d139d715e..a7d8d1e2e 100644 --- a/cmd/proxy/main.go +++ b/cmd/proxy/main.go @@ -6,6 +6,7 @@ import ( "flag" "fmt" stdlog "log" + "log/slog" "net" "net/http" _ "net/http/pprof" @@ -18,7 +19,6 @@ import ( "github.com/gomods/athens/pkg/build" "github.com/gomods/athens/pkg/config" athenslog "github.com/gomods/athens/pkg/log" - "github.com/sirupsen/logrus" ) var ( @@ -37,26 +37,17 @@ func main() { stdlog.Fatalf("Could not load config file: %v", err) } - logLvl, err := logrus.ParseLevel(conf.LogLevel) + logLvl := slog.Level(0) + err = logLvl.UnmarshalText([]byte(conf.LogLevel)) if err != nil { stdlog.Fatalf("Could not parse log level %q: %v", conf.LogLevel, err) } logger := athenslog.New(conf.CloudRuntime, logLvl, conf.LogFormat) - // Turn standard logger output into logrus Errors. - logrusErrorWriter := logger.WriterLevel(logrus.ErrorLevel) - defer func() { - if err := logrusErrorWriter.Close(); err != nil { - logger.WithError(err).Warn("Could not close logrus writer pipe") - } - }() - stdlog.SetOutput(logrusErrorWriter) - stdlog.SetFlags(stdlog.Flags() &^ (stdlog.Ldate | stdlog.Ltime)) - handler, err := actions.App(logger, conf) if err != nil { - logger.WithError(err).Fatal("Could not create App") + logger.With(err.Error()).Error("Could not create App") } srv := &http.Server{ @@ -75,7 +66,7 @@ func main() { ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(conf.ShutdownTimeout)) defer cancel() if err := srv.Shutdown(ctx); err != nil { - logger.WithError(err).Fatal("Could not shut down server") + logger.With(err).Error("Could not shut down server") } close(idleConnsClosed) }() @@ -86,7 +77,7 @@ func main() { // not to expose profiling data and avoid DoS attacks (profiling slows down the service) // https://www.farsightsecurity.com/txt-record/2016/10/28/cmikk-go-remote-profiling/ logger.WithField("port", conf.PprofPort).Infof("starting pprof") - logger.Fatal(http.ListenAndServe(conf.PprofPort, nil)) //nolint:gosec // This should not be exposed to the world. + logger.Error(http.ListenAndServe(conf.PprofPort, nil).Error()) //nolint:gosec // This should not be exposed to the world. }() } @@ -95,19 +86,19 @@ func main() { if conf.UnixSocket != "" { logger := logger.WithField("unixSocket", conf.UnixSocket) - logger.Info("Starting application") + logger.Infof("Starting application") ln, err = net.Listen("unix", conf.UnixSocket) if err != nil { - logger.WithError(err).Fatal("Could not listen on Unix domain socket") + logger.WithError(err).Fatalf("Could not listen on Unix domain socket") } } else { logger := logger.WithField("tcpPort", conf.Port) - logger.Info("Starting application") + logger.Infof("Starting application") ln, err = net.Listen("tcp", conf.Port) if err != nil { - logger.WithError(err).Fatal("Could not listen on TCP port") + logger.WithError(err).Fatalf("Could not listen on TCP port") } } @@ -118,7 +109,7 @@ func main() { } if !errors.Is(err, http.ErrServerClosed) { - logger.WithError(err).Fatal("Could not start server") + logger.WithError(err).Fatalf("Could not start server") } <-idleConnsClosed diff --git a/pkg/download/protocol.go b/pkg/download/protocol.go index a6942a9c7..c4cc76911 100644 --- a/pkg/download/protocol.go +++ b/pkg/download/protocol.go @@ -299,6 +299,9 @@ func union(list1, list2 []string) []string { func copyContextWithCustomTimeout(ctx context.Context, timeout time.Duration) (context.Context, context.CancelFunc) { ctxCopy, cancel := context.WithTimeout(context.Background(), timeout) ctxCopy = requestid.SetInContext(ctxCopy, requestid.FromContext(ctx)) - ctxCopy = log.SetEntryInContext(ctxCopy, log.EntryFromContext(ctx)) + + if entry := log.EntryFromContext(ctx); entry != nil { + ctxCopy = log.SetEntryInContext(ctxCopy, &entry) + } return ctxCopy, cancel } diff --git a/pkg/download/protocol_test.go b/pkg/download/protocol_test.go index d7070e013..5037eae22 100644 --- a/pkg/download/protocol_test.go +++ b/pkg/download/protocol_test.go @@ -507,8 +507,12 @@ func (e *testEntry) Debugf(format string, args ...any) { func (*testEntry) Infof(format string, args ...any) {} func (*testEntry) Warnf(format string, args ...any) {} func (*testEntry) Errorf(format string, args ...any) {} +func (*testEntry) Fatalf(format string, args ...any) {} func (*testEntry) WithFields(fields map[string]any) log.Entry { return nil } func (*testEntry) SystemErr(err error) {} +func (*testEntry) WithContext(ctx context.Context) log.Entry { return nil } +func (*testEntry) WithError(err error) log.Entry { return nil } +func (*testEntry) WithField(key string, value any) log.Entry { return nil } func Test_copyContextWithCustomTimeout(t *testing.T) { testEntry := &testEntry{} diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go index b2378b19d..189ce9622 100644 --- a/pkg/errors/errors.go +++ b/pkg/errors/errors.go @@ -3,10 +3,9 @@ package errors import ( "errors" "fmt" + "log/slog" "net/http" "runtime" - - "github.com/sirupsen/logrus" ) // Kind enums. @@ -37,7 +36,7 @@ type Error struct { Module M Version V Err error - Severity logrus.Level + Severity slog.Level } // Error returns the underlying error's @@ -111,7 +110,7 @@ func E(op Op, args ...any) error { e.Module = a case V: e.Version = a - case logrus.Level: + case slog.Level: e.Severity = a case int: e.Kind = a @@ -126,17 +125,17 @@ func E(op Op, args ...any) error { // Severity returns the log level of an error // if none exists, then the level is Error because // it is an unexpected. -func Severity(err error) logrus.Level { +func Severity(err error) slog.Level { var e Error if !errors.As(err, &e) { - return logrus.ErrorLevel + return slog.LevelError } // if there's no severity (0 is Panic level in logrus // which we should not use since cloud providers only have // debug, info, warn, and error) then look for the // child's severity. - if e.Severity < logrus.ErrorLevel { + if e.Severity < slog.LevelError { return Severity(e.Err) } @@ -146,13 +145,13 @@ func Severity(err error) logrus.Level { // Expect is a helper that returns an Info level // if the error has the expected kind, otherwise // it returns an Error level. -func Expect(err error, kinds ...int) logrus.Level { +func Expect(err error, kinds ...int) slog.Level { for _, kind := range kinds { if Kind(err) == kind { - return logrus.InfoLevel + return slog.LevelInfo } } - return logrus.ErrorLevel + return slog.LevelError } // Kind recursively searches for the diff --git a/pkg/log/entry.go b/pkg/log/entry.go index da0869803..221ba43d6 100644 --- a/pkg/log/entry.go +++ b/pkg/log/entry.go @@ -1,8 +1,10 @@ package log import ( + "context" + "log/slog" + "github.com/gomods/athens/pkg/errors" - "github.com/sirupsen/logrus" ) // Entry is an abstraction to the @@ -16,46 +18,52 @@ type Entry interface { Infof(format string, args ...any) Warnf(format string, args ...any) Errorf(format string, args ...any) + Fatalf(format string, args ...any) // Attach contextual information to the logging entry WithFields(fields map[string]any) Entry + WithField(key string, value any) Entry + + WithError(err error) Entry + + WithContext(ctx context.Context) Entry + // SystemErr is a method that disects the error // and logs the appropriate level and fields for it. SystemErr(err error) } type entry struct { - *logrus.Entry + *slog.Logger } func (e *entry) WithFields(fields map[string]any) Entry { - ent := e.Entry.WithFields(fields) - return &entry{ent} + ent := e.WithFields(fields) + return ent } func (e *entry) SystemErr(err error) { var athensErr errors.Error if !errors.AsErr(err, &athensErr) { - e.Error(err) + e.Error(err.Error()) return } ent := e.WithFields(errFields(athensErr)) switch errors.Severity(err) { - case logrus.WarnLevel: + case slog.LevelWarn: ent.Warnf("%v", err) - case logrus.InfoLevel: + case slog.LevelInfo: ent.Infof("%v", err) - case logrus.DebugLevel: + case slog.LevelDebug: ent.Debugf("%v", err) default: ent.Errorf("%v", err) } } - -func errFields(err errors.Error) logrus.Fields { - f := logrus.Fields{} +func errFields(err errors.Error) map[string]any { + f := map[string]any{} f["operation"] = err.Op f["kind"] = errors.KindText(err) f["module"] = err.Module diff --git a/pkg/log/format.go b/pkg/log/format.go index 4db34ae19..13f373e7b 100644 --- a/pkg/log/format.go +++ b/pkg/log/format.go @@ -1,62 +1,64 @@ package log import ( - "bytes" - "fmt" + "log/slog" + "os" "sort" - "strings" "time" "github.com/fatih/color" - "github.com/sirupsen/logrus" ) -func getGCPFormatter() logrus.Formatter { - return &logrus.JSONFormatter{ - FieldMap: logrus.FieldMap{ - logrus.FieldKeyLevel: "severity", - logrus.FieldKeyMsg: "message", - logrus.FieldKeyTime: "timestamp", - }, - } -} +func getGCPFormatter(level slog.Level) *slog.Logger { -func getDevFormatter() logrus.Formatter { - return devFormatter{} + l := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: level})) + return l } +const lightGrey = 0xffccc + type devFormatter struct{} -const lightGrey = 0xffccc +func getDevFormatter(level slog.Level) *slog.Logger { + return slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + Level: level, + ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { + if a.Key == slog.TimeKey { + t := a.Value.Time() + return slog.String(slog.TimeKey, t.Format(time.Kitchen)) + } + if a.Key == slog.LevelKey { + level := a.Value.Any().(slog.Level) + var colored string + switch level { + case slog.LevelDebug: + colored = color.New(lightGrey).Sprint(level) + case slog.LevelWarn: + colored = color.YellowString(level.String()) + case slog.LevelError: + colored = color.RedString(level.String()) + default: + colored = color.CyanString(level.String()) + } + return slog.String(slog.LevelKey, colored) + } + if len(groups) == 0 { + return slog.Attr{ + Key: color.MagentaString(a.Key), + Value: a.Value, + } + } + return a + }, + })) +} -func (devFormatter) Format(e *logrus.Entry) ([]byte, error) { - var buf bytes.Buffer - var sprintf func(format string, a ...any) string - switch e.Level { - case logrus.DebugLevel: - sprintf = color.New(lightGrey).Sprintf - case logrus.WarnLevel: - sprintf = color.YellowString - case logrus.ErrorLevel: - sprintf = color.RedString - default: - sprintf = color.CyanString +func sortFields(data map[string]any) []string { + if data == nil { + return nil } - lvl := strings.ToUpper(e.Level.String()) - buf.WriteString(sprintf(lvl)) - buf.WriteString("[" + e.Time.Format(time.Kitchen) + "]") - buf.WriteString(": ") - buf.WriteString(e.Message) - buf.WriteByte('\t') - for _, k := range sortFields(e.Data) { - fmt.Fprintf(&buf, "%s=%s ", color.MagentaString(k), e.Data[k]) - } - buf.WriteByte('\n') - return buf.Bytes(), nil -} -func sortFields(data logrus.Fields) []string { - keys := []string{} + keys := make([]string, 0, len(data)) for k := range data { keys = append(keys, k) } @@ -64,10 +66,10 @@ func sortFields(data logrus.Fields) []string { return keys } -func parseFormat(format string) logrus.Formatter { +func parseFormat(format string, level slog.Level) *slog.Logger { if format == "json" { - return &logrus.JSONFormatter{} + return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})) } - return getDevFormatter() + return getDevFormatter(level) } diff --git a/pkg/log/log.go b/pkg/log/log.go index 8013d4b74..421b665d2 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -1,47 +1,70 @@ package log import ( - "github.com/sirupsen/logrus" + "bytes" + "context" + "log/slog" + "os" ) // Logger is the main struct that any athens // internal service should use to communicate things. type Logger struct { - *logrus.Logger + *slog.Logger + + Out *bytes.Buffer } // New constructs a new logger based on the // environment and the cloud platform it is // running on. TODO: take cloud arg and env // to construct the correct JSON formatter. -func New(cloudProvider string, level logrus.Level, format string) *Logger { - l := logrus.New() +func New(cloudProvider string, level slog.Level, format string) *Logger { + l := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: level})) switch cloudProvider { case "GCP": - l.Formatter = getGCPFormatter() + l = getGCPFormatter(level) default: - l.Formatter = parseFormat(format) + l = parseFormat(format, level) } - l.Level = level + slog.SetDefault(l) return &Logger{Logger: l} } // SystemErr Entry implementation. func (l *Logger) SystemErr(err error) { - e := &entry{Entry: logrus.NewEntry(l.Logger)} + e := &entry{Logger: l.Logger} e.SystemErr(err) } // WithFields Entry implementation. func (l *Logger) WithFields(fields map[string]any) Entry { - e := l.Logger.WithFields(fields) + return l.WithFields(fields) +} + +func (l *Logger) WithField(key string, value any) Entry { + keys := map[string]any{ + key: value, + } + return l.WithFields(keys) +} - return &entry{e} +func (l *Logger) WithError(err error) Entry { + keys := map[string]any{ + "error": err, + } + return l.WithFields(keys) +} + +func (l *Logger) WithContext(ctx context.Context) Entry { + keys := map[string]any{ + "context": ctx, + } + return l.WithFields(keys) } // NoOpLogger provides a Logger that does nothing. func NoOpLogger() *Logger { - return &Logger{ - Logger: &logrus.Logger{}, - } + l := slog.New(slog.NewTextHandler(os.Stdout, nil)) + return &Logger{Logger: l} } diff --git a/pkg/log/log_context.go b/pkg/log/log_context.go index fd36069de..176ad4d22 100644 --- a/pkg/log/log_context.go +++ b/pkg/log/log_context.go @@ -9,7 +9,7 @@ type ctxKey string const logEntryKey ctxKey = "log-entry-context-key" // SetEntryInContext stores an Entry in the request context. -func SetEntryInContext(ctx context.Context, e Entry) context.Context { +func SetEntryInContext(ctx context.Context, e *Entry) context.Context { return context.WithValue(ctx, logEntryKey, e) } @@ -19,7 +19,8 @@ func SetEntryInContext(ctx context.Context, e Entry) context.Context { func EntryFromContext(ctx context.Context) Entry { e, ok := ctx.Value(logEntryKey).(Entry) if !ok || e == nil { - return NoOpLogger() + //return a new entry + return &Entry{} } return e } diff --git a/pkg/log/log_test.go b/pkg/log/log_test.go index 2639c7a12..9819fd0be 100644 --- a/pkg/log/log_test.go +++ b/pkg/log/log_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "github.com/sirupsen/logrus" + "log/slog" "github.com/stretchr/testify/require" ) @@ -15,8 +15,8 @@ type input struct { name string cloudProvider string format string - level logrus.Level - fields logrus.Fields + level slog.Level + fields map[string]any logFunc func(e Entry) time.Time output string } @@ -25,8 +25,8 @@ var testCases = []input{ { name: "gcp_debug", cloudProvider: "GCP", - level: logrus.DebugLevel, - fields: logrus.Fields{}, + level: slog.LevelDebug, + fields: map[string]any{}, logFunc: func(e Entry) time.Time { t := time.Now() e.Infof("info message") @@ -37,8 +37,8 @@ var testCases = []input{ { name: "gcp_error", cloudProvider: "GCP", - level: logrus.DebugLevel, - fields: logrus.Fields{}, + level: slog.LevelDebug, + fields: map[string]any{}, logFunc: func(e Entry) time.Time { t := time.Now() e.Errorf("err message") @@ -49,8 +49,8 @@ var testCases = []input{ { name: "gcp_empty", cloudProvider: "GCP", - level: logrus.ErrorLevel, - fields: logrus.Fields{}, + level: slog.LevelError, + fields: map[string]any{}, logFunc: func(e Entry) time.Time { t := time.Now() e.Infof("info message") @@ -61,8 +61,8 @@ var testCases = []input{ { name: "gcp_fields", cloudProvider: "GCP", - level: logrus.DebugLevel, - fields: logrus.Fields{"field1": "value1", "field2": 2}, + level: slog.LevelDebug, + fields: map[string]any{"field1": "value1", "field2": 2}, logFunc: func(e Entry) time.Time { t := time.Now() e.Debugf("debug message") @@ -73,8 +73,8 @@ var testCases = []input{ { name: "gcp_logs", cloudProvider: "GCP", - level: logrus.DebugLevel, - fields: logrus.Fields{}, + level: slog.LevelDebug, + fields: map[string]any{}, logFunc: func(e Entry) time.Time { t := time.Now() e.Warnf("warn message") @@ -86,8 +86,8 @@ var testCases = []input{ name: "default plain", format: "plain", cloudProvider: "none", - level: logrus.DebugLevel, - fields: logrus.Fields{"xyz": "abc", "abc": "xyz"}, + level: slog.LevelDebug, + fields: map[string]any{"xyz": "abc", "abc": "xyz"}, logFunc: func(e Entry) time.Time { t := time.Now() e.Warnf("warn message") @@ -98,8 +98,8 @@ var testCases = []input{ { name: "default", cloudProvider: "none", - level: logrus.DebugLevel, - fields: logrus.Fields{"xyz": "abc", "abc": "xyz"}, + level: slog.LevelDebug, + fields: map[string]any{"xyz": "abc", "abc": "xyz"}, logFunc: func(e Entry) time.Time { t := time.Now() e.Warnf("warn message") @@ -111,8 +111,8 @@ var testCases = []input{ name: "default json", format: "json", cloudProvider: "none", - level: logrus.DebugLevel, - fields: logrus.Fields{"xyz": "abc", "abc": "xyz"}, + level: slog.LevelDebug, + fields: map[string]any{"xyz": "abc", "abc": "xyz"}, logFunc: func(e Entry) time.Time { t := time.Now() e.Warnf("warn message") @@ -147,5 +147,5 @@ func TestCloudLogger(t *testing.T) { func TestNoOpLogger(t *testing.T) { l := NoOpLogger() - require.NotPanics(t, func() { l.Infof("test") }) + require.NotPanics(t, func() { l.Info("test") }) } diff --git a/pkg/middleware/log_entry.go b/pkg/middleware/log_entry.go index 8d6016cf7..00bc2e6e3 100644 --- a/pkg/middleware/log_entry.go +++ b/pkg/middleware/log_entry.go @@ -6,7 +6,6 @@ import ( "github.com/gomods/athens/pkg/log" "github.com/gomods/athens/pkg/requestid" "github.com/gorilla/mux" - "github.com/sirupsen/logrus" ) // LogEntryMiddleware builds a log.Entry, setting the request fields @@ -15,11 +14,11 @@ func LogEntryMiddleware(lggr *log.Logger) mux.MiddlewareFunc { return func(h http.Handler) http.Handler { f := func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - ent := lggr.WithFields(logrus.Fields{ - "http-method": r.Method, - "http-path": r.URL.Path, - "request-id": requestid.FromContext(ctx), - }) + ent := lggr.With( + "http-method", r.Method, + "http-path", r.URL.Path, + "request-id", requestid.FromContext(ctx), + ) ctx = log.SetEntryInContext(ctx, ent) r = r.WithContext(ctx) h.ServeHTTP(w, r) diff --git a/pkg/middleware/log_entry_test.go b/pkg/middleware/log_entry_test.go index 2135748fd..835b7e528 100644 --- a/pkg/middleware/log_entry_test.go +++ b/pkg/middleware/log_entry_test.go @@ -2,15 +2,14 @@ package middleware import ( "bytes" - "fmt" + "encoding/json" + "log/slog" "net/http" "net/http/httptest" - "strings" "testing" "github.com/gomods/athens/pkg/log" "github.com/gorilla/mux" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" ) @@ -23,10 +22,8 @@ func TestLogContext(t *testing.T) { r := mux.NewRouter() r.HandleFunc("/test", h) - var buf bytes.Buffer - lggr := log.New("", logrus.DebugLevel, "") - lggr.Formatter = &logrus.JSONFormatter{DisableTimestamp: true} - lggr.Out = &buf + buf := new(bytes.Buffer) + lggr := log.New("", slog.LevelInfo, "") r.Use(LogEntryMiddleware(lggr)) @@ -34,6 +31,19 @@ func TestLogContext(t *testing.T) { req, _ := http.NewRequest("GET", "/test", nil) r.ServeHTTP(w, req) - expected := `{"http-method":"GET","http-path":"/test","level":"info","msg":"test","request-id":""}` - assert.True(t, strings.Contains(buf.String(), expected), fmt.Sprintf("%s should contain: %s", buf.String(), expected)) + var logEntry map[string]interface{} + err := json.Unmarshal(buf.Bytes(), &logEntry) + assert.NoError(t, err) + + expectedFields := map[string]interface{}{ + "level": "INFO", + "msg": "test", + "http-method": "GET", + "http-path": "/test", + "request-id": "", + } + + for k, v := range expectedFields { + assert.Equal(t, v, logEntry[k], "Log entry should contain %s with value %v", k, v) + } } From bd63c16805a7d7de166aaf3e03f752eb5d80040c Mon Sep 17 00:00:00 2001 From: Madhur Date: Wed, 4 Dec 2024 23:34:54 +0530 Subject: [PATCH 02/17] modifying entry interface --- cmd/proxy/actions/app_proxy.go | 2 +- cmd/proxy/actions/basicauth_test.go | 3 +- cmd/proxy/main.go | 29 +++-- pkg/download/protocol.go | 5 +- pkg/download/protocol_test.go | 31 ++++-- pkg/errors/errors.go | 8 +- pkg/log/entry.go | 165 +++++++++++++++++++++++----- pkg/log/log.go | 28 ++++- pkg/log/log_context.go | 5 +- pkg/middleware/log_entry.go | 11 +- pkg/middleware/log_entry_test.go | 27 ++--- 11 files changed, 227 insertions(+), 87 deletions(-) diff --git a/cmd/proxy/actions/app_proxy.go b/cmd/proxy/actions/app_proxy.go index 47ad60ac8..86c2a2478 100644 --- a/cmd/proxy/actions/app_proxy.go +++ b/cmd/proxy/actions/app_proxy.go @@ -134,7 +134,7 @@ type athensLoggerForRedis struct { } func (l *athensLoggerForRedis) Printf(ctx context.Context, format string, v ...any) { - l.logger.WithContext(ctx).Infof(format, v...) + l.logger.WithContext(ctx).Printf(format, v...) } func getSingleFlight(l *log.Logger, c *config.Config, s storage.Backend, checker storage.Checker) (stash.Wrapper, error) { diff --git a/cmd/proxy/actions/basicauth_test.go b/cmd/proxy/actions/basicauth_test.go index 57515c8c5..1f02f84fc 100644 --- a/cmd/proxy/actions/basicauth_test.go +++ b/cmd/proxy/actions/basicauth_test.go @@ -3,13 +3,12 @@ package actions import ( "bytes" "context" + "log/slog" "net/http" "net/http/httptest" "strings" "testing" - "log/slog" - "github.com/gomods/athens/pkg/log" ) diff --git a/cmd/proxy/main.go b/cmd/proxy/main.go index a7d8d1e2e..e56547c4d 100644 --- a/cmd/proxy/main.go +++ b/cmd/proxy/main.go @@ -37,7 +37,7 @@ func main() { stdlog.Fatalf("Could not load config file: %v", err) } - logLvl := slog.Level(0) + var logLvl slog.Level err = logLvl.UnmarshalText([]byte(conf.LogLevel)) if err != nil { stdlog.Fatalf("Could not parse log level %q: %v", conf.LogLevel, err) @@ -45,9 +45,19 @@ func main() { logger := athenslog.New(conf.CloudRuntime, logLvl, conf.LogFormat) + // Turn standard logger output into slog Errors. + logrusErrorWriter := logger.WriterLevel(slog.LevelError) + defer func() { + if err := logrusErrorWriter.Close(); err != nil { + logger.WithError(err).Warn("Could not close logrus writer pipe") + } + }() + stdlog.SetOutput(logrusErrorWriter) + stdlog.SetFlags(stdlog.Flags() &^ (stdlog.Ldate | stdlog.Ltime)) + handler, err := actions.App(logger, conf) if err != nil { - logger.With(err.Error()).Error("Could not create App") + logger.WithError(err).Fatal("Could not create App") } srv := &http.Server{ @@ -66,7 +76,7 @@ func main() { ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(conf.ShutdownTimeout)) defer cancel() if err := srv.Shutdown(ctx); err != nil { - logger.With(err).Error("Could not shut down server") + logger.WithError(err).Fatal("Could not shut down server") } close(idleConnsClosed) }() @@ -77,7 +87,7 @@ func main() { // not to expose profiling data and avoid DoS attacks (profiling slows down the service) // https://www.farsightsecurity.com/txt-record/2016/10/28/cmikk-go-remote-profiling/ logger.WithField("port", conf.PprofPort).Infof("starting pprof") - logger.Error(http.ListenAndServe(conf.PprofPort, nil).Error()) //nolint:gosec // This should not be exposed to the world. + logger.Fatal(http.ListenAndServe(conf.PprofPort, nil)) //nolint:gosec // This should not be exposed to the world. }() } @@ -86,19 +96,19 @@ func main() { if conf.UnixSocket != "" { logger := logger.WithField("unixSocket", conf.UnixSocket) - logger.Infof("Starting application") + logger.Info("Starting application") ln, err = net.Listen("unix", conf.UnixSocket) if err != nil { - logger.WithError(err).Fatalf("Could not listen on Unix domain socket") + logger.WithError(err).Fatal("Could not listen on Unix domain socket") } } else { logger := logger.WithField("tcpPort", conf.Port) - logger.Infof("Starting application") + logger.Info("Starting application") ln, err = net.Listen("tcp", conf.Port) if err != nil { - logger.WithError(err).Fatalf("Could not listen on TCP port") + logger.WithError(err).Fatal("Could not listen on TCP port") } } @@ -109,8 +119,9 @@ func main() { } if !errors.Is(err, http.ErrServerClosed) { - logger.WithError(err).Fatalf("Could not start server") + logger.WithError(err).Fatal("Could not start server") } <-idleConnsClosed + } diff --git a/pkg/download/protocol.go b/pkg/download/protocol.go index c4cc76911..a6942a9c7 100644 --- a/pkg/download/protocol.go +++ b/pkg/download/protocol.go @@ -299,9 +299,6 @@ func union(list1, list2 []string) []string { func copyContextWithCustomTimeout(ctx context.Context, timeout time.Duration) (context.Context, context.CancelFunc) { ctxCopy, cancel := context.WithTimeout(context.Background(), timeout) ctxCopy = requestid.SetInContext(ctxCopy, requestid.FromContext(ctx)) - - if entry := log.EntryFromContext(ctx); entry != nil { - ctxCopy = log.SetEntryInContext(ctxCopy, &entry) - } + ctxCopy = log.SetEntryInContext(ctxCopy, log.EntryFromContext(ctx)) return ctxCopy, cancel } diff --git a/pkg/download/protocol_test.go b/pkg/download/protocol_test.go index 5037eae22..e2d46ad2c 100644 --- a/pkg/download/protocol_test.go +++ b/pkg/download/protocol_test.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "io" + "log/slog" "os" "path/filepath" "regexp" @@ -504,15 +505,27 @@ var _ log.Entry = &testEntry{} func (e *testEntry) Debugf(format string, args ...any) { e.msg = format } -func (*testEntry) Infof(format string, args ...any) {} -func (*testEntry) Warnf(format string, args ...any) {} -func (*testEntry) Errorf(format string, args ...any) {} -func (*testEntry) Fatalf(format string, args ...any) {} -func (*testEntry) WithFields(fields map[string]any) log.Entry { return nil } -func (*testEntry) SystemErr(err error) {} -func (*testEntry) WithContext(ctx context.Context) log.Entry { return nil } -func (*testEntry) WithError(err error) log.Entry { return nil } -func (*testEntry) WithField(key string, value any) log.Entry { return nil } +func (*testEntry) Infof(format string, args ...any) {} +func (*testEntry) Warnf(format string, args ...any) {} +func (*testEntry) Errorf(format string, args ...any) {} +func (*testEntry) Fatalf(format string, args ...any) {} +func (*testEntry) Panicf(format string, args ...any) {} +func (*testEntry) Printf(format string, args ...any) {} + +func (*testEntry) Debug(args ...any) {} +func (*testEntry) Info(args ...any) {} +func (*testEntry) Warn(args ...any) {} +func (*testEntry) Error(args ...any) {} +func (*testEntry) Fatal(args ...any) {} +func (*testEntry) Panic(args ...any) {} +func (*testEntry) Print(args ...any) {} + +func (*testEntry) WithFields(fields map[string]any) log.Entry { return nil } +func (*testEntry) SystemErr(err error) {} +func (*testEntry) WithField(key string, value any) log.Entry { return nil } +func (*testEntry) WithError(err error) log.Entry { return nil } +func (*testEntry) WithContext(ctx context.Context) log.Entry { return nil } +func (*testEntry) WriterLevel(level slog.Level) *io.PipeWriter { return nil } func Test_copyContextWithCustomTimeout(t *testing.T) { testEntry := &testEntry{} diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go index 189ce9622..67799ea7b 100644 --- a/pkg/errors/errors.go +++ b/pkg/errors/errors.go @@ -6,6 +6,8 @@ import ( "log/slog" "net/http" "runtime" + + "github.com/sirupsen/logrus" ) // Kind enums. @@ -145,13 +147,13 @@ func Severity(err error) slog.Level { // Expect is a helper that returns an Info level // if the error has the expected kind, otherwise // it returns an Error level. -func Expect(err error, kinds ...int) slog.Level { +func Expect(err error, kinds ...int) logrus.Level { for _, kind := range kinds { if Kind(err) == kind { - return slog.LevelInfo + return logrus.InfoLevel } } - return slog.LevelError + return logrus.ErrorLevel } // Kind recursively searches for the diff --git a/pkg/log/entry.go b/pkg/log/entry.go index 221ba43d6..97cefb392 100644 --- a/pkg/log/entry.go +++ b/pkg/log/entry.go @@ -1,46 +1,64 @@ package log import ( + "bufio" "context" + "fmt" + "io" "log/slog" + "os" "github.com/gomods/athens/pkg/errors" ) -// Entry is an abstraction to the -// Logger and the logrus.Entry -// so that *Logger always creates -// an Entry copy which ensures no -// Fields are being overwritten. type Entry interface { - // Basic Logging Operation - Debugf(format string, args ...any) - Infof(format string, args ...any) - Warnf(format string, args ...any) - Errorf(format string, args ...any) - Fatalf(format string, args ...any) - - // Attach contextual information to the logging entry - WithFields(fields map[string]any) Entry + // Keep the existing interface methods unchanged + Debugf(string, ...interface{}) + Infof(string, ...interface{}) + Warnf(string, ...interface{}) + Errorf(string, ...interface{}) + Fatalf(string, ...interface{}) + Panicf(string, ...interface{}) + Printf(string, ...interface{}) - WithField(key string, value any) Entry + Debug(...interface{}) + Info(...interface{}) + Warn(...interface{}) + Error(...interface{}) + Fatal(...interface{}) + Panic(...interface{}) + Print(...interface{}) + WithFields(fields map[string]any) Entry + WithField(key string, value any) Entry WithError(err error) Entry - WithContext(ctx context.Context) Entry - - // SystemErr is a method that disects the error - // and logs the appropriate level and fields for it. SystemErr(err error) + WriterLevel(level slog.Level) *io.PipeWriter } type entry struct { - *slog.Logger + logger *slog.Logger } func (e *entry) WithFields(fields map[string]any) Entry { - ent := e.WithFields(fields) - return ent + attrs := make([]any, 0, len(fields)*2) + for k, v := range fields { + attrs = append(attrs, slog.Any(k, v)) + } + return &entry{logger: e.logger.With(attrs...)} +} + +func (e *entry) WithField(key string, value any) Entry { + return &entry{logger: e.logger.With(key, value)} +} + +func (e *entry) WithError(err error) Entry { + return &entry{logger: e.logger.With("error", err)} +} + +func (e *entry) WithContext(ctx context.Context) Entry { + return &entry{logger: e.logger.With("context", ctx)} } func (e *entry) SystemErr(err error) { @@ -62,13 +80,102 @@ func (e *entry) SystemErr(err error) { ent.Errorf("%v", err) } } -func errFields(err errors.Error) map[string]any { - f := map[string]any{} - f["operation"] = err.Op - f["kind"] = errors.KindText(err) - f["module"] = err.Module - f["version"] = err.Version - f["ops"] = errors.Ops(err) +func (e *entry) Debug(args ...interface{}) { + e.logger.Debug(fmt.Sprint(args...)) +} + +func (e *entry) Info(args ...interface{}) { + e.logger.Info(fmt.Sprint(args...)) +} + +func (e *entry) Warn(args ...interface{}) { + e.logger.Warn(fmt.Sprint(args...)) +} + +func (e *entry) Error(args ...interface{}) { + e.logger.Error(fmt.Sprint(args...)) +} + +func (e *entry) Fatal(args ...interface{}) { + e.logger.Error(fmt.Sprint(args...)) // slog doesn't have Fatal, using Error +} + +func (e *entry) Panic(args ...interface{}) { + e.logger.Error(fmt.Sprint(args...)) // slog doesn't have Panic, using Error +} + +func (e *entry) Print(args ...interface{}) { + e.logger.Info(fmt.Sprint(args...)) +} + +func (e *entry) Debugf(format string, args ...interface{}) { + e.logger.Debug(fmt.Sprintf(format, args...)) +} + +func (e *entry) Infof(format string, args ...interface{}) { + e.logger.Info(fmt.Sprintf(format, args...)) +} + +func (e *entry) Warnf(format string, args ...interface{}) { + e.logger.Warn(fmt.Sprintf(format, args...)) +} + +func (e *entry) Errorf(format string, args ...interface{}) { + e.logger.Error(fmt.Sprintf(format, args...)) +} + +func (e *entry) Fatalf(format string, args ...interface{}) { + e.logger.Error(fmt.Sprintf(format, args...)) + os.Exit(1) +} + +func (e *entry) Panicf(format string, args ...interface{}) { + e.logger.Error(fmt.Sprintf(format, args...)) // slog doesn't have Panic +} + +func (e *entry) Printf(format string, args ...interface{}) { + e.logger.Info(fmt.Sprintf(format, args...)) +} + +func (e *entry) WriterLevel(level slog.Level) *io.PipeWriter { + reader, writer := io.Pipe() + + var logFunc func(args ...interface{}) + + // Determine which log function to use based on the specified log level + switch level { + case slog.LevelDebug: + logFunc = e.Debug + case slog.LevelInfo: + logFunc = e.Print + case slog.LevelWarn: + logFunc = e.Warn + case slog.LevelError: + logFunc = e.Error + default: + logFunc = e.Print + } + + // Start a new goroutine to scan and write to logger + go func(r *io.PipeReader, logFn func(...interface{})) { + scanner := bufio.NewScanner(r) + scanner.Buffer(make([]byte, 65536), 65536) + for scanner.Scan() { + logFn(scanner.Text()) + } + r.Close() + }(reader, logFunc) + + return writer +} + +func errFields(err errors.Error) map[string]any { + f := map[string]any{ + "kind": errors.KindText(err), + "module": err.Module, + "version": err.Version, + "ops": errors.Ops(err), + } return f } diff --git a/pkg/log/log.go b/pkg/log/log.go index 421b665d2..de35ea089 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -1,8 +1,10 @@ package log import ( + "bufio" "bytes" "context" + "io" "log/slog" "os" ) @@ -33,13 +35,17 @@ func New(cloudProvider string, level slog.Level, format string) *Logger { // SystemErr Entry implementation. func (l *Logger) SystemErr(err error) { - e := &entry{Logger: l.Logger} + e := &entry{l.Logger} e.SystemErr(err) } // WithFields Entry implementation. func (l *Logger) WithFields(fields map[string]any) Entry { - return l.WithFields(fields) + attrs := make([]any, 0, len(fields)) + for k, v := range fields { + attrs = append(attrs, slog.Any(k, v)) + } + return &entry{logger: l.Logger.With(attrs...)} } func (l *Logger) WithField(key string, value any) Entry { @@ -63,8 +69,22 @@ func (l *Logger) WithContext(ctx context.Context) Entry { return l.WithFields(keys) } +// Define WriterLevel +func (l *Logger) WriterLevel(level slog.Level) *io.PipeWriter { + pipeReader, pipeWriter := io.Pipe() + go func() { + scanner := bufio.NewScanner(pipeReader) + for scanner. + Scan() { + l.Info(scanner.Text()) + } + }() + return pipeWriter +} + // NoOpLogger provides a Logger that does nothing. func NoOpLogger() *Logger { - l := slog.New(slog.NewTextHandler(os.Stdout, nil)) - return &Logger{Logger: l} + return &Logger{ + Logger: &slog.Logger{}, + } } diff --git a/pkg/log/log_context.go b/pkg/log/log_context.go index 176ad4d22..54a65ddf3 100644 --- a/pkg/log/log_context.go +++ b/pkg/log/log_context.go @@ -9,7 +9,7 @@ type ctxKey string const logEntryKey ctxKey = "log-entry-context-key" // SetEntryInContext stores an Entry in the request context. -func SetEntryInContext(ctx context.Context, e *Entry) context.Context { +func SetEntryInContext(ctx context.Context, e Entry) context.Context { return context.WithValue(ctx, logEntryKey, e) } @@ -19,8 +19,7 @@ func SetEntryInContext(ctx context.Context, e *Entry) context.Context { func EntryFromContext(ctx context.Context) Entry { e, ok := ctx.Value(logEntryKey).(Entry) if !ok || e == nil { - //return a new entry - return &Entry{} + return &entry{NoOpLogger().Logger} } return e } diff --git a/pkg/middleware/log_entry.go b/pkg/middleware/log_entry.go index 00bc2e6e3..8d6016cf7 100644 --- a/pkg/middleware/log_entry.go +++ b/pkg/middleware/log_entry.go @@ -6,6 +6,7 @@ import ( "github.com/gomods/athens/pkg/log" "github.com/gomods/athens/pkg/requestid" "github.com/gorilla/mux" + "github.com/sirupsen/logrus" ) // LogEntryMiddleware builds a log.Entry, setting the request fields @@ -14,11 +15,11 @@ func LogEntryMiddleware(lggr *log.Logger) mux.MiddlewareFunc { return func(h http.Handler) http.Handler { f := func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - ent := lggr.With( - "http-method", r.Method, - "http-path", r.URL.Path, - "request-id", requestid.FromContext(ctx), - ) + ent := lggr.WithFields(logrus.Fields{ + "http-method": r.Method, + "http-path": r.URL.Path, + "request-id": requestid.FromContext(ctx), + }) ctx = log.SetEntryInContext(ctx, ent) r = r.WithContext(ctx) h.ServeHTTP(w, r) diff --git a/pkg/middleware/log_entry_test.go b/pkg/middleware/log_entry_test.go index 835b7e528..7ce3e87b2 100644 --- a/pkg/middleware/log_entry_test.go +++ b/pkg/middleware/log_entry_test.go @@ -2,10 +2,11 @@ package middleware import ( "bytes" - "encoding/json" + "fmt" "log/slog" "net/http" "net/http/httptest" + "strings" "testing" "github.com/gomods/athens/pkg/log" @@ -22,8 +23,11 @@ func TestLogContext(t *testing.T) { r := mux.NewRouter() r.HandleFunc("/test", h) - buf := new(bytes.Buffer) - lggr := log.New("", slog.LevelInfo, "") + var buf bytes.Buffer + lggr := log.New("", slog.LevelDebug, "") + opts := slog.HandlerOptions{Level: slog.LevelDebug} + handler := slog.NewJSONHandler(&buf, &opts) + lggr.Logger = slog.New(handler) r.Use(LogEntryMiddleware(lggr)) @@ -31,19 +35,6 @@ func TestLogContext(t *testing.T) { req, _ := http.NewRequest("GET", "/test", nil) r.ServeHTTP(w, req) - var logEntry map[string]interface{} - err := json.Unmarshal(buf.Bytes(), &logEntry) - assert.NoError(t, err) - - expectedFields := map[string]interface{}{ - "level": "INFO", - "msg": "test", - "http-method": "GET", - "http-path": "/test", - "request-id": "", - } - - for k, v := range expectedFields { - assert.Equal(t, v, logEntry[k], "Log entry should contain %s with value %v", k, v) - } + expected := `{"http-method":"GET","http-path":"/test","level":"info","msg":"test","request-id":""}` + assert.True(t, strings.Contains(buf.String(), expected), fmt.Sprintf("%s should contain: %s", buf.String(), expected)) } From 647228e23da6c5e08a8bf1df3ca0b87c803e41af Mon Sep 17 00:00:00 2001 From: Madhur Date: Wed, 4 Dec 2024 23:55:57 +0530 Subject: [PATCH 03/17] #1890 modifying logger pkg to use slog --- pkg/log/format.go | 18 +++++++++++++++--- pkg/middleware/log_entry.go | 3 +-- pkg/middleware/log_entry_test.go | 4 ++-- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/pkg/log/format.go b/pkg/log/format.go index 13f373e7b..57a97e250 100644 --- a/pkg/log/format.go +++ b/pkg/log/format.go @@ -10,9 +10,21 @@ import ( ) func getGCPFormatter(level slog.Level) *slog.Logger { - - l := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: level})) - return l + return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ + Level: level, + ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { + switch a.Key { + case slog.LevelKey: + return slog.String("severity", a.Value.String()) + case slog.MessageKey: + return slog.String("message", a.Value.String()) + case slog.TimeKey: + return slog.String("timestamp", a.Value.Time().Format(time.RFC3339)) + default: + return a + } + }, + })) } const lightGrey = 0xffccc diff --git a/pkg/middleware/log_entry.go b/pkg/middleware/log_entry.go index 8d6016cf7..980637cae 100644 --- a/pkg/middleware/log_entry.go +++ b/pkg/middleware/log_entry.go @@ -6,7 +6,6 @@ import ( "github.com/gomods/athens/pkg/log" "github.com/gomods/athens/pkg/requestid" "github.com/gorilla/mux" - "github.com/sirupsen/logrus" ) // LogEntryMiddleware builds a log.Entry, setting the request fields @@ -15,7 +14,7 @@ func LogEntryMiddleware(lggr *log.Logger) mux.MiddlewareFunc { return func(h http.Handler) http.Handler { f := func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - ent := lggr.WithFields(logrus.Fields{ + ent := lggr.WithFields(map[string]interface{}{ "http-method": r.Method, "http-path": r.URL.Path, "request-id": requestid.FromContext(ctx), diff --git a/pkg/middleware/log_entry_test.go b/pkg/middleware/log_entry_test.go index 7ce3e87b2..1b7b4e9a2 100644 --- a/pkg/middleware/log_entry_test.go +++ b/pkg/middleware/log_entry_test.go @@ -23,10 +23,10 @@ func TestLogContext(t *testing.T) { r := mux.NewRouter() r.HandleFunc("/test", h) - var buf bytes.Buffer + buf := &bytes.Buffer{} lggr := log.New("", slog.LevelDebug, "") opts := slog.HandlerOptions{Level: slog.LevelDebug} - handler := slog.NewJSONHandler(&buf, &opts) + handler := slog.NewJSONHandler(buf, &opts) lggr.Logger = slog.New(handler) r.Use(LogEntryMiddleware(lggr)) From 366bc6ec7ff260e6a9dd5205c4fac5a04cc0a495 Mon Sep 17 00:00:00 2001 From: Madhur Date: Thu, 5 Dec 2024 00:56:59 +0530 Subject: [PATCH 04/17] Rectifying small mistakes --- pkg/log/entry.go | 10 ++++++++-- pkg/log/format.go | 2 +- pkg/log/log_test.go | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/pkg/log/entry.go b/pkg/log/entry.go index 97cefb392..2c9bd9190 100644 --- a/pkg/log/entry.go +++ b/pkg/log/entry.go @@ -11,6 +11,11 @@ import ( "github.com/gomods/athens/pkg/errors" ) +// Entry is an abstraction to the +// Logger and the logrus.Entry +// so that *Logger always creates +// an Entry copy which ensures no +// Fields are being overwritten. type Entry interface { // Keep the existing interface methods unchanged Debugf(string, ...interface{}) @@ -98,11 +103,12 @@ func (e *entry) Error(args ...interface{}) { } func (e *entry) Fatal(args ...interface{}) { - e.logger.Error(fmt.Sprint(args...)) // slog doesn't have Fatal, using Error + e.logger.Error(fmt.Sprint(args...)) + os.Exit(1) } func (e *entry) Panic(args ...interface{}) { - e.logger.Error(fmt.Sprint(args...)) // slog doesn't have Panic, using Error + e.logger.Error(fmt.Sprint(args...)) } func (e *entry) Print(args ...interface{}) { diff --git a/pkg/log/format.go b/pkg/log/format.go index 57a97e250..56f1a226b 100644 --- a/pkg/log/format.go +++ b/pkg/log/format.go @@ -80,7 +80,7 @@ func sortFields(data map[string]any) []string { func parseFormat(format string, level slog.Level) *slog.Logger { if format == "json" { - return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})) + return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: level})) } return getDevFormatter(level) diff --git a/pkg/log/log_test.go b/pkg/log/log_test.go index 9819fd0be..3b36c2ac6 100644 --- a/pkg/log/log_test.go +++ b/pkg/log/log_test.go @@ -6,8 +6,8 @@ import ( "strings" "testing" "time" - "log/slog" + "github.com/stretchr/testify/require" ) From b5d0a069d2001b07f88e953fd16c2c232b7faf26 Mon Sep 17 00:00:00 2001 From: Madhur Date: Thu, 5 Dec 2024 10:34:09 +0530 Subject: [PATCH 05/17] adding option for writing in buf and resolving errors --- cmd/proxy/actions/basicauth_test.go | 6 ++--- cmd/proxy/main.go | 8 +++--- pkg/log/entry.go | 38 ++++++++++++++++++++++++++++- pkg/log/format.go | 9 ++++--- pkg/log/log.go | 18 ++++++++++---- pkg/log/log_test.go | 14 +++++------ pkg/middleware/log_entry_test.go | 2 +- 7 files changed, 69 insertions(+), 26 deletions(-) diff --git a/cmd/proxy/actions/basicauth_test.go b/cmd/proxy/actions/basicauth_test.go index 1f02f84fc..aa5742517 100644 --- a/cmd/proxy/actions/basicauth_test.go +++ b/cmd/proxy/actions/basicauth_test.go @@ -70,10 +70,10 @@ func TestBasicAuth(t *testing.T) { w := httptest.NewRecorder() r := httptest.NewRequest(http.MethodGet, tc.path, nil) r.SetBasicAuth(tc.user, tc.pass) - lggr := log.New("none", slog.LevelDebug, "") buf := &bytes.Buffer{} - lggr.Out = buf - ctx := log.SetEntryInContext(context.Background(), lggr) + lggr := log.New("none", slog.LevelDebug, "", buf) + entry := lggr.WithFields(map[string]any{}) + ctx := log.SetEntryInContext(context.Background(), entry) r = r.WithContext(ctx) handler.ServeHTTP(w, r) resp := w.Result() diff --git a/cmd/proxy/main.go b/cmd/proxy/main.go index e56547c4d..e3cf95c5d 100644 --- a/cmd/proxy/main.go +++ b/cmd/proxy/main.go @@ -43,16 +43,16 @@ func main() { stdlog.Fatalf("Could not parse log level %q: %v", conf.LogLevel, err) } - logger := athenslog.New(conf.CloudRuntime, logLvl, conf.LogFormat) + logger := athenslog.New(conf.CloudRuntime, logLvl, conf.LogFormat, os.Stdout) // Turn standard logger output into slog Errors. - logrusErrorWriter := logger.WriterLevel(slog.LevelError) + slogErrorWriter := logger.WriterLevel(slog.LevelError) defer func() { - if err := logrusErrorWriter.Close(); err != nil { + if err := slogErrorWriter.Close(); err != nil { logger.WithError(err).Warn("Could not close logrus writer pipe") } }() - stdlog.SetOutput(logrusErrorWriter) + stdlog.SetOutput(slogErrorWriter) stdlog.SetFlags(stdlog.Flags() &^ (stdlog.Ldate | stdlog.Ltime)) handler, err := actions.App(logger, conf) diff --git a/pkg/log/entry.go b/pkg/log/entry.go index 2c9bd9190..09347a3f7 100644 --- a/pkg/log/entry.go +++ b/pkg/log/entry.go @@ -17,28 +17,64 @@ import ( // an Entry copy which ensures no // Fields are being overwritten. type Entry interface { - // Keep the existing interface methods unchanged + // Debugf logs a debug message with formatting Debugf(string, ...interface{}) + + // Infof logs an info message with formatting Infof(string, ...interface{}) + + // Warnf logs a warning message with formatting Warnf(string, ...interface{}) + + // Errorf logs an error message with formatting Errorf(string, ...interface{}) + + // Fatalf logs a fatal message with formatting and terminates the program Fatalf(string, ...interface{}) + + // Panicf logs a panic message with formatting and panics Panicf(string, ...interface{}) + + // Printf logs a message with formatting at default level Printf(string, ...interface{}) + // Debug logs a debug message Debug(...interface{}) + + // Info logs an info message Info(...interface{}) + + // Warn logs a warning message Warn(...interface{}) + + // Error logs an error message Error(...interface{}) + + // Fatal logs a fatal message and terminates the program Fatal(...interface{}) + + // Panic logs a panic message and panics Panic(...interface{}) + + // Print logs a message at default level Print(...interface{}) + // WithFields returns a new Entry with the provided fields added WithFields(fields map[string]any) Entry + + // WithField returns a new Entry with a single field added WithField(key string, value any) Entry + + // WithError returns a new Entry with the error added to the fields WithError(err error) Entry + + // WithContext returns a new Entry with the context added to the fields WithContext(ctx context.Context) Entry + + // SystemErr handles system errors with appropriate logging levels SystemErr(err error) + + // WriterLevel returns an io.PipeWriter for the specified logging level WriterLevel(level slog.Level) *io.PipeWriter } diff --git a/pkg/log/format.go b/pkg/log/format.go index 56f1a226b..b94f1292a 100644 --- a/pkg/log/format.go +++ b/pkg/log/format.go @@ -1,6 +1,7 @@ package log import ( + "io" "log/slog" "os" "sort" @@ -9,8 +10,8 @@ import ( "github.com/fatih/color" ) -func getGCPFormatter(level slog.Level) *slog.Logger { - return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ +func getGCPFormatter(level slog.Level, w io.Writer) *slog.Logger { + return slog.New(slog.NewJSONHandler(w, &slog.HandlerOptions{ Level: level, ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { switch a.Key { @@ -78,9 +79,9 @@ func sortFields(data map[string]any) []string { return keys } -func parseFormat(format string, level slog.Level) *slog.Logger { +func parseFormat(format string, level slog.Level, w io.Writer) *slog.Logger { if format == "json" { - return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: level})) + return slog.New(slog.NewJSONHandler(w, &slog.HandlerOptions{Level: level})) } return getDevFormatter(level) diff --git a/pkg/log/log.go b/pkg/log/log.go index de35ea089..a39384efc 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "context" + "fmt" "io" "log/slog" "os" @@ -21,13 +22,16 @@ type Logger struct { // environment and the cloud platform it is // running on. TODO: take cloud arg and env // to construct the correct JSON formatter. -func New(cloudProvider string, level slog.Level, format string) *Logger { - l := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: level})) +func New(cloudProvider string, level slog.Level, format string, w io.Writer) *Logger { + var l *slog.Logger switch cloudProvider { case "GCP": - l = getGCPFormatter(level) + l = getGCPFormatter(level, w) default: - l = parseFormat(format, level) + l = parseFormat(format, level, w) + } + if l == nil { + l = slog.New(slog.NewTextHandler(w, &slog.HandlerOptions{Level: level})) } slog.SetDefault(l) return &Logger{Logger: l} @@ -69,7 +73,6 @@ func (l *Logger) WithContext(ctx context.Context) Entry { return l.WithFields(keys) } -// Define WriterLevel func (l *Logger) WriterLevel(level slog.Level) *io.PipeWriter { pipeReader, pipeWriter := io.Pipe() go func() { @@ -82,6 +85,11 @@ func (l *Logger) WriterLevel(level slog.Level) *io.PipeWriter { return pipeWriter } +func (l *Logger) Fatal(args ...any) { + l.Logger.Error(fmt.Sprint(args...)) + os.Exit(1) +} + // NoOpLogger provides a Logger that does nothing. func NoOpLogger() *Logger { return &Logger{ diff --git a/pkg/log/log_test.go b/pkg/log/log_test.go index 3b36c2ac6..7172c127c 100644 --- a/pkg/log/log_test.go +++ b/pkg/log/log_test.go @@ -3,11 +3,11 @@ package log import ( "bytes" "fmt" + "log/slog" "strings" "testing" "time" - "log/slog" - + "github.com/stretchr/testify/require" ) @@ -87,7 +87,7 @@ var testCases = []input{ format: "plain", cloudProvider: "none", level: slog.LevelDebug, - fields: map[string]any{"xyz": "abc", "abc": "xyz"}, + fields: map[string]any{"xyz": "abc", "abc": "xyz"}, logFunc: func(e Entry) time.Time { t := time.Now() e.Warnf("warn message") @@ -112,7 +112,7 @@ var testCases = []input{ format: "json", cloudProvider: "none", level: slog.LevelDebug, - fields: map[string]any{"xyz": "abc", "abc": "xyz"}, + fields: map[string]any{"xyz": "abc", "abc": "xyz"}, logFunc: func(e Entry) time.Time { t := time.Now() e.Warnf("warn message") @@ -125,9 +125,8 @@ var testCases = []input{ func TestCloudLogger(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - lggr := New(tc.cloudProvider, tc.level, tc.format) - var buf bytes.Buffer - lggr.Out = &buf + buf := &bytes.Buffer{} + lggr := New(tc.cloudProvider, tc.level, tc.format, buf) e := lggr.WithFields(tc.fields) entryTime := tc.logFunc(e) out := buf.String() @@ -144,7 +143,6 @@ func TestCloudLogger(t *testing.T) { }) } } - func TestNoOpLogger(t *testing.T) { l := NoOpLogger() require.NotPanics(t, func() { l.Info("test") }) diff --git a/pkg/middleware/log_entry_test.go b/pkg/middleware/log_entry_test.go index 1b7b4e9a2..3b8af3414 100644 --- a/pkg/middleware/log_entry_test.go +++ b/pkg/middleware/log_entry_test.go @@ -24,7 +24,7 @@ func TestLogContext(t *testing.T) { r.HandleFunc("/test", h) buf := &bytes.Buffer{} - lggr := log.New("", slog.LevelDebug, "") + lggr := log.New("", slog.LevelDebug, "", buf) opts := slog.HandlerOptions{Level: slog.LevelDebug} handler := slog.NewJSONHandler(buf, &opts) lggr.Logger = slog.New(handler) From e81aa1084e81f2accf81d41077b94f8dcdde112d Mon Sep 17 00:00:00 2001 From: Madhur Date: Thu, 5 Dec 2024 10:36:06 +0530 Subject: [PATCH 06/17] Removing Out from Logger struct --- pkg/log/log.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/log/log.go b/pkg/log/log.go index a39384efc..987890ef1 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -2,7 +2,6 @@ package log import ( "bufio" - "bytes" "context" "fmt" "io" @@ -14,8 +13,6 @@ import ( // internal service should use to communicate things. type Logger struct { *slog.Logger - - Out *bytes.Buffer } // New constructs a new logger based on the From 28340f3dfad1a8165b5c9a5458f0bb77210dacb4 Mon Sep 17 00:00:00 2001 From: Madhur Date: Thu, 5 Dec 2024 11:36:00 +0530 Subject: [PATCH 07/17] Resolving testing errors due to logrus --- cmd/proxy/actions/index.go | 6 +++--- cmd/proxy/main.go | 2 +- pkg/errors/errors.go | 12 ++++-------- pkg/errors/errors_test.go | 22 +++++++++++----------- pkg/log/entry.go | 2 +- pkg/middleware/request.go | 3 +-- 6 files changed, 21 insertions(+), 26 deletions(-) diff --git a/cmd/proxy/actions/index.go b/cmd/proxy/actions/index.go index 1e4744e8d..5b0af31a8 100644 --- a/cmd/proxy/actions/index.go +++ b/cmd/proxy/actions/index.go @@ -3,6 +3,7 @@ package actions import ( "encoding/json" "fmt" + "log/slog" "net/http" "strconv" "time" @@ -10,7 +11,6 @@ import ( "github.com/gomods/athens/pkg/errors" "github.com/gomods/athens/pkg/index" "github.com/gomods/athens/pkg/log" - "github.com/sirupsen/logrus" ) // indexHandler implements GET baseURL/index. @@ -46,13 +46,13 @@ func getIndexLines(r *http.Request, index index.Indexer) ([]*index.Line, error) if limitStr := r.FormValue("limit"); limitStr != "" { limit, err = strconv.Atoi(limitStr) if err != nil || limit <= 0 { - return nil, errors.E(op, err, errors.KindBadRequest, logrus.InfoLevel) + return nil, errors.E(op, err, errors.KindBadRequest, slog.LevelInfo) } } if sinceStr := r.FormValue("since"); sinceStr != "" { since, err = time.Parse(time.RFC3339, sinceStr) if err != nil { - return nil, errors.E(op, err, errors.KindBadRequest, logrus.InfoLevel) + return nil, errors.E(op, err, errors.KindBadRequest, slog.LevelInfo) } } list, err := index.Lines(r.Context(), since, limit) diff --git a/cmd/proxy/main.go b/cmd/proxy/main.go index e3cf95c5d..85ddda168 100644 --- a/cmd/proxy/main.go +++ b/cmd/proxy/main.go @@ -49,7 +49,7 @@ func main() { slogErrorWriter := logger.WriterLevel(slog.LevelError) defer func() { if err := slogErrorWriter.Close(); err != nil { - logger.WithError(err).Warn("Could not close logrus writer pipe") + logger.WithError(err).Warn("Could not close slog writer pipe") } }() stdlog.SetOutput(slogErrorWriter) diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go index 67799ea7b..7894bd46a 100644 --- a/pkg/errors/errors.go +++ b/pkg/errors/errors.go @@ -6,8 +6,6 @@ import ( "log/slog" "net/http" "runtime" - - "github.com/sirupsen/logrus" ) // Kind enums. @@ -133,9 +131,7 @@ func Severity(err error) slog.Level { return slog.LevelError } - // if there's no severity (0 is Panic level in logrus - // which we should not use since cloud providers only have - // debug, info, warn, and error) then look for the + // if there's no severity then look for the // child's severity. if e.Severity < slog.LevelError { return Severity(e.Err) @@ -147,13 +143,13 @@ func Severity(err error) slog.Level { // Expect is a helper that returns an Info level // if the error has the expected kind, otherwise // it returns an Error level. -func Expect(err error, kinds ...int) logrus.Level { +func Expect(err error, kinds ...int) slog.Level { for _, kind := range kinds { if Kind(err) == kind { - return logrus.InfoLevel + return slog.LevelInfo } } - return logrus.ErrorLevel + return slog.LevelError } // Kind recursively searches for the diff --git a/pkg/errors/errors_test.go b/pkg/errors/errors_test.go index b3c43b441..e1a0dee7c 100644 --- a/pkg/errors/errors_test.go +++ b/pkg/errors/errors_test.go @@ -2,10 +2,10 @@ package errors import ( "errors" + "log/slog" "net/http" "testing" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) @@ -35,16 +35,16 @@ func TestSeverity(t *testing.T) { msg := "test error" err := E(op, msg) - require.Equal(t, logrus.ErrorLevel, Severity(err)) + require.Equal(t, slog.LevelError, Severity(err)) - err = E(op, msg, logrus.WarnLevel) - require.Equal(t, logrus.WarnLevel, Severity(err)) + err = E(op, msg, slog.LevelWarn) + require.Equal(t, slog.LevelWarn, Severity(err)) err = E(op, err) - require.Equal(t, logrus.WarnLevel, Severity(err)) + require.Equal(t, slog.LevelWarn, Severity(err)) - err = E(op, err, logrus.InfoLevel) - require.Equal(t, logrus.InfoLevel, Severity(err)) + err = E(op, err, slog.LevelInfo) + require.Equal(t, slog.LevelInfo, Severity(err)) } func TestKind(t *testing.T) { @@ -85,14 +85,14 @@ func TestExpect(t *testing.T) { err := E("TestExpect", "error message", KindBadRequest) severity := Expect(err, KindBadRequest) - require.Equalf(t, severity, logrus.InfoLevel, "expected an info level log but got %v", severity) + require.Equalf(t, severity, slog.LevelInfo, "expected an info level log but got %v", severity) severity = Expect(err, KindAlreadyExists) - require.Equalf(t, severity, logrus.ErrorLevel, "expected an error level but got %v", severity) + require.Equalf(t, severity, slog.LevelError, "expected an error level but got %v", severity) severity = Expect(err, KindAlreadyExists, KindBadRequest) - require.Equalf(t, severity, logrus.InfoLevel, "expected an info level log but got %v", severity) + require.Equalf(t, severity, slog.LevelInfo, "expected an info level log but got %v", severity) severity = Expect(err, KindAlreadyExists, KindNotImplemented) - require.Equalf(t, severity, logrus.ErrorLevel, "expected an error level but got %v", severity) + require.Equalf(t, severity, slog.LevelError, "expected an error level but got %v", severity) } diff --git a/pkg/log/entry.go b/pkg/log/entry.go index 09347a3f7..624aedf12 100644 --- a/pkg/log/entry.go +++ b/pkg/log/entry.go @@ -12,7 +12,7 @@ import ( ) // Entry is an abstraction to the -// Logger and the logrus.Entry +// Logger and the slog.Entry // so that *Logger always creates // an Entry copy which ensures no // Fields are being overwritten. diff --git a/pkg/middleware/request.go b/pkg/middleware/request.go index 1bff12e6d..fb1df4548 100644 --- a/pkg/middleware/request.go +++ b/pkg/middleware/request.go @@ -6,7 +6,6 @@ import ( "github.com/fatih/color" "github.com/gomods/athens/pkg/log" - logrus "github.com/sirupsen/logrus" ) type responseWriter struct { @@ -25,7 +24,7 @@ func RequestLogger(h http.Handler) http.Handler { f := func(w http.ResponseWriter, r *http.Request) { rw := &responseWriter{w, 0} h.ServeHTTP(rw, r) - log.EntryFromContext(r.Context()).WithFields(logrus.Fields{ + log.EntryFromContext(r.Context()).WithFields(map[string]any{ "http-status": fmtResponseCode(rw.statusCode), }).Infof("incoming request") } From e765552c3a4bcedace23d5bbcff9676ea587161c Mon Sep 17 00:00:00 2001 From: Madhur Date: Thu, 5 Dec 2024 11:36:00 +0530 Subject: [PATCH 08/17] removing a few lint errors --- cmd/proxy/actions/index.go | 6 +++--- cmd/proxy/main.go | 2 +- pkg/errors/errors.go | 12 ++++-------- pkg/errors/errors_test.go | 22 +++++++++++----------- pkg/log/entry.go | 2 +- pkg/log/format.go | 16 ---------------- pkg/middleware/request.go | 3 +-- 7 files changed, 21 insertions(+), 42 deletions(-) diff --git a/cmd/proxy/actions/index.go b/cmd/proxy/actions/index.go index 1e4744e8d..5b0af31a8 100644 --- a/cmd/proxy/actions/index.go +++ b/cmd/proxy/actions/index.go @@ -3,6 +3,7 @@ package actions import ( "encoding/json" "fmt" + "log/slog" "net/http" "strconv" "time" @@ -10,7 +11,6 @@ import ( "github.com/gomods/athens/pkg/errors" "github.com/gomods/athens/pkg/index" "github.com/gomods/athens/pkg/log" - "github.com/sirupsen/logrus" ) // indexHandler implements GET baseURL/index. @@ -46,13 +46,13 @@ func getIndexLines(r *http.Request, index index.Indexer) ([]*index.Line, error) if limitStr := r.FormValue("limit"); limitStr != "" { limit, err = strconv.Atoi(limitStr) if err != nil || limit <= 0 { - return nil, errors.E(op, err, errors.KindBadRequest, logrus.InfoLevel) + return nil, errors.E(op, err, errors.KindBadRequest, slog.LevelInfo) } } if sinceStr := r.FormValue("since"); sinceStr != "" { since, err = time.Parse(time.RFC3339, sinceStr) if err != nil { - return nil, errors.E(op, err, errors.KindBadRequest, logrus.InfoLevel) + return nil, errors.E(op, err, errors.KindBadRequest, slog.LevelInfo) } } list, err := index.Lines(r.Context(), since, limit) diff --git a/cmd/proxy/main.go b/cmd/proxy/main.go index e3cf95c5d..85ddda168 100644 --- a/cmd/proxy/main.go +++ b/cmd/proxy/main.go @@ -49,7 +49,7 @@ func main() { slogErrorWriter := logger.WriterLevel(slog.LevelError) defer func() { if err := slogErrorWriter.Close(); err != nil { - logger.WithError(err).Warn("Could not close logrus writer pipe") + logger.WithError(err).Warn("Could not close slog writer pipe") } }() stdlog.SetOutput(slogErrorWriter) diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go index 67799ea7b..7894bd46a 100644 --- a/pkg/errors/errors.go +++ b/pkg/errors/errors.go @@ -6,8 +6,6 @@ import ( "log/slog" "net/http" "runtime" - - "github.com/sirupsen/logrus" ) // Kind enums. @@ -133,9 +131,7 @@ func Severity(err error) slog.Level { return slog.LevelError } - // if there's no severity (0 is Panic level in logrus - // which we should not use since cloud providers only have - // debug, info, warn, and error) then look for the + // if there's no severity then look for the // child's severity. if e.Severity < slog.LevelError { return Severity(e.Err) @@ -147,13 +143,13 @@ func Severity(err error) slog.Level { // Expect is a helper that returns an Info level // if the error has the expected kind, otherwise // it returns an Error level. -func Expect(err error, kinds ...int) logrus.Level { +func Expect(err error, kinds ...int) slog.Level { for _, kind := range kinds { if Kind(err) == kind { - return logrus.InfoLevel + return slog.LevelInfo } } - return logrus.ErrorLevel + return slog.LevelError } // Kind recursively searches for the diff --git a/pkg/errors/errors_test.go b/pkg/errors/errors_test.go index b3c43b441..e1a0dee7c 100644 --- a/pkg/errors/errors_test.go +++ b/pkg/errors/errors_test.go @@ -2,10 +2,10 @@ package errors import ( "errors" + "log/slog" "net/http" "testing" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) @@ -35,16 +35,16 @@ func TestSeverity(t *testing.T) { msg := "test error" err := E(op, msg) - require.Equal(t, logrus.ErrorLevel, Severity(err)) + require.Equal(t, slog.LevelError, Severity(err)) - err = E(op, msg, logrus.WarnLevel) - require.Equal(t, logrus.WarnLevel, Severity(err)) + err = E(op, msg, slog.LevelWarn) + require.Equal(t, slog.LevelWarn, Severity(err)) err = E(op, err) - require.Equal(t, logrus.WarnLevel, Severity(err)) + require.Equal(t, slog.LevelWarn, Severity(err)) - err = E(op, err, logrus.InfoLevel) - require.Equal(t, logrus.InfoLevel, Severity(err)) + err = E(op, err, slog.LevelInfo) + require.Equal(t, slog.LevelInfo, Severity(err)) } func TestKind(t *testing.T) { @@ -85,14 +85,14 @@ func TestExpect(t *testing.T) { err := E("TestExpect", "error message", KindBadRequest) severity := Expect(err, KindBadRequest) - require.Equalf(t, severity, logrus.InfoLevel, "expected an info level log but got %v", severity) + require.Equalf(t, severity, slog.LevelInfo, "expected an info level log but got %v", severity) severity = Expect(err, KindAlreadyExists) - require.Equalf(t, severity, logrus.ErrorLevel, "expected an error level but got %v", severity) + require.Equalf(t, severity, slog.LevelError, "expected an error level but got %v", severity) severity = Expect(err, KindAlreadyExists, KindBadRequest) - require.Equalf(t, severity, logrus.InfoLevel, "expected an info level log but got %v", severity) + require.Equalf(t, severity, slog.LevelInfo, "expected an info level log but got %v", severity) severity = Expect(err, KindAlreadyExists, KindNotImplemented) - require.Equalf(t, severity, logrus.ErrorLevel, "expected an error level but got %v", severity) + require.Equalf(t, severity, slog.LevelError, "expected an error level but got %v", severity) } diff --git a/pkg/log/entry.go b/pkg/log/entry.go index 09347a3f7..624aedf12 100644 --- a/pkg/log/entry.go +++ b/pkg/log/entry.go @@ -12,7 +12,7 @@ import ( ) // Entry is an abstraction to the -// Logger and the logrus.Entry +// Logger and the slog.Entry // so that *Logger always creates // an Entry copy which ensures no // Fields are being overwritten. diff --git a/pkg/log/format.go b/pkg/log/format.go index b94f1292a..33ff21589 100644 --- a/pkg/log/format.go +++ b/pkg/log/format.go @@ -4,7 +4,6 @@ import ( "io" "log/slog" "os" - "sort" "time" "github.com/fatih/color" @@ -30,8 +29,6 @@ func getGCPFormatter(level slog.Level, w io.Writer) *slog.Logger { const lightGrey = 0xffccc -type devFormatter struct{} - func getDevFormatter(level slog.Level) *slog.Logger { return slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ Level: level, @@ -66,19 +63,6 @@ func getDevFormatter(level slog.Level) *slog.Logger { })) } -func sortFields(data map[string]any) []string { - if data == nil { - return nil - } - - keys := make([]string, 0, len(data)) - for k := range data { - keys = append(keys, k) - } - sort.Strings(keys) - return keys -} - func parseFormat(format string, level slog.Level, w io.Writer) *slog.Logger { if format == "json" { return slog.New(slog.NewJSONHandler(w, &slog.HandlerOptions{Level: level})) diff --git a/pkg/middleware/request.go b/pkg/middleware/request.go index 1bff12e6d..fb1df4548 100644 --- a/pkg/middleware/request.go +++ b/pkg/middleware/request.go @@ -6,7 +6,6 @@ import ( "github.com/fatih/color" "github.com/gomods/athens/pkg/log" - logrus "github.com/sirupsen/logrus" ) type responseWriter struct { @@ -25,7 +24,7 @@ func RequestLogger(h http.Handler) http.Handler { f := func(w http.ResponseWriter, r *http.Request) { rw := &responseWriter{w, 0} h.ServeHTTP(rw, r) - log.EntryFromContext(r.Context()).WithFields(logrus.Fields{ + log.EntryFromContext(r.Context()).WithFields(map[string]any{ "http-status": fmtResponseCode(rw.statusCode), }).Infof("incoming request") } From ba79569f20e1c981dfc323540211413c534d9b11 Mon Sep 17 00:00:00 2001 From: Madhur Date: Sat, 7 Dec 2024 12:46:18 +0530 Subject: [PATCH 09/17] removinf lint errors for entry --- pkg/log/entry.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/log/entry.go b/pkg/log/entry.go index 624aedf12..5495ed678 100644 --- a/pkg/log/entry.go +++ b/pkg/log/entry.go @@ -18,25 +18,25 @@ import ( // Fields are being overwritten. type Entry interface { // Debugf logs a debug message with formatting - Debugf(string, ...interface{}) + Debugf(format string, args ...interface{}) // Infof logs an info message with formatting - Infof(string, ...interface{}) + Infof(format string, args ...interface{}) // Warnf logs a warning message with formatting - Warnf(string, ...interface{}) + Warnf(format string, args ...interface{}) // Errorf logs an error message with formatting - Errorf(string, ...interface{}) + Errorf(format string, args ...interface{}) // Fatalf logs a fatal message with formatting and terminates the program - Fatalf(string, ...interface{}) + Fatalf(format string, args ...interface{}) // Panicf logs a panic message with formatting and panics - Panicf(string, ...interface{}) + Panicf(format string, args ...interface{}) // Printf logs a message with formatting at default level - Printf(string, ...interface{}) + Printf(format string, args ...interface{}) // Debug logs a debug message Debug(...interface{}) From 5039841eaf91338b5b279e075dbacff28fbeaa7c Mon Sep 17 00:00:00 2001 From: Madhur Date: Sat, 7 Dec 2024 12:52:19 +0530 Subject: [PATCH 10/17] Resolving lint errors for main and entry --- cmd/proxy/main.go | 1 - pkg/log/entry.go | 19 +++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/cmd/proxy/main.go b/cmd/proxy/main.go index 85ddda168..1bffae82f 100644 --- a/cmd/proxy/main.go +++ b/cmd/proxy/main.go @@ -123,5 +123,4 @@ func main() { } <-idleConnsClosed - } diff --git a/pkg/log/entry.go b/pkg/log/entry.go index 5495ed678..a4475496c 100644 --- a/pkg/log/entry.go +++ b/pkg/log/entry.go @@ -39,25 +39,25 @@ type Entry interface { Printf(format string, args ...interface{}) // Debug logs a debug message - Debug(...interface{}) + Debug(args ...interface{}) // Info logs an info message - Info(...interface{}) + Info(args ...interface{}) // Warn logs a warning message - Warn(...interface{}) + Warn(args ...interface{}) // Error logs an error message - Error(...interface{}) + Error(args ...interface{}) // Fatal logs a fatal message and terminates the program - Fatal(...interface{}) + Fatal(args ...interface{}) // Panic logs a panic message and panics - Panic(...interface{}) + Panic(args ...interface{}) // Print logs a message at default level - Print(...interface{}) + Print(args ...interface{}) // WithFields returns a new Entry with the provided fields added WithFields(fields map[string]any) Entry @@ -206,7 +206,10 @@ func (e *entry) WriterLevel(level slog.Level) *io.PipeWriter { for scanner.Scan() { logFn(scanner.Text()) } - r.Close() + err := r.Close() + if err != nil { + e.Error(err) + } }(reader, logFunc) return writer From f22cc6c75695fb2f0927240f0862f2747b73ffec Mon Sep 17 00:00:00 2001 From: Madhur Date: Sat, 7 Dec 2024 13:05:18 +0530 Subject: [PATCH 11/17] declogging entry interface to avoid interfacebloat --- pkg/log/entry.go | 68 +++++++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/pkg/log/entry.go b/pkg/log/entry.go index a4475496c..e76bc858b 100644 --- a/pkg/log/entry.go +++ b/pkg/log/entry.go @@ -11,33 +11,8 @@ import ( "github.com/gomods/athens/pkg/errors" ) -// Entry is an abstraction to the -// Logger and the slog.Entry -// so that *Logger always creates -// an Entry copy which ensures no -// Fields are being overwritten. -type Entry interface { - // Debugf logs a debug message with formatting - Debugf(format string, args ...interface{}) - - // Infof logs an info message with formatting - Infof(format string, args ...interface{}) - - // Warnf logs a warning message with formatting - Warnf(format string, args ...interface{}) - - // Errorf logs an error message with formatting - Errorf(format string, args ...interface{}) - - // Fatalf logs a fatal message with formatting and terminates the program - Fatalf(format string, args ...interface{}) - - // Panicf logs a panic message with formatting and panics - Panicf(format string, args ...interface{}) - - // Printf logs a message with formatting at default level - Printf(format string, args ...interface{}) - +// Logger handles basic logging operations +type LogOps interface { // Debug logs a debug message Debug(args ...interface{}) @@ -58,7 +33,32 @@ type Entry interface { // Print logs a message at default level Print(args ...interface{}) +} +type FormattedLogOps interface { + // Debugf logs a debug message with formatting + Debugf(format string, args ...interface{}) + + // Infof logs an info message with formatting + Infof(format string, args ...interface{}) + + // Warnf logs a warning message with formatting + Warnf(format string, args ...interface{}) + + // Errorf logs an error message with formatting + Errorf(format string, args ...interface{}) + + // Fatalf logs a fatal message with formatting and terminates the program + Fatalf(format string, args ...interface{}) + + // Panicf logs a panic message with formatting and panics + Panicf(format string, args ...interface{}) + + // Printf logs a message with formatting at default level + Printf(format string, args ...interface{}) +} + +type ContextualLogOps interface { // WithFields returns a new Entry with the provided fields added WithFields(fields map[string]any) Entry @@ -70,7 +70,9 @@ type Entry interface { // WithContext returns a new Entry with the context added to the fields WithContext(ctx context.Context) Entry +} +type SystemLogger interface { // SystemErr handles system errors with appropriate logging levels SystemErr(err error) @@ -78,6 +80,18 @@ type Entry interface { WriterLevel(level slog.Level) *io.PipeWriter } +// Entry is an abstraction to the +// Logger and the slog.Entry +// so that *Logger always creates +// an Entry copy which ensures no +// Fields are being overwritten. +type Entry interface { + LogOps + FormattedLogOps + ContextualLogOps + SystemLogger +} + type entry struct { logger *slog.Logger } From 4859555afddfdb86489ecc225e1cf8e918aa84d0 Mon Sep 17 00:00:00 2001 From: Madhur Date: Sat, 7 Dec 2024 13:15:38 +0530 Subject: [PATCH 12/17] resolved comment related linter errors for entry --- pkg/log/entry.go | 46 +++++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/pkg/log/entry.go b/pkg/log/entry.go index e76bc858b..70cbc3166 100644 --- a/pkg/log/entry.go +++ b/pkg/log/entry.go @@ -11,72 +11,76 @@ import ( "github.com/gomods/athens/pkg/errors" ) -// Logger handles basic logging operations +// Logger handles basic logging operations. type LogOps interface { - // Debug logs a debug message + // Debug logs a debug message. Debug(args ...interface{}) - // Info logs an info message + // Info logs an info message. Info(args ...interface{}) - // Warn logs a warning message + // Warn logs a warning message. Warn(args ...interface{}) - // Error logs an error message + // Error logs an error message. Error(args ...interface{}) - // Fatal logs a fatal message and terminates the program + // Fatal logs a fatal message and terminates the program. Fatal(args ...interface{}) - // Panic logs a panic message and panics + // Panic logs a panic message and panics. Panic(args ...interface{}) - // Print logs a message at default level + // Print logs a message at default level. Print(args ...interface{}) } +// FormattedLogOps is an extension of LogOps that supports formatted logging. type FormattedLogOps interface { - // Debugf logs a debug message with formatting + // Debugf logs a debug message with formatting. Debugf(format string, args ...interface{}) - // Infof logs an info message with formatting + // Infof logs an info message with formatting. Infof(format string, args ...interface{}) - // Warnf logs a warning message with formatting + // Warnf logs a warning message with formatting. Warnf(format string, args ...interface{}) - // Errorf logs an error message with formatting + // Errorf logs an error message with formatting. Errorf(format string, args ...interface{}) - // Fatalf logs a fatal message with formatting and terminates the program + // Fatalf logs a fatal message with formatting and terminates the program. Fatalf(format string, args ...interface{}) - // Panicf logs a panic message with formatting and panics + // Panicf logs a panic message with formatting and panics. Panicf(format string, args ...interface{}) - // Printf logs a message with formatting at default level + // Printf logs a message with formatting at default level. Printf(format string, args ...interface{}) } +// Entry is a contextual logger that can be used to log messages with additional +// fields. type ContextualLogOps interface { - // WithFields returns a new Entry with the provided fields added + // WithFields returns a new Entry with the provided fields added. WithFields(fields map[string]any) Entry - // WithField returns a new Entry with a single field added + // WithField returns a new Entry with a single field added. WithField(key string, value any) Entry - // WithError returns a new Entry with the error added to the fields + // WithError returns a new Entry with the error added to the fields. WithError(err error) Entry - // WithContext returns a new Entry with the context added to the fields + // WithContext returns a new Entry with the context added to the fields. WithContext(ctx context.Context) Entry } +// SystemLogger is an interface to handle system errors. type SystemLogger interface { - // SystemErr handles system errors with appropriate logging levels + // SystemErr handles system errors with appropriate logging levels. SystemErr(err error) - // WriterLevel returns an io.PipeWriter for the specified logging level + // WriterLevel returns an io.PipeWriter for the specified logging level. WriterLevel(level slog.Level) *io.PipeWriter } From f9b398ffa3099c099c53f64f839cc4ae0c93d3a9 Mon Sep 17 00:00:00 2001 From: Madhur Date: Sat, 7 Dec 2024 13:18:50 +0530 Subject: [PATCH 13/17] entry related comment linter issue resolution --- pkg/log/entry.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/log/entry.go b/pkg/log/entry.go index 70cbc3166..f92b65289 100644 --- a/pkg/log/entry.go +++ b/pkg/log/entry.go @@ -11,7 +11,7 @@ import ( "github.com/gomods/athens/pkg/errors" ) -// Logger handles basic logging operations. +// LogOps handles basic logging operations. type LogOps interface { // Debug logs a debug message. Debug(args ...interface{}) @@ -59,8 +59,7 @@ type FormattedLogOps interface { Printf(format string, args ...interface{}) } -// Entry is a contextual logger that can be used to log messages with additional -// fields. +// ContextualLogOps is a contextual logger that can be used to log messages with additional fields. type ContextualLogOps interface { // WithFields returns a new Entry with the provided fields added. WithFields(fields map[string]any) Entry From d48ecdf8b90c6b27133a4a068acafabb6ad116b0 Mon Sep 17 00:00:00 2001 From: Madhur Date: Sun, 8 Dec 2024 11:51:05 +0530 Subject: [PATCH 14/17] removing unwanted methods from entry --- pkg/download/protocol_test.go | 16 +++++----------- pkg/log/entry.go | 12 ------------ 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/pkg/download/protocol_test.go b/pkg/download/protocol_test.go index e2d46ad2c..d3ea3ec07 100644 --- a/pkg/download/protocol_test.go +++ b/pkg/download/protocol_test.go @@ -502,23 +502,17 @@ type testEntry struct { var _ log.Entry = &testEntry{} -func (e *testEntry) Debugf(format string, args ...any) { - e.msg = format -} -func (*testEntry) Infof(format string, args ...any) {} -func (*testEntry) Warnf(format string, args ...any) {} -func (*testEntry) Errorf(format string, args ...any) {} -func (*testEntry) Fatalf(format string, args ...any) {} -func (*testEntry) Panicf(format string, args ...any) {} -func (*testEntry) Printf(format string, args ...any) {} +func (e *testEntry) Debugf(format string, args ...any) {e.msg = format} +func (e *testEntry) Infof(format string, args ...any) {e.msg = format} +func (e *testEntry) Warnf(format string, args ...any) {e.msg = format} +func (e *testEntry) Errorf(format string, args ...any) {e.msg = format} +func (e *testEntry) Fatalf(format string, args ...any) {e.msg = format} func (*testEntry) Debug(args ...any) {} func (*testEntry) Info(args ...any) {} func (*testEntry) Warn(args ...any) {} func (*testEntry) Error(args ...any) {} func (*testEntry) Fatal(args ...any) {} -func (*testEntry) Panic(args ...any) {} -func (*testEntry) Print(args ...any) {} func (*testEntry) WithFields(fields map[string]any) log.Entry { return nil } func (*testEntry) SystemErr(err error) {} diff --git a/pkg/log/entry.go b/pkg/log/entry.go index f92b65289..72d61c50f 100644 --- a/pkg/log/entry.go +++ b/pkg/log/entry.go @@ -27,12 +27,6 @@ type LogOps interface { // Fatal logs a fatal message and terminates the program. Fatal(args ...interface{}) - - // Panic logs a panic message and panics. - Panic(args ...interface{}) - - // Print logs a message at default level. - Print(args ...interface{}) } // FormattedLogOps is an extension of LogOps that supports formatted logging. @@ -51,12 +45,6 @@ type FormattedLogOps interface { // Fatalf logs a fatal message with formatting and terminates the program. Fatalf(format string, args ...interface{}) - - // Panicf logs a panic message with formatting and panics. - Panicf(format string, args ...interface{}) - - // Printf logs a message with formatting at default level. - Printf(format string, args ...interface{}) } // ContextualLogOps is a contextual logger that can be used to log messages with additional fields. From e2f532cbb26e129a21d66483434ec7df78d74a35 Mon Sep 17 00:00:00 2001 From: Madhur Date: Sun, 8 Dec 2024 11:54:29 +0530 Subject: [PATCH 15/17] adding printf for entry --- pkg/download/protocol_test.go | 11 ++++++----- pkg/log/entry.go | 3 +++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pkg/download/protocol_test.go b/pkg/download/protocol_test.go index d3ea3ec07..1a74996a9 100644 --- a/pkg/download/protocol_test.go +++ b/pkg/download/protocol_test.go @@ -502,11 +502,12 @@ type testEntry struct { var _ log.Entry = &testEntry{} -func (e *testEntry) Debugf(format string, args ...any) {e.msg = format} -func (e *testEntry) Infof(format string, args ...any) {e.msg = format} -func (e *testEntry) Warnf(format string, args ...any) {e.msg = format} -func (e *testEntry) Errorf(format string, args ...any) {e.msg = format} -func (e *testEntry) Fatalf(format string, args ...any) {e.msg = format} +func (e *testEntry) Debugf(format string, args ...any) { e.msg = format } +func (e *testEntry) Infof(format string, args ...any) { e.msg = format } +func (e *testEntry) Warnf(format string, args ...any) { e.msg = format } +func (e *testEntry) Errorf(format string, args ...any) { e.msg = format } +func (e *testEntry) Fatalf(format string, args ...any) { e.msg = format } +func (e *testEntry) Printf(format string, args ...any) { e.msg = format } func (*testEntry) Debug(args ...any) {} func (*testEntry) Info(args ...any) {} diff --git a/pkg/log/entry.go b/pkg/log/entry.go index 72d61c50f..027e5f8e4 100644 --- a/pkg/log/entry.go +++ b/pkg/log/entry.go @@ -45,6 +45,9 @@ type FormattedLogOps interface { // Fatalf logs a fatal message with formatting and terminates the program. Fatalf(format string, args ...interface{}) + + //Printf logs a message with formatting. + Printf(format string, args ...interface{}) } // ContextualLogOps is a contextual logger that can be used to log messages with additional fields. From 0b291b0d2f7c5343b9baefe853ef075465786855 Mon Sep 17 00:00:00 2001 From: Madhur Date: Sun, 8 Dec 2024 11:56:25 +0530 Subject: [PATCH 16/17] resolving minor mistake --- pkg/log/entry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/log/entry.go b/pkg/log/entry.go index 027e5f8e4..144b79fef 100644 --- a/pkg/log/entry.go +++ b/pkg/log/entry.go @@ -46,7 +46,7 @@ type FormattedLogOps interface { // Fatalf logs a fatal message with formatting and terminates the program. Fatalf(format string, args ...interface{}) - //Printf logs a message with formatting. + // Printf logs a message with formatting. Printf(format string, args ...interface{}) } From caf4ea3733b1104e80c8a3730f65842079be3030 Mon Sep 17 00:00:00 2001 From: Madhur Date: Fri, 13 Dec 2024 09:08:36 +0530 Subject: [PATCH 17/17] changing no op logger for slog and removing logrus from go.mod and go.sum --- go.mod | 1 - go.sum | 12 ------------ pkg/log/log.go | 2 +- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/go.mod b/go.mod index d297be167..e280aea5f 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,6 @@ require ( github.com/lib/pq v1.10.9 github.com/minio/minio-go/v6 v6.0.57 github.com/mitchellh/go-homedir v1.1.0 - github.com/sirupsen/logrus v1.9.3 github.com/spf13/afero v1.11.0 github.com/stretchr/testify v1.9.0 github.com/technosophos/moniker v0.0.0-20180509230615-a5dbd03a2245 diff --git a/go.sum b/go.sum index d88d1feda..6f2f07701 100644 --- a/go.sum +++ b/go.sum @@ -1188,18 +1188,6 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= -github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= diff --git a/pkg/log/log.go b/pkg/log/log.go index 987890ef1..3cb6f2317 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -90,6 +90,6 @@ func (l *Logger) Fatal(args ...any) { // NoOpLogger provides a Logger that does nothing. func NoOpLogger() *Logger { return &Logger{ - Logger: &slog.Logger{}, + Logger: slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})), } }