From 96ac6a6940c530e223d9569661946c50c5a7f52f Mon Sep 17 00:00:00 2001 From: Neur0toxine Date: Fri, 24 Nov 2023 15:23:07 +0300 Subject: [PATCH] integrate zap logger --- core/engine.go | 20 ++- core/engine_test.go | 2 +- core/healthcheck/processor.go | 17 ++- core/healthcheck/processor_test.go | 16 +-- core/job_manager.go | 10 +- core/job_manager_test.go | 120 +++++++++++------- core/logger/attrs.go | 29 +++-- core/logger/default.go | 112 ++++++---------- core/logger/gin.go | 21 +-- core/logger/handler.go | 8 -- core/logger/logger.go | 44 ------- core/logger/mg_transport_client_adapter.go | 1 + core/logger/nil.go | 52 ++++++++ core/logger/nil_handler.go | 26 ---- core/logger/writer_adapter.go | 10 +- core/logger/zap.go | 52 ++++++++ core/module_features_uploader.go | 8 +- core/sentry.go | 6 +- core/util/httputil/http_client_builder.go | 4 +- .../util/httputil/http_client_builder_test.go | 2 +- core/util/testutil/buffer_logger.go | 25 +++- core/util/testutil/buffer_logger_test.go | 6 +- core/util/testutil/lockable_buffer.go | 5 + core/util/utils.go | 5 +- core/util/utils_test.go | 2 +- go.mod | 2 + go.sum | 11 +- 27 files changed, 330 insertions(+), 286 deletions(-) delete mode 100644 core/logger/handler.go delete mode 100644 core/logger/logger.go create mode 100644 core/logger/nil.go delete mode 100644 core/logger/nil_handler.go create mode 100644 core/logger/zap.go diff --git a/core/engine.go b/core/engine.go index 655920e..8c318b0 100644 --- a/core/engine.go +++ b/core/engine.go @@ -5,7 +5,6 @@ import ( "fmt" "html/template" "io/fs" - "log/slog" "net/http" "sync" @@ -15,6 +14,7 @@ import ( "github.com/gorilla/securecookie" "github.com/gorilla/sessions" "github.com/retailcrm/zabbix-metrics-collector" + "go.uber.org/zap" "golang.org/x/text/language" "github.com/retailcrm/mg-transport-core/v2/core/config" @@ -141,13 +141,9 @@ func (e *Engine) Prepare() *Engine { e.Localizer.Preload(e.PreloadLanguages) } - if !e.Config.IsDebug() { - logger.DefaultOpts.Level = slog.LevelInfo - } - e.CreateDB(e.Config.GetDBConfig()) e.ResetUtils(e.Config.GetAWSConfig(), e.Config.IsDebug(), 0) - e.SetLogger(logger.NewDefaultText()) + e.SetLogger(logger.NewDefault(e.Config.IsDebug())) e.Sentry.Localizer = &e.Localizer e.Utils.Logger = e.Logger() e.Sentry.Logger = e.Logger() @@ -180,14 +176,14 @@ func (e *Engine) HijackGinLogs() *Engine { if e.Logger() == nil { return e } - gin.DefaultWriter = logger.WriterAdapter(e.Logger(), slog.LevelDebug) - gin.DefaultErrorWriter = logger.WriterAdapter(e.Logger(), slog.LevelError) + gin.DefaultWriter = logger.WriterAdapter(e.Logger(), zap.DebugLevel) + gin.DefaultErrorWriter = logger.WriterAdapter(e.Logger(), zap.ErrorLevel) gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { e.Logger().Debug("route", - slog.String(logger.HTTPMethodAttr, httpMethod), - slog.String("path", absolutePath), - slog.String(logger.HandlerAttr, handlerName), - slog.Int("handlerCount", nuHandlers)) + zap.String(logger.HTTPMethodAttr, httpMethod), + zap.String("path", absolutePath), + zap.String(logger.HandlerAttr, handlerName), + zap.Int("handlerCount", nuHandlers)) } return e } diff --git a/core/engine_test.go b/core/engine_test.go index b59e5d3..2d89d76 100644 --- a/core/engine_test.go +++ b/core/engine_test.go @@ -252,7 +252,7 @@ func (e *EngineTest) Test_SetLogger() { defer func() { e.engine.logger = origLogger }() - e.engine.logger = logger.NewDefaultNil() + e.engine.logger = logger.NewNil() e.engine.SetLogger(nil) assert.NotNil(e.T(), e.engine.logger) } diff --git a/core/healthcheck/processor.go b/core/healthcheck/processor.go index e75ca93..f7f4216 100644 --- a/core/healthcheck/processor.go +++ b/core/healthcheck/processor.go @@ -1,9 +1,8 @@ package healthcheck import ( - "log/slog" - "github.com/retailcrm/mg-transport-core/v2/core/logger" + "go.uber.org/zap" ) const ( @@ -31,19 +30,19 @@ type CounterProcessor struct { func (c CounterProcessor) Process(id int, counter Counter) bool { // nolint:varnamelen if counter.IsFailed() { if counter.IsFailureProcessed() { - c.debugLog("skipping counter because its failure is already processed", slog.Int(logger.CounterIDAttr, id)) + c.debugLog("skipping counter because its failure is already processed", zap.Int(logger.CounterIDAttr, id)) return true } apiURL, apiKey, _, exists := c.ConnectionDataProvider(id) if !exists { - c.debugLog("cannot find connection data for counter", slog.Int(logger.CounterIDAttr, id)) + c.debugLog("cannot find connection data for counter", zap.Int(logger.CounterIDAttr, id)) return true } err := c.Notifier(apiURL, apiKey, counter.Message()) if err != nil { c.debugLog("cannot send notification for counter", - slog.Int(logger.CounterIDAttr, id), logger.Err(err), slog.String(logger.FailureMessageAttr, counter.Message())) + zap.Int(logger.CounterIDAttr, id), logger.Err(err), zap.String(logger.FailureMessageAttr, counter.Message())) } counter.FailureProcessed() return true @@ -56,7 +55,7 @@ func (c CounterProcessor) Process(id int, counter Counter) bool { // nolint:varn // The results may not be representative. if (succeeded + failed) < c.MinRequests { c.debugLog("skipping counter because it has too few requests", - slog.Int(logger.CounterIDAttr, id), slog.Any("minRequests", c.MinRequests)) + zap.Int(logger.CounterIDAttr, id), zap.Any("minRequests", c.MinRequests)) return true } @@ -75,13 +74,13 @@ func (c CounterProcessor) Process(id int, counter Counter) bool { // nolint:varn apiURL, apiKey, lang, exists := c.ConnectionDataProvider(id) if !exists { - c.debugLog("cannot find connection data for counter", slog.Int(logger.CounterIDAttr, id)) + c.debugLog("cannot find connection data for counter", zap.Int(logger.CounterIDAttr, id)) return true } err := c.Notifier(apiURL, apiKey, c.getErrorText(counter.Name(), c.Error, lang)) if err != nil { c.debugLog("cannot send notification for counter", - slog.Int(logger.CounterIDAttr, id), logger.Err(err), slog.String(logger.FailureMessageAttr, counter.Message())) + zap.Int(logger.CounterIDAttr, id), logger.Err(err), zap.String(logger.FailureMessageAttr, counter.Message())) } counter.CountersProcessed() return true @@ -99,6 +98,6 @@ func (c CounterProcessor) getErrorText(name, msg, lang string) string { func (c CounterProcessor) debugLog(msg string, args ...interface{}) { if c.Debug { - c.Logger.Debug(msg, args...) + c.Logger.Debug(msg, logger.AnyZapFields(args)...) } } diff --git a/core/healthcheck/processor_test.go b/core/healthcheck/processor_test.go index 8ffa8d5..0492454 100644 --- a/core/healthcheck/processor_test.go +++ b/core/healthcheck/processor_test.go @@ -96,7 +96,7 @@ func (t *CounterProcessorTest) Test_FailureProcessed() { p.Process(1, c) c.AssertExpectations(t.T()) t.Assert().Contains(log.String(), "skipping counter because its failure is already processed") - t.Assert().Contains(log.String(), "counterId=1") + t.Assert().Contains(log.String(), `"counterId": 1`) } func (t *CounterProcessorTest) Test_CounterFailed_CannotFindConnection() { @@ -109,7 +109,7 @@ func (t *CounterProcessorTest) Test_CounterFailed_CannotFindConnection() { p.Process(1, c) c.AssertExpectations(t.T()) t.Assert().Contains(log.String(), "cannot find connection data for counter") - t.Assert().Contains(log.String(), "counterId=1") + t.Assert().Contains(log.String(), `"counterId": 1`) } func (t *CounterProcessorTest) Test_CounterFailed_ErrWhileNotifying() { @@ -124,9 +124,9 @@ func (t *CounterProcessorTest) Test_CounterFailed_ErrWhileNotifying() { p.Process(1, c) c.AssertExpectations(t.T()) t.Assert().Contains(log.String(), "cannot send notification for counter") - t.Assert().Contains(log.String(), "counterId=1") - t.Assert().Contains(log.String(), `error="http status code: 500"`) - t.Assert().Contains(log.String(), `failureMessage="error message"`) + t.Assert().Contains(log.String(), `"counterId": 1`) + t.Assert().Contains(log.String(), `"error": "http status code: 500"`) + t.Assert().Contains(log.String(), `"failureMessage": "error message"`) t.Assert().Equal(t.apiURL, n.apiURL) t.Assert().Equal(t.apiKey, n.apiKey) t.Assert().Equal("error message", n.message) @@ -160,7 +160,7 @@ func (t *CounterProcessorTest) Test_TooFewRequests() { p.Process(1, c) c.AssertExpectations(t.T()) t.Assert().Contains(log.String(), - fmt.Sprintf(`msg="skipping counter because it has too few requests" counterId=%d minRequests=%d`, 1, DefaultMinRequests)) + fmt.Sprintf(`skipping counter because it has too few requests {"counterId": %d, "minRequests": %d}`, 1, DefaultMinRequests)) } func (t *CounterProcessorTest) Test_ThresholdNotPassed() { @@ -206,7 +206,7 @@ func (t *CounterProcessorTest) Test_ThresholdPassed_NoConnectionFound() { p.Process(1, c) c.AssertExpectations(t.T()) t.Assert().Contains(log.String(), "cannot find connection data for counter") - t.Assert().Contains(log.String(), "counterId=1") + t.Assert().Contains(log.String(), `"counterId": 1`) t.Assert().Empty(n.message) } @@ -224,7 +224,7 @@ func (t *CounterProcessorTest) Test_ThresholdPassed_NotifyingError() { p.Process(1, c) c.AssertExpectations(t.T()) - t.Assert().Contains(log.String(), `msg="cannot send notification for counter" counterId=1 error="unknown error" failureMessage=""`) + t.Assert().Contains(log.String(), `cannot send notification for counter {"counterId": 1, "error": "unknown error", "failureMessage": ""}`) t.Assert().Equal(`default error [{"Name":"MockedCounter"}]`, n.message) } diff --git a/core/job_manager.go b/core/job_manager.go index da50bdd..4a734cc 100644 --- a/core/job_manager.go +++ b/core/job_manager.go @@ -3,11 +3,11 @@ package core import ( "errors" "fmt" - "log/slog" "sync" "time" "github.com/retailcrm/mg-transport-core/v2/core/logger" + "go.uber.org/zap" ) // JobFunc is empty func which should be executed in a parallel goroutine. @@ -74,7 +74,7 @@ func (j *Job) getWrappedFunc(name string, log logger.Logger) func(callback JobAf } }() - log = log.With(logger.HandlerAttr, name) + log = log.With(logger.Handler(name)) err := j.Command(log) if err != nil && j.ErrorHandler != nil { j.ErrorHandler(name, err, log) @@ -148,14 +148,14 @@ func (j *Job) runOnceSync(name string, log logger.Logger) { // NewJobManager is a JobManager constructor. func NewJobManager() *JobManager { - return &JobManager{jobs: &sync.Map{}, nilLogger: logger.NewDefaultNil()} + return &JobManager{jobs: &sync.Map{}, nilLogger: logger.NewNil()} } // DefaultJobErrorHandler returns default error handler for a job. func DefaultJobErrorHandler() JobErrorHandler { return func(name string, err error, log logger.Logger) { if err != nil && name != "" { - log.Error("job failed with an error", slog.String("job", name), logger.Err(err)) + log.Error("job failed with an error", zap.String("job", name), logger.Err(err)) } } } @@ -164,7 +164,7 @@ func DefaultJobErrorHandler() JobErrorHandler { func DefaultJobPanicHandler() JobPanicHandler { return func(name string, recoverValue interface{}, log logger.Logger) { if recoverValue != nil && name != "" { - log.Error("job panicked with the value", slog.String("job", name), slog.Any("value", recoverValue)) + log.Error("job panicked with the value", zap.String("job", name), zap.Any("value", recoverValue)) } } } diff --git a/core/job_manager_test.go b/core/job_manager_test.go index dbfb09c..6de4fc5 100644 --- a/core/job_manager_test.go +++ b/core/job_manager_test.go @@ -1,10 +1,8 @@ package core import ( - "context" "errors" "fmt" - "log/slog" "math/rand" "strings" "sync" @@ -14,6 +12,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/retailcrm/mg-transport-core/v2/core/logger" ) @@ -26,7 +26,7 @@ type JobTest struct { executeErr chan error panicValue chan interface{} lastLog string - lastMsgLevel slog.Level + lastMsgLevel zapcore.Level syncBool bool } @@ -37,18 +37,58 @@ type JobManagerTest struct { syncRunnerFlag bool } -type callbackLoggerFunc func(ctx context.Context, level slog.Level, msg string, args ...any) +type callbackLoggerFunc func(level zapcore.Level, msg string, args ...zap.Field) type callbackLogger struct { - fn callbackLoggerFunc + fields []zap.Field + fn callbackLoggerFunc } -func (n *callbackLogger) Handler() slog.Handler { - return logger.NilHandler +func (n *callbackLogger) Check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry { + return &zapcore.CheckedEntry{} } -func (n *callbackLogger) With(args ...any) logger.Logger { - return n +func (n *callbackLogger) DPanic(msg string, fields ...zap.Field) { + n.fn(zap.PanicLevel, msg, fields...) +} + +func (n *callbackLogger) Panic(msg string, fields ...zap.Field) { + n.fn(zap.PanicLevel, msg, fields...) +} + +func (n *callbackLogger) Fatal(msg string, fields ...zap.Field) { + n.fn(zap.FatalLevel, msg, fields...) +} + +func (n *callbackLogger) Sync() error { + return nil +} + +func (n *callbackLogger) clone() *callbackLogger { + return &callbackLogger{fn: n.fn, fields: n.fields} +} + +func (n *callbackLogger) cloneWithFields(fields []zap.Field) *callbackLogger { + cl := &callbackLogger{fn: n.fn, fields: n.fields} + existing := cl.fields + if len(existing) == 0 { + cl.fields = fields + return cl + } + cl.fields = append(existing, fields...) + return cl +} + +func (n *callbackLogger) Level() zapcore.Level { + return zapcore.DebugLevel +} + +func (n *callbackLogger) With(args ...zap.Field) logger.Logger { + return n.cloneWithFields(args) +} + +func (n *callbackLogger) WithLazy(args ...zap.Field) logger.Logger { + return n.cloneWithFields(args) } func (n *callbackLogger) WithGroup(name string) logger.Logger { @@ -59,47 +99,24 @@ func (n *callbackLogger) ForAccount(handler, conn, acc any) logger.Logger { return n } -func (n *callbackLogger) Enabled(ctx context.Context, level slog.Level) bool { - return true +func (n *callbackLogger) Log(level zapcore.Level, msg string, args ...zap.Field) { + n.fn(level, msg, args...) } -func (n *callbackLogger) Log(ctx context.Context, level slog.Level, msg string, args ...any) { - n.fn(ctx, level, msg, args...) +func (n *callbackLogger) Debug(msg string, args ...zap.Field) { + n.Log(zap.DebugLevel, msg, args...) } -func (n *callbackLogger) LogAttrs(ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr) { +func (n *callbackLogger) Info(msg string, args ...zap.Field) { + n.Log(zap.InfoLevel, msg, args...) } -func (n *callbackLogger) Debug(msg string, args ...any) { - n.Log(nil, slog.LevelDebug, msg, args...) +func (n *callbackLogger) Warn(msg string, args ...zap.Field) { + n.Log(zap.WarnLevel, msg, args...) } -func (n *callbackLogger) DebugContext(ctx context.Context, msg string, args ...any) { - n.Log(ctx, slog.LevelDebug, msg, args...) -} - -func (n *callbackLogger) Info(msg string, args ...any) { - n.Log(nil, slog.LevelInfo, msg, args...) -} - -func (n *callbackLogger) InfoContext(ctx context.Context, msg string, args ...any) { - n.Log(ctx, slog.LevelInfo, msg, args...) -} - -func (n *callbackLogger) Warn(msg string, args ...any) { - n.Log(nil, slog.LevelWarn, msg, args...) -} - -func (n *callbackLogger) WarnContext(ctx context.Context, msg string, args ...any) { - n.Log(ctx, slog.LevelWarn, msg, args...) -} - -func (n *callbackLogger) Error(msg string, args ...any) { - n.Log(nil, slog.LevelError, msg, args...) -} - -func (n *callbackLogger) ErrorContext(ctx context.Context, msg string, args ...any) { - n.Log(ctx, slog.LevelError, msg, args...) +func (n *callbackLogger) Error(msg string, args ...zap.Field) { + n.Log(zap.ErrorLevel, msg, args...) } func TestJob(t *testing.T) { @@ -117,9 +134,9 @@ func TestDefaultJobErrorHandler(t *testing.T) { fn := DefaultJobErrorHandler() require.NotNil(t, fn) - fn("job", errors.New("test"), &callbackLogger{fn: func(_ context.Context, level slog.Level, s string, i ...interface{}) { + fn("job", errors.New("test"), &callbackLogger{fn: func(level zapcore.Level, s string, i ...zap.Field) { require.Len(t, i, 2) - assert.Equal(t, "error=test", fmt.Sprintf("%s", i[1])) + assert.Equal(t, "error=test", fmt.Sprintf("%s=%v", i[1].Key, i[1].Interface)) }}) } @@ -130,9 +147,9 @@ func TestDefaultJobPanicHandler(t *testing.T) { fn := DefaultJobPanicHandler() require.NotNil(t, fn) - fn("job", errors.New("test"), &callbackLogger{fn: func(_ context.Context, level slog.Level, s string, i ...interface{}) { + fn("job", errors.New("test"), &callbackLogger{fn: func(level zapcore.Level, s string, i ...zap.Field) { require.Len(t, i, 2) - assert.Equal(t, "value=test", fmt.Sprintf("%s", i[1])) + assert.Equal(t, "value=test", fmt.Sprintf("%s=%s", i[1].Key, i[1].Interface)) }}) } @@ -149,7 +166,7 @@ func (t *JobTest) testPanicHandler() JobPanicHandler { } func (t *JobTest) testLogger() logger.Logger { - return &callbackLogger{fn: func(_ context.Context, level slog.Level, format string, args ...interface{}) { + return &callbackLogger{fn: func(level zapcore.Level, format string, args ...zap.Field) { if format == "" { var sb strings.Builder sb.Grow(3 * len(args)) // nolint:gomnd @@ -161,7 +178,12 @@ func (t *JobTest) testLogger() logger.Logger { format = strings.TrimRight(sb.String(), " ") } - t.lastLog = fmt.Sprintf(format, args...) + anyFields := []any{} + for _, item := range args { + anyFields = append(anyFields, item.Key+"="+fmt.Sprint(item.Interface)) + } + + t.lastLog = fmt.Sprintf(format, anyFields...) t.lastMsgLevel = level }} } @@ -406,7 +428,7 @@ func (t *JobManagerTest) WaitForJob() bool { func (t *JobManagerTest) Test_SetLogger() { t.manager.logger = nil - t.manager.SetLogger(logger.NewDefaultText()) + t.manager.SetLogger(logger.NewDefault(true)) assert.IsType(t.T(), &logger.Default{}, t.manager.logger) t.manager.SetLogger(nil) diff --git a/core/logger/attrs.go b/core/logger/attrs.go index 3f21387..17bd746 100644 --- a/core/logger/attrs.go +++ b/core/logger/attrs.go @@ -2,8 +2,9 @@ package logger import ( "fmt" - "log/slog" "net/http" + + "go.uber.org/zap" ) const ( @@ -19,28 +20,32 @@ const ( HTTPStatusNameAttr = "statusName" ) -func Err(err any) slog.Attr { +func Err(err any) zap.Field { if err == nil { - return slog.String(ErrorAttr, "") + return zap.String(ErrorAttr, "") } - return slog.Any(ErrorAttr, err) + return zap.Any(ErrorAttr, err) } -func HTTPStatusCode(code int) slog.Attr { - return slog.Int(HTTPStatusAttr, code) +func Handler(name string) zap.Field { + return zap.String(HandlerAttr, name) } -func HTTPStatusName(code int) slog.Attr { - return slog.String(HTTPStatusNameAttr, http.StatusText(code)) +func HTTPStatusCode(code int) zap.Field { + return zap.Int(HTTPStatusAttr, code) } -func Body(val any) slog.Attr { +func HTTPStatusName(code int) zap.Field { + return zap.String(HTTPStatusNameAttr, http.StatusText(code)) +} + +func Body(val any) zap.Field { switch item := val.(type) { case string: - return slog.String(BodyAttr, item) + return zap.String(BodyAttr, item) case []byte: - return slog.String(BodyAttr, string(item)) + return zap.String(BodyAttr, string(item)) default: - return slog.String(BodyAttr, fmt.Sprintf("%#v", val)) + return zap.String(BodyAttr, fmt.Sprintf("%#v", val)) } } diff --git a/core/logger/default.go b/core/logger/default.go index 78240a3..dc92980 100644 --- a/core/logger/default.go +++ b/core/logger/default.go @@ -1,87 +1,59 @@ package logger import ( - "context" - "log/slog" - "os" + "strconv" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" ) +type Logger interface { + With(fields ...zap.Field) Logger + WithLazy(fields ...zap.Field) Logger + Level() zapcore.Level + Check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry + Log(lvl zapcore.Level, msg string, fields ...zap.Field) + Debug(msg string, fields ...zap.Field) + Info(msg string, fields ...zap.Field) + Warn(msg string, fields ...zap.Field) + Error(msg string, fields ...zap.Field) + DPanic(msg string, fields ...zap.Field) + Panic(msg string, fields ...zap.Field) + Fatal(msg string, fields ...zap.Field) + ForAccount(handler, conn, acc any) Logger + Sync() error +} + type Default struct { - Logger *slog.Logger + *zap.Logger } -func NewDefault(log *slog.Logger) Logger { - return &Default{Logger: log} +func NewDefault(debug bool) Logger { + return &Default{ + Logger: NewZap(debug), + } } -func NewDefaultText() Logger { - return NewDefault(slog.New(slog.NewTextHandler(os.Stdout, DefaultOpts))) +func (l *Default) With(fields ...zap.Field) Logger { + return l.With(fields...).(Logger) } -func NewDefaultJSON() Logger { - return NewDefault(slog.New(slog.NewJSONHandler(os.Stdout, DefaultOpts))) +func (l *Default) WithLazy(fields ...zap.Field) Logger { + return l.WithLazy(fields...).(Logger) } -func NewDefaultNil() Logger { - return NewDefault(slog.New(NilHandler)) +func (l *Default) ForAccount(handler, conn, acc any) Logger { + return l.WithLazy(zap.Any(HandlerAttr, handler), zap.Any(ConnectionAttr, conn), zap.Any(AccountAttr, acc)) } -func (d *Default) Handler() slog.Handler { - return d.Logger.Handler() -} - -func (d *Default) ForAccount(handler, conn, acc any) Logger { - return d.With(slog.Any(HandlerAttr, handler), slog.Any(ConnectionAttr, conn), slog.Any(AccountAttr, acc)) -} - -func (d *Default) With(args ...any) Logger { - return &Default{Logger: d.Logger.With(args...)} -} - -func (d *Default) WithGroup(name string) Logger { - return &Default{Logger: d.Logger.WithGroup(name)} -} - -func (d *Default) Enabled(ctx context.Context, level slog.Level) bool { - return d.Logger.Enabled(ctx, level) -} - -func (d *Default) Log(ctx context.Context, level slog.Level, msg string, args ...any) { - d.Logger.Log(ctx, level, msg, args...) -} - -func (d *Default) LogAttrs(ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr) { - d.Logger.LogAttrs(ctx, level, msg, attrs...) -} - -func (d *Default) Debug(msg string, args ...any) { - d.Logger.Debug(msg, args...) -} - -func (d *Default) DebugContext(ctx context.Context, msg string, args ...any) { - d.Logger.DebugContext(ctx, msg, args...) -} - -func (d *Default) Info(msg string, args ...any) { - d.Logger.Info(msg, args...) -} - -func (d *Default) InfoContext(ctx context.Context, msg string, args ...any) { - d.Logger.InfoContext(ctx, msg, args...) -} - -func (d *Default) Warn(msg string, args ...any) { - d.Logger.Warn(msg, args...) -} - -func (d *Default) WarnContext(ctx context.Context, msg string, args ...any) { - d.Logger.WarnContext(ctx, msg, args...) -} - -func (d *Default) Error(msg string, args ...any) { - d.Logger.Error(msg, args...) -} - -func (d *Default) ErrorContext(ctx context.Context, msg string, args ...any) { - d.Logger.ErrorContext(ctx, msg, args...) +func AnyZapFields(args []interface{}) []zap.Field { + fields := make([]zap.Field, len(args)) + for i := 0; i < len(fields); i++ { + if val, ok := args[i].(zap.Field); ok { + fields[i] = val + continue + } + fields[i] = zap.Any("arg"+strconv.Itoa(i), args[i]) + } + return fields } diff --git a/core/logger/gin.go b/core/logger/gin.go index 0443468..5ecf3b2 100644 --- a/core/logger/gin.go +++ b/core/logger/gin.go @@ -1,9 +1,10 @@ package logger import ( - "github.com/gin-gonic/gin" - "log/slog" "time" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" ) // GinMiddleware will construct Gin middleware which will log requests. @@ -23,14 +24,14 @@ func GinMiddleware(log Logger) gin.HandlerFunc { } log.Info("request", - slog.String(HandlerAttr, "GIN"), - slog.String("startTime", start.Format(time.RFC3339)), - slog.String("endTime", end.Format(time.RFC3339)), - slog.Any("latency", end.Sub(start)/time.Millisecond), - slog.String("remoteAddress", c.ClientIP()), - slog.String(HTTPMethodAttr, c.Request.Method), - slog.String("path", path), - slog.Int("bodySize", c.Writer.Size()), + zap.String(HandlerAttr, "GIN"), + zap.String("startTime", start.Format(time.RFC3339)), + zap.String("endTime", end.Format(time.RFC3339)), + zap.Any("latency", end.Sub(start)/time.Millisecond), + zap.String("remoteAddress", c.ClientIP()), + zap.String(HTTPMethodAttr, c.Request.Method), + zap.String("path", path), + zap.Int("bodySize", c.Writer.Size()), ) } } diff --git a/core/logger/handler.go b/core/logger/handler.go deleted file mode 100644 index 1c5ffe9..0000000 --- a/core/logger/handler.go +++ /dev/null @@ -1,8 +0,0 @@ -package logger - -import "log/slog" - -var DefaultOpts = &slog.HandlerOptions{ - AddSource: false, - Level: slog.LevelDebug, -} diff --git a/core/logger/logger.go b/core/logger/logger.go deleted file mode 100644 index 4fb9f16..0000000 --- a/core/logger/logger.go +++ /dev/null @@ -1,44 +0,0 @@ -package logger - -import ( - "context" - "log/slog" -) - -// LoggerOld contains methods which should be present in logger implementation. -type LoggerOld interface { - Fatal(args ...any) - Fatalf(format string, args ...any) - Panic(args ...any) - Panicf(format string, args ...any) - Critical(args ...any) - Criticalf(format string, args ...any) - Error(args ...any) - Errorf(format string, args ...any) - Warning(args ...any) - Warningf(format string, args ...any) - Notice(args ...any) - Noticef(format string, args ...any) - Info(args ...any) - Infof(format string, args ...any) - Debug(args ...any) - Debugf(format string, args ...any) -} - -type Logger interface { - Handler() slog.Handler - With(args ...any) Logger - WithGroup(name string) Logger - ForAccount(handler, conn, acc any) Logger - Enabled(ctx context.Context, level slog.Level) bool - Log(ctx context.Context, level slog.Level, msg string, args ...any) - LogAttrs(ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr) - Debug(msg string, args ...any) - DebugContext(ctx context.Context, msg string, args ...any) - Info(msg string, args ...any) - InfoContext(ctx context.Context, msg string, args ...any) - Warn(msg string, args ...any) - WarnContext(ctx context.Context, msg string, args ...any) - Error(msg string, args ...any) - ErrorContext(ctx context.Context, msg string, args ...any) -} diff --git a/core/logger/mg_transport_client_adapter.go b/core/logger/mg_transport_client_adapter.go index 0b1dbb7..917056f 100644 --- a/core/logger/mg_transport_client_adapter.go +++ b/core/logger/mg_transport_client_adapter.go @@ -2,6 +2,7 @@ package logger import ( "fmt" + v1 "github.com/retailcrm/mg-transport-api-client-go/v1" ) diff --git a/core/logger/nil.go b/core/logger/nil.go new file mode 100644 index 0000000..74d0257 --- /dev/null +++ b/core/logger/nil.go @@ -0,0 +1,52 @@ +package logger + +import ( + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type Nil struct{} + +func NewNil() Logger { + return &Nil{} +} + +func (l *Nil) With(fields ...zap.Field) Logger { + return l +} + +func (l *Nil) WithLazy(fields ...zap.Field) Logger { + return l +} + +func (l *Nil) Level() zapcore.Level { + return zapcore.DebugLevel +} + +func (l *Nil) Check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry { + return &zapcore.CheckedEntry{} +} + +func (l *Nil) Log(lvl zapcore.Level, msg string, fields ...zap.Field) {} + +func (l *Nil) Debug(msg string, fields ...zap.Field) {} + +func (l *Nil) Info(msg string, fields ...zap.Field) {} + +func (l *Nil) Warn(msg string, fields ...zap.Field) {} + +func (l *Nil) Error(msg string, fields ...zap.Field) {} + +func (l *Nil) DPanic(msg string, fields ...zap.Field) {} + +func (l *Nil) Panic(msg string, fields ...zap.Field) {} + +func (l *Nil) Fatal(msg string, fields ...zap.Field) {} + +func (l *Nil) ForAccount(handler, conn, acc any) Logger { + return l +} + +func (l *Nil) Sync() error { + return nil +} diff --git a/core/logger/nil_handler.go b/core/logger/nil_handler.go deleted file mode 100644 index 6b167b3..0000000 --- a/core/logger/nil_handler.go +++ /dev/null @@ -1,26 +0,0 @@ -package logger - -import ( - "context" - "log/slog" -) - -var NilHandler slog.Handler = &nilHandler{} - -type nilHandler struct{} - -func (n *nilHandler) Enabled(ctx context.Context, level slog.Level) bool { - return false -} - -func (n *nilHandler) Handle(ctx context.Context, record slog.Record) error { - return nil -} - -func (n *nilHandler) WithAttrs(attrs []slog.Attr) slog.Handler { - return n -} - -func (n *nilHandler) WithGroup(name string) slog.Handler { - return n -} diff --git a/core/logger/writer_adapter.go b/core/logger/writer_adapter.go index 890fe80..e80459e 100644 --- a/core/logger/writer_adapter.go +++ b/core/logger/writer_adapter.go @@ -1,21 +1,21 @@ package logger import ( - "context" "io" - "log/slog" + + "go.uber.org/zap/zapcore" ) type writerAdapter struct { log Logger - level slog.Level + level zapcore.Level } -func WriterAdapter(log Logger, level slog.Level) io.Writer { +func WriterAdapter(log Logger, level zapcore.Level) io.Writer { return &writerAdapter{log: log, level: level} } func (w *writerAdapter) Write(p []byte) (n int, err error) { - w.log.Log(context.Background(), w.level, string(p)) + w.log.Log(w.level, string(p)) return len(p), nil } diff --git a/core/logger/zap.go b/core/logger/zap.go new file mode 100644 index 0000000..69db51e --- /dev/null +++ b/core/logger/zap.go @@ -0,0 +1,52 @@ +package logger + +import ( + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +func NewZap(debug bool) *zap.Logger { + level := zapcore.InfoLevel + if debug { + level = zapcore.DebugLevel + } + log, err := zap.Config{ + Level: zap.NewAtomicLevelAt(level), + Development: debug, + Encoding: "console", + EncoderConfig: EncoderConfig(), + OutputPaths: []string{"stdout"}, + ErrorOutputPaths: []string{"stderr"}, + }.Build() + if err != nil { + panic(err) + } + return log +} + +func EncoderConfig() zapcore.EncoderConfig { + return zapcore.EncoderConfig{ + MessageKey: "message", + LevelKey: "level", + TimeKey: "timestamp", + NameKey: "logger", + CallerKey: "caller", + FunctionKey: zapcore.OmitKey, + StacktraceKey: "", + LineEnding: "\n", + EncodeLevel: func(level zapcore.Level, encoder zapcore.PrimitiveArrayEncoder) { + encoder.AppendString("level=" + level.String()) + }, + EncodeTime: func(t time.Time, encoder zapcore.PrimitiveArrayEncoder) { + encoder.AppendString("time=" + t.Format(time.RFC3339)) + }, + EncodeDuration: zapcore.StringDurationEncoder, + EncodeCaller: func(caller zapcore.EntryCaller, encoder zapcore.PrimitiveArrayEncoder) { + encoder.AppendString("caller=" + caller.TrimmedPath()) + }, + EncodeName: zapcore.FullNameEncoder, + ConsoleSeparator: " ", + } +} diff --git a/core/module_features_uploader.go b/core/module_features_uploader.go index 306870b..d014938 100644 --- a/core/module_features_uploader.go +++ b/core/module_features_uploader.go @@ -5,7 +5,6 @@ import ( "context" "fmt" "html/template" - "log/slog" "os" "github.com/aws/aws-sdk-go-v2/aws" @@ -13,6 +12,7 @@ import ( "github.com/aws/aws-sdk-go-v2/feature/s3/manager" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/gomarkdown/markdown" + "go.uber.org/zap" "golang.org/x/text/language" "github.com/retailcrm/mg-transport-core/v2/core/config" @@ -91,14 +91,14 @@ func (s *ModuleFeaturesUploader) Upload() { content, err := os.ReadFile(s.featuresFilename) if err != nil { - s.log.Error("cannot read markdown file %s %s", slog.String("fileName", s.featuresFilename), logger.Err(err)) + s.log.Error("cannot read markdown file %s %s", zap.String("fileName", s.featuresFilename), logger.Err(err)) return } for _, lang := range languages { translated, err := s.translate(content, lang) if err != nil { - s.log.Error("cannot translate module features file", slog.String("lang", lang.String()), logger.Err(err)) + s.log.Error("cannot translate module features file", zap.String("lang", lang.String()), logger.Err(err)) continue } @@ -106,7 +106,7 @@ func (s *ModuleFeaturesUploader) Upload() { resp, err := s.uploadFile(html, lang.String()) if err != nil { - s.log.Error("cannot upload file", slog.String("lang", lang.String()), logger.Err(err)) + s.log.Error("cannot upload file", zap.String("lang", lang.String()), logger.Err(err)) continue } diff --git a/core/sentry.go b/core/sentry.go index 4b23565..27b2057 100644 --- a/core/sentry.go +++ b/core/sentry.go @@ -2,7 +2,6 @@ package core import ( "fmt" - "log/slog" "net" "net/http" "net/http/httputil" @@ -16,6 +15,7 @@ import ( "github.com/getsentry/sentry-go" sentrygin "github.com/getsentry/sentry-go/gin" "github.com/pkg/errors" + "go.uber.org/zap" "github.com/retailcrm/mg-transport-core/v2/core/logger" "github.com/retailcrm/mg-transport-core/v2/core/stacktrace" @@ -260,8 +260,8 @@ func (s *Sentry) recoveryMiddleware() gin.HandlerFunc { // nolint } headers[idx] = "header: " + headers[idx] } - headersToStr := slog.String("headers", strings.Join(headers, "\r\n")) - formattedStack := slog.String("stacktrace", string(stack)) + headersToStr := zap.String("headers", strings.Join(headers, "\r\n")) + formattedStack := zap.String("stacktrace", string(stack)) switch { case brokenPipe: l.Error("error", formattedErr, headersToStr) diff --git a/core/util/httputil/http_client_builder.go b/core/util/httputil/http_client_builder.go index 0948fcb..3d8bf3a 100644 --- a/core/util/httputil/http_client_builder.go +++ b/core/util/httputil/http_client_builder.go @@ -12,9 +12,7 @@ import ( "time" "github.com/pkg/errors" - "github.com/retailcrm/mg-transport-core/v2/core/config" - "github.com/retailcrm/mg-transport-core/v2/core/logger" ) @@ -275,7 +273,7 @@ func (b *HTTPClientBuilder) buildMocks() error { func (b *HTTPClientBuilder) log(msg string, args ...interface{}) { if b.logging { if b.logger != nil { - b.logger.Info(msg, args...) + b.logger.Info(msg, logger.AnyZapFields(args)...) } else { fmt.Println(append([]any{msg}, args...)) } diff --git a/core/util/httputil/http_client_builder_test.go b/core/util/httputil/http_client_builder_test.go index dae78c4..f2e9356 100644 --- a/core/util/httputil/http_client_builder_test.go +++ b/core/util/httputil/http_client_builder_test.go @@ -142,7 +142,7 @@ func (t *HTTPClientBuilderTest) Test_WithLogger() { builder.WithLogger(nil) assert.Nil(t.T(), builder.logger) - log := logger.NewDefaultText() + log := logger.NewDefault(true) builder.WithLogger(log) assert.NotNil(t.T(), builder.logger) } diff --git a/core/util/testutil/buffer_logger.go b/core/util/testutil/buffer_logger.go index f28713d..a7d0723 100644 --- a/core/util/testutil/buffer_logger.go +++ b/core/util/testutil/buffer_logger.go @@ -3,9 +3,11 @@ package testutil import ( "fmt" "io" - "log/slog" + "os" "github.com/retailcrm/mg-transport-core/v2/core/logger" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" ) // ReadBuffer is implemented by the BufferLogger. @@ -32,21 +34,32 @@ type BufferLogger struct { // NewBufferedLogger returns new BufferedLogger instance. func NewBufferedLogger() BufferedLogger { bl := &BufferLogger{} - bl.Logger = slog.New(slog.NewTextHandler(&bl.buf, logger.DefaultOpts)) + bl.Logger = zap.New( + zapcore.NewCore( + zapcore.NewConsoleEncoder( + logger.EncoderConfig()), zap.CombineWriteSyncers(os.Stdout, os.Stderr, &bl.buf), zapcore.DebugLevel)) return bl } -// With doesn't do anything here and only added for backwards compatibility with the interface. -func (l *BufferLogger) With(args ...any) logger.Logger { +func (l *BufferLogger) With(fields ...zapcore.Field) logger.Logger { return &BufferLogger{ Default: logger.Default{ - Logger: l.Logger.With(args...), + Logger: l.Logger.With(fields...), + }, + } +} + +func (l *BufferLogger) WithLazy(fields ...zapcore.Field) logger.Logger { + return &BufferLogger{ + Default: logger.Default{ + Logger: l.Logger.WithLazy(fields...), }, } } func (l *BufferLogger) ForAccount(handler, conn, acc any) logger.Logger { - return l.With(slog.Any(logger.HandlerAttr, handler), slog.Any(logger.ConnectionAttr, conn), slog.Any(logger.AccountAttr, acc)) + return l.WithLazy( + zap.Any(logger.HandlerAttr, handler), zap.Any(logger.ConnectionAttr, conn), zap.Any(logger.AccountAttr, acc)) } // Read bytes from the logger buffer. io.Reader implementation. diff --git a/core/util/testutil/buffer_logger_test.go b/core/util/testutil/buffer_logger_test.go index 0bfa23e..dd4865c 100644 --- a/core/util/testutil/buffer_logger_test.go +++ b/core/util/testutil/buffer_logger_test.go @@ -29,17 +29,17 @@ func (t *BufferLoggerTest) Test_Read() { data, err := io.ReadAll(t.logger) t.Require().NoError(err) - t.Assert().Contains(string(data), "level=DEBUG msg=test") + t.Assert().Contains(string(data), "level=debug test") } func (t *BufferLoggerTest) Test_Bytes() { t.logger.Debug("test") - t.Assert().Contains(string(t.logger.Bytes()), "level=DEBUG msg=test") + t.Assert().Contains(string(t.logger.Bytes()), "level=debug test") } func (t *BufferLoggerTest) Test_String() { t.logger.Debug("test") - t.Assert().Contains(t.logger.String(), "level=DEBUG msg=test") + t.Assert().Contains(t.logger.String(), "level=debug test") } func (t *BufferLoggerTest) TestRace() { diff --git a/core/util/testutil/lockable_buffer.go b/core/util/testutil/lockable_buffer.go index ae71356..8636a9a 100644 --- a/core/util/testutil/lockable_buffer.go +++ b/core/util/testutil/lockable_buffer.go @@ -148,3 +148,8 @@ func (b *LockableBuffer) ReadString(delim byte) (line string, err error) { b.rw.Lock() return b.buf.ReadString(delim) } + +// Sync is a no-op. +func (b *LockableBuffer) Sync() error { + return nil +} diff --git a/core/util/utils.go b/core/util/utils.go index 6e098cc..27e6e71 100644 --- a/core/util/utils.go +++ b/core/util/utils.go @@ -21,7 +21,6 @@ import ( "github.com/gin-gonic/gin" retailcrm "github.com/retailcrm/api-client-go/v2" v1 "github.com/retailcrm/mg-transport-api-client-go/v1" - "github.com/retailcrm/mg-transport-core/v2/core/config" "github.com/retailcrm/mg-transport-core/v2/core/logger" @@ -145,12 +144,12 @@ func (u *Utils) GetAPIClient( if res := u.checkScopes(cr.Scopes, scopes); len(res) != 0 { if len(credentials) == 0 || len(cr.Scopes) > 0 { - u.Logger.Error(url, status, res) + u.Logger.Error(url, logger.HTTPStatusCode(status), logger.Body(res)) return nil, http.StatusBadRequest, errorutil.NewInsufficientScopesErr(res) } if res := u.checkScopes(cr.Credentials, credentials[0]); len(res) != 0 { - u.Logger.Error(url, status, res) + u.Logger.Error(url, logger.HTTPStatusCode(status), logger.Body(res)) return nil, http.StatusBadRequest, errorutil.NewInsufficientScopesErr(res) } } diff --git a/core/util/utils_test.go b/core/util/utils_test.go index 2890c54..ffa2374 100644 --- a/core/util/utils_test.go +++ b/core/util/utils_test.go @@ -38,7 +38,7 @@ func mgClient() *v1.MgClient { } func (u *UtilsTest) SetupSuite() { - logger := logger.NewDefaultText() + logger := logger.NewDefault(true) awsConfig := config.AWS{ AccessKeyID: "access key id (will be removed)", SecretAccessKey: "secret access key", diff --git a/go.mod b/go.mod index ef530ce..b458d9d 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,7 @@ require ( github.com/retailcrm/zabbix-metrics-collector v1.0.0 github.com/stretchr/testify v1.8.3 go.uber.org/atomic v1.10.0 + go.uber.org/zap v1.26.0 golang.org/x/text v0.14.0 gopkg.in/gormigrate.v1 v1.6.0 gopkg.in/yaml.v2 v2.4.0 @@ -77,6 +78,7 @@ require ( github.com/stretchr/objx v0.5.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect + go.uber.org/multierr v1.10.0 // indirect golang.org/x/arch v0.3.0 // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/net v0.23.0 // indirect diff --git a/go.sum b/go.sum index eaf8e2f..3c610f9 100644 --- a/go.sum +++ b/go.sum @@ -297,8 +297,9 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= @@ -377,8 +378,6 @@ github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/retailcrm/api-client-go/v2 v2.1.3 h1:AVcp9oeSOm6+3EWXCgdQs+XE3PTjzCKKB//MUAe0Zb0= github.com/retailcrm/api-client-go/v2 v2.1.3/go.mod h1:1yTZl9+gd3+/k0kAJe7sYvC+mL4fqMwIwtnSgSWZlkQ= -github.com/retailcrm/mg-transport-api-client-go v1.1.32 h1:IBPltSoD5q2PPZJbNC/prK5F9rEVPXVx/ZzDpi7HKhs= -github.com/retailcrm/mg-transport-api-client-go v1.1.32/go.mod h1:AWV6BueE28/6SCoyfKURTo4lF0oXYoOKmHTzehd5vAI= github.com/retailcrm/mg-transport-api-client-go v1.3.4 h1:HIn4eorABNfudn7hr5Rd6XYC/ieDTqCkaq6wv0AFTBE= github.com/retailcrm/mg-transport-api-client-go v1.3.4/go.mod h1:gDe/tj7t3Hr/uwIFSBVgGAmP85PoLajVl1A+skBo1Ro= github.com/retailcrm/zabbix-metrics-collector v1.0.0 h1:ju3rhpgVoiKII6oXEJEf2eoJy5bNcYAmOPRp1oPWDmA= @@ -444,6 +443,12 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=