integrate zap logger

This commit is contained in:
Pavel 2023-11-24 15:23:07 +03:00
parent 2455c11704
commit 96ac6a6940
27 changed files with 330 additions and 286 deletions

View File

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"html/template" "html/template"
"io/fs" "io/fs"
"log/slog"
"net/http" "net/http"
"sync" "sync"
@ -15,6 +14,7 @@ import (
"github.com/gorilla/securecookie" "github.com/gorilla/securecookie"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
"github.com/retailcrm/zabbix-metrics-collector" "github.com/retailcrm/zabbix-metrics-collector"
"go.uber.org/zap"
"golang.org/x/text/language" "golang.org/x/text/language"
"github.com/retailcrm/mg-transport-core/v2/core/config" "github.com/retailcrm/mg-transport-core/v2/core/config"
@ -141,13 +141,9 @@ func (e *Engine) Prepare() *Engine {
e.Localizer.Preload(e.PreloadLanguages) e.Localizer.Preload(e.PreloadLanguages)
} }
if !e.Config.IsDebug() {
logger.DefaultOpts.Level = slog.LevelInfo
}
e.CreateDB(e.Config.GetDBConfig()) e.CreateDB(e.Config.GetDBConfig())
e.ResetUtils(e.Config.GetAWSConfig(), e.Config.IsDebug(), 0) 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.Sentry.Localizer = &e.Localizer
e.Utils.Logger = e.Logger() e.Utils.Logger = e.Logger()
e.Sentry.Logger = e.Logger() e.Sentry.Logger = e.Logger()
@ -180,14 +176,14 @@ func (e *Engine) HijackGinLogs() *Engine {
if e.Logger() == nil { if e.Logger() == nil {
return e return e
} }
gin.DefaultWriter = logger.WriterAdapter(e.Logger(), slog.LevelDebug) gin.DefaultWriter = logger.WriterAdapter(e.Logger(), zap.DebugLevel)
gin.DefaultErrorWriter = logger.WriterAdapter(e.Logger(), slog.LevelError) gin.DefaultErrorWriter = logger.WriterAdapter(e.Logger(), zap.ErrorLevel)
gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
e.Logger().Debug("route", e.Logger().Debug("route",
slog.String(logger.HTTPMethodAttr, httpMethod), zap.String(logger.HTTPMethodAttr, httpMethod),
slog.String("path", absolutePath), zap.String("path", absolutePath),
slog.String(logger.HandlerAttr, handlerName), zap.String(logger.HandlerAttr, handlerName),
slog.Int("handlerCount", nuHandlers)) zap.Int("handlerCount", nuHandlers))
} }
return e return e
} }

View File

@ -252,7 +252,7 @@ func (e *EngineTest) Test_SetLogger() {
defer func() { defer func() {
e.engine.logger = origLogger e.engine.logger = origLogger
}() }()
e.engine.logger = logger.NewDefaultNil() e.engine.logger = logger.NewNil()
e.engine.SetLogger(nil) e.engine.SetLogger(nil)
assert.NotNil(e.T(), e.engine.logger) assert.NotNil(e.T(), e.engine.logger)
} }

View File

@ -1,9 +1,8 @@
package healthcheck package healthcheck
import ( import (
"log/slog"
"github.com/retailcrm/mg-transport-core/v2/core/logger" "github.com/retailcrm/mg-transport-core/v2/core/logger"
"go.uber.org/zap"
) )
const ( const (
@ -31,19 +30,19 @@ type CounterProcessor struct {
func (c CounterProcessor) Process(id int, counter Counter) bool { // nolint:varnamelen func (c CounterProcessor) Process(id int, counter Counter) bool { // nolint:varnamelen
if counter.IsFailed() { if counter.IsFailed() {
if counter.IsFailureProcessed() { 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 return true
} }
apiURL, apiKey, _, exists := c.ConnectionDataProvider(id) apiURL, apiKey, _, exists := c.ConnectionDataProvider(id)
if !exists { 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 return true
} }
err := c.Notifier(apiURL, apiKey, counter.Message()) err := c.Notifier(apiURL, apiKey, counter.Message())
if err != nil { if err != nil {
c.debugLog("cannot send notification for counter", 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() counter.FailureProcessed()
return true return true
@ -56,7 +55,7 @@ func (c CounterProcessor) Process(id int, counter Counter) bool { // nolint:varn
// The results may not be representative. // The results may not be representative.
if (succeeded + failed) < c.MinRequests { if (succeeded + failed) < c.MinRequests {
c.debugLog("skipping counter because it has too few requests", 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 return true
} }
@ -75,13 +74,13 @@ func (c CounterProcessor) Process(id int, counter Counter) bool { // nolint:varn
apiURL, apiKey, lang, exists := c.ConnectionDataProvider(id) apiURL, apiKey, lang, exists := c.ConnectionDataProvider(id)
if !exists { 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 return true
} }
err := c.Notifier(apiURL, apiKey, c.getErrorText(counter.Name(), c.Error, lang)) err := c.Notifier(apiURL, apiKey, c.getErrorText(counter.Name(), c.Error, lang))
if err != nil { if err != nil {
c.debugLog("cannot send notification for counter", 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() counter.CountersProcessed()
return true return true
@ -99,6 +98,6 @@ func (c CounterProcessor) getErrorText(name, msg, lang string) string {
func (c CounterProcessor) debugLog(msg string, args ...interface{}) { func (c CounterProcessor) debugLog(msg string, args ...interface{}) {
if c.Debug { if c.Debug {
c.Logger.Debug(msg, args...) c.Logger.Debug(msg, logger.AnyZapFields(args)...)
} }
} }

View File

@ -96,7 +96,7 @@ func (t *CounterProcessorTest) Test_FailureProcessed() {
p.Process(1, c) p.Process(1, c)
c.AssertExpectations(t.T()) c.AssertExpectations(t.T())
t.Assert().Contains(log.String(), "skipping counter because its failure is already processed") 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() { func (t *CounterProcessorTest) Test_CounterFailed_CannotFindConnection() {
@ -109,7 +109,7 @@ func (t *CounterProcessorTest) Test_CounterFailed_CannotFindConnection() {
p.Process(1, c) p.Process(1, c)
c.AssertExpectations(t.T()) c.AssertExpectations(t.T())
t.Assert().Contains(log.String(), "cannot find connection data for counter") 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() { func (t *CounterProcessorTest) Test_CounterFailed_ErrWhileNotifying() {
@ -124,9 +124,9 @@ func (t *CounterProcessorTest) Test_CounterFailed_ErrWhileNotifying() {
p.Process(1, c) p.Process(1, c)
c.AssertExpectations(t.T()) c.AssertExpectations(t.T())
t.Assert().Contains(log.String(), "cannot send notification for counter") t.Assert().Contains(log.String(), "cannot send notification for counter")
t.Assert().Contains(log.String(), "counterId=1") t.Assert().Contains(log.String(), `"counterId": 1`)
t.Assert().Contains(log.String(), `error="http status code: 500"`) t.Assert().Contains(log.String(), `"error": "http status code: 500"`)
t.Assert().Contains(log.String(), `failureMessage="error message"`) t.Assert().Contains(log.String(), `"failureMessage": "error message"`)
t.Assert().Equal(t.apiURL, n.apiURL) t.Assert().Equal(t.apiURL, n.apiURL)
t.Assert().Equal(t.apiKey, n.apiKey) t.Assert().Equal(t.apiKey, n.apiKey)
t.Assert().Equal("error message", n.message) t.Assert().Equal("error message", n.message)
@ -160,7 +160,7 @@ func (t *CounterProcessorTest) Test_TooFewRequests() {
p.Process(1, c) p.Process(1, c)
c.AssertExpectations(t.T()) c.AssertExpectations(t.T())
t.Assert().Contains(log.String(), 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() { func (t *CounterProcessorTest) Test_ThresholdNotPassed() {
@ -206,7 +206,7 @@ func (t *CounterProcessorTest) Test_ThresholdPassed_NoConnectionFound() {
p.Process(1, c) p.Process(1, c)
c.AssertExpectations(t.T()) c.AssertExpectations(t.T())
t.Assert().Contains(log.String(), "cannot find connection data for counter") 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) t.Assert().Empty(n.message)
} }
@ -224,7 +224,7 @@ func (t *CounterProcessorTest) Test_ThresholdPassed_NotifyingError() {
p.Process(1, c) p.Process(1, c)
c.AssertExpectations(t.T()) 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) t.Assert().Equal(`default error [{"Name":"MockedCounter"}]`, n.message)
} }

View File

@ -3,11 +3,11 @@ package core
import ( import (
"errors" "errors"
"fmt" "fmt"
"log/slog"
"sync" "sync"
"time" "time"
"github.com/retailcrm/mg-transport-core/v2/core/logger" "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. // 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) err := j.Command(log)
if err != nil && j.ErrorHandler != nil { if err != nil && j.ErrorHandler != nil {
j.ErrorHandler(name, err, log) j.ErrorHandler(name, err, log)
@ -148,14 +148,14 @@ func (j *Job) runOnceSync(name string, log logger.Logger) {
// NewJobManager is a JobManager constructor. // NewJobManager is a JobManager constructor.
func NewJobManager() *JobManager { 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. // DefaultJobErrorHandler returns default error handler for a job.
func DefaultJobErrorHandler() JobErrorHandler { func DefaultJobErrorHandler() JobErrorHandler {
return func(name string, err error, log logger.Logger) { return func(name string, err error, log logger.Logger) {
if err != nil && name != "" { 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 { func DefaultJobPanicHandler() JobPanicHandler {
return func(name string, recoverValue interface{}, log logger.Logger) { return func(name string, recoverValue interface{}, log logger.Logger) {
if recoverValue != nil && name != "" { 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))
} }
} }
} }

View File

@ -1,10 +1,8 @@
package core package core
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"log/slog"
"math/rand" "math/rand"
"strings" "strings"
"sync" "sync"
@ -14,6 +12,8 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"github.com/retailcrm/mg-transport-core/v2/core/logger" "github.com/retailcrm/mg-transport-core/v2/core/logger"
) )
@ -26,7 +26,7 @@ type JobTest struct {
executeErr chan error executeErr chan error
panicValue chan interface{} panicValue chan interface{}
lastLog string lastLog string
lastMsgLevel slog.Level lastMsgLevel zapcore.Level
syncBool bool syncBool bool
} }
@ -37,18 +37,58 @@ type JobManagerTest struct {
syncRunnerFlag bool 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 { type callbackLogger struct {
fn callbackLoggerFunc fields []zap.Field
fn callbackLoggerFunc
} }
func (n *callbackLogger) Handler() slog.Handler { func (n *callbackLogger) Check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry {
return logger.NilHandler return &zapcore.CheckedEntry{}
} }
func (n *callbackLogger) With(args ...any) logger.Logger { func (n *callbackLogger) DPanic(msg string, fields ...zap.Field) {
return n 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 { func (n *callbackLogger) WithGroup(name string) logger.Logger {
@ -59,47 +99,24 @@ func (n *callbackLogger) ForAccount(handler, conn, acc any) logger.Logger {
return n return n
} }
func (n *callbackLogger) Enabled(ctx context.Context, level slog.Level) bool { func (n *callbackLogger) Log(level zapcore.Level, msg string, args ...zap.Field) {
return true n.fn(level, msg, args...)
} }
func (n *callbackLogger) Log(ctx context.Context, level slog.Level, msg string, args ...any) { func (n *callbackLogger) Debug(msg string, args ...zap.Field) {
n.fn(ctx, level, msg, args...) 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) { func (n *callbackLogger) Warn(msg string, args ...zap.Field) {
n.Log(nil, slog.LevelDebug, msg, args...) n.Log(zap.WarnLevel, msg, args...)
} }
func (n *callbackLogger) DebugContext(ctx context.Context, msg string, args ...any) { func (n *callbackLogger) Error(msg string, args ...zap.Field) {
n.Log(ctx, slog.LevelDebug, msg, args...) n.Log(zap.ErrorLevel, 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 TestJob(t *testing.T) { func TestJob(t *testing.T) {
@ -117,9 +134,9 @@ func TestDefaultJobErrorHandler(t *testing.T) {
fn := DefaultJobErrorHandler() fn := DefaultJobErrorHandler()
require.NotNil(t, fn) 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) 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() fn := DefaultJobPanicHandler()
require.NotNil(t, fn) 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) 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 { 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 == "" { if format == "" {
var sb strings.Builder var sb strings.Builder
sb.Grow(3 * len(args)) // nolint:gomnd sb.Grow(3 * len(args)) // nolint:gomnd
@ -161,7 +178,12 @@ func (t *JobTest) testLogger() logger.Logger {
format = strings.TrimRight(sb.String(), " ") 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 t.lastMsgLevel = level
}} }}
} }
@ -406,7 +428,7 @@ func (t *JobManagerTest) WaitForJob() bool {
func (t *JobManagerTest) Test_SetLogger() { func (t *JobManagerTest) Test_SetLogger() {
t.manager.logger = nil t.manager.logger = nil
t.manager.SetLogger(logger.NewDefaultText()) t.manager.SetLogger(logger.NewDefault(true))
assert.IsType(t.T(), &logger.Default{}, t.manager.logger) assert.IsType(t.T(), &logger.Default{}, t.manager.logger)
t.manager.SetLogger(nil) t.manager.SetLogger(nil)

View File

@ -2,8 +2,9 @@ package logger
import ( import (
"fmt" "fmt"
"log/slog"
"net/http" "net/http"
"go.uber.org/zap"
) )
const ( const (
@ -19,28 +20,32 @@ const (
HTTPStatusNameAttr = "statusName" HTTPStatusNameAttr = "statusName"
) )
func Err(err any) slog.Attr { func Err(err any) zap.Field {
if err == nil { if err == nil {
return slog.String(ErrorAttr, "<nil>") return zap.String(ErrorAttr, "<nil>")
} }
return slog.Any(ErrorAttr, err) return zap.Any(ErrorAttr, err)
} }
func HTTPStatusCode(code int) slog.Attr { func Handler(name string) zap.Field {
return slog.Int(HTTPStatusAttr, code) return zap.String(HandlerAttr, name)
} }
func HTTPStatusName(code int) slog.Attr { func HTTPStatusCode(code int) zap.Field {
return slog.String(HTTPStatusNameAttr, http.StatusText(code)) 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) { switch item := val.(type) {
case string: case string:
return slog.String(BodyAttr, item) return zap.String(BodyAttr, item)
case []byte: case []byte:
return slog.String(BodyAttr, string(item)) return zap.String(BodyAttr, string(item))
default: default:
return slog.String(BodyAttr, fmt.Sprintf("%#v", val)) return zap.String(BodyAttr, fmt.Sprintf("%#v", val))
} }
} }

View File

@ -1,87 +1,59 @@
package logger package logger
import ( import (
"context" "strconv"
"log/slog"
"os" "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 { type Default struct {
Logger *slog.Logger *zap.Logger
} }
func NewDefault(log *slog.Logger) Logger { func NewDefault(debug bool) Logger {
return &Default{Logger: log} return &Default{
Logger: NewZap(debug),
}
} }
func NewDefaultText() Logger { func (l *Default) With(fields ...zap.Field) Logger {
return NewDefault(slog.New(slog.NewTextHandler(os.Stdout, DefaultOpts))) return l.With(fields...).(Logger)
} }
func NewDefaultJSON() Logger { func (l *Default) WithLazy(fields ...zap.Field) Logger {
return NewDefault(slog.New(slog.NewJSONHandler(os.Stdout, DefaultOpts))) return l.WithLazy(fields...).(Logger)
} }
func NewDefaultNil() Logger { func (l *Default) ForAccount(handler, conn, acc any) Logger {
return NewDefault(slog.New(NilHandler)) return l.WithLazy(zap.Any(HandlerAttr, handler), zap.Any(ConnectionAttr, conn), zap.Any(AccountAttr, acc))
} }
func (d *Default) Handler() slog.Handler { func AnyZapFields(args []interface{}) []zap.Field {
return d.Logger.Handler() fields := make([]zap.Field, len(args))
} for i := 0; i < len(fields); i++ {
if val, ok := args[i].(zap.Field); ok {
func (d *Default) ForAccount(handler, conn, acc any) Logger { fields[i] = val
return d.With(slog.Any(HandlerAttr, handler), slog.Any(ConnectionAttr, conn), slog.Any(AccountAttr, acc)) continue
} }
fields[i] = zap.Any("arg"+strconv.Itoa(i), args[i])
func (d *Default) With(args ...any) Logger { }
return &Default{Logger: d.Logger.With(args...)} return fields
}
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...)
} }

View File

@ -1,9 +1,10 @@
package logger package logger
import ( import (
"github.com/gin-gonic/gin"
"log/slog"
"time" "time"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
) )
// GinMiddleware will construct Gin middleware which will log requests. // GinMiddleware will construct Gin middleware which will log requests.
@ -23,14 +24,14 @@ func GinMiddleware(log Logger) gin.HandlerFunc {
} }
log.Info("request", log.Info("request",
slog.String(HandlerAttr, "GIN"), zap.String(HandlerAttr, "GIN"),
slog.String("startTime", start.Format(time.RFC3339)), zap.String("startTime", start.Format(time.RFC3339)),
slog.String("endTime", end.Format(time.RFC3339)), zap.String("endTime", end.Format(time.RFC3339)),
slog.Any("latency", end.Sub(start)/time.Millisecond), zap.Any("latency", end.Sub(start)/time.Millisecond),
slog.String("remoteAddress", c.ClientIP()), zap.String("remoteAddress", c.ClientIP()),
slog.String(HTTPMethodAttr, c.Request.Method), zap.String(HTTPMethodAttr, c.Request.Method),
slog.String("path", path), zap.String("path", path),
slog.Int("bodySize", c.Writer.Size()), zap.Int("bodySize", c.Writer.Size()),
) )
} }
} }

View File

@ -1,8 +0,0 @@
package logger
import "log/slog"
var DefaultOpts = &slog.HandlerOptions{
AddSource: false,
Level: slog.LevelDebug,
}

View File

@ -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)
}

View File

@ -2,6 +2,7 @@ package logger
import ( import (
"fmt" "fmt"
v1 "github.com/retailcrm/mg-transport-api-client-go/v1" v1 "github.com/retailcrm/mg-transport-api-client-go/v1"
) )

52
core/logger/nil.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -1,21 +1,21 @@
package logger package logger
import ( import (
"context"
"io" "io"
"log/slog"
"go.uber.org/zap/zapcore"
) )
type writerAdapter struct { type writerAdapter struct {
log Logger 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} return &writerAdapter{log: log, level: level}
} }
func (w *writerAdapter) Write(p []byte) (n int, err error) { 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 return len(p), nil
} }

52
core/logger/zap.go Normal file
View File

@ -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: " ",
}
}

View File

@ -5,7 +5,6 @@ import (
"context" "context"
"fmt" "fmt"
"html/template" "html/template"
"log/slog"
"os" "os"
"github.com/aws/aws-sdk-go-v2/aws" "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/feature/s3/manager"
"github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/gomarkdown/markdown" "github.com/gomarkdown/markdown"
"go.uber.org/zap"
"golang.org/x/text/language" "golang.org/x/text/language"
"github.com/retailcrm/mg-transport-core/v2/core/config" "github.com/retailcrm/mg-transport-core/v2/core/config"
@ -91,14 +91,14 @@ func (s *ModuleFeaturesUploader) Upload() {
content, err := os.ReadFile(s.featuresFilename) content, err := os.ReadFile(s.featuresFilename)
if err != nil { 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 return
} }
for _, lang := range languages { for _, lang := range languages {
translated, err := s.translate(content, lang) translated, err := s.translate(content, lang)
if err != nil { 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 continue
} }
@ -106,7 +106,7 @@ func (s *ModuleFeaturesUploader) Upload() {
resp, err := s.uploadFile(html, lang.String()) resp, err := s.uploadFile(html, lang.String())
if err != nil { 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 continue
} }

View File

@ -2,7 +2,6 @@ package core
import ( import (
"fmt" "fmt"
"log/slog"
"net" "net"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
@ -16,6 +15,7 @@ import (
"github.com/getsentry/sentry-go" "github.com/getsentry/sentry-go"
sentrygin "github.com/getsentry/sentry-go/gin" sentrygin "github.com/getsentry/sentry-go/gin"
"github.com/pkg/errors" "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/logger"
"github.com/retailcrm/mg-transport-core/v2/core/stacktrace" "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] headers[idx] = "header: " + headers[idx]
} }
headersToStr := slog.String("headers", strings.Join(headers, "\r\n")) headersToStr := zap.String("headers", strings.Join(headers, "\r\n"))
formattedStack := slog.String("stacktrace", string(stack)) formattedStack := zap.String("stacktrace", string(stack))
switch { switch {
case brokenPipe: case brokenPipe:
l.Error("error", formattedErr, headersToStr) l.Error("error", formattedErr, headersToStr)

View File

@ -12,9 +12,7 @@ import (
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/retailcrm/mg-transport-core/v2/core/config" "github.com/retailcrm/mg-transport-core/v2/core/config"
"github.com/retailcrm/mg-transport-core/v2/core/logger" "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{}) { func (b *HTTPClientBuilder) log(msg string, args ...interface{}) {
if b.logging { if b.logging {
if b.logger != nil { if b.logger != nil {
b.logger.Info(msg, args...) b.logger.Info(msg, logger.AnyZapFields(args)...)
} else { } else {
fmt.Println(append([]any{msg}, args...)) fmt.Println(append([]any{msg}, args...))
} }

View File

@ -142,7 +142,7 @@ func (t *HTTPClientBuilderTest) Test_WithLogger() {
builder.WithLogger(nil) builder.WithLogger(nil)
assert.Nil(t.T(), builder.logger) assert.Nil(t.T(), builder.logger)
log := logger.NewDefaultText() log := logger.NewDefault(true)
builder.WithLogger(log) builder.WithLogger(log)
assert.NotNil(t.T(), builder.logger) assert.NotNil(t.T(), builder.logger)
} }

View File

@ -3,9 +3,11 @@ package testutil
import ( import (
"fmt" "fmt"
"io" "io"
"log/slog" "os"
"github.com/retailcrm/mg-transport-core/v2/core/logger" "github.com/retailcrm/mg-transport-core/v2/core/logger"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
) )
// ReadBuffer is implemented by the BufferLogger. // ReadBuffer is implemented by the BufferLogger.
@ -32,21 +34,32 @@ type BufferLogger struct {
// NewBufferedLogger returns new BufferedLogger instance. // NewBufferedLogger returns new BufferedLogger instance.
func NewBufferedLogger() BufferedLogger { func NewBufferedLogger() BufferedLogger {
bl := &BufferLogger{} 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 return bl
} }
// With doesn't do anything here and only added for backwards compatibility with the interface. func (l *BufferLogger) With(fields ...zapcore.Field) logger.Logger {
func (l *BufferLogger) With(args ...any) logger.Logger {
return &BufferLogger{ return &BufferLogger{
Default: logger.Default{ 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 { 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. // Read bytes from the logger buffer. io.Reader implementation.

View File

@ -29,17 +29,17 @@ func (t *BufferLoggerTest) Test_Read() {
data, err := io.ReadAll(t.logger) data, err := io.ReadAll(t.logger)
t.Require().NoError(err) 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() { func (t *BufferLoggerTest) Test_Bytes() {
t.logger.Debug("test") 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() { func (t *BufferLoggerTest) Test_String() {
t.logger.Debug("test") 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() { func (t *BufferLoggerTest) TestRace() {

View File

@ -148,3 +148,8 @@ func (b *LockableBuffer) ReadString(delim byte) (line string, err error) {
b.rw.Lock() b.rw.Lock()
return b.buf.ReadString(delim) return b.buf.ReadString(delim)
} }
// Sync is a no-op.
func (b *LockableBuffer) Sync() error {
return nil
}

View File

@ -21,7 +21,6 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
retailcrm "github.com/retailcrm/api-client-go/v2" retailcrm "github.com/retailcrm/api-client-go/v2"
v1 "github.com/retailcrm/mg-transport-api-client-go/v1" 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/config"
"github.com/retailcrm/mg-transport-core/v2/core/logger" "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 res := u.checkScopes(cr.Scopes, scopes); len(res) != 0 {
if len(credentials) == 0 || len(cr.Scopes) > 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) return nil, http.StatusBadRequest, errorutil.NewInsufficientScopesErr(res)
} }
if res := u.checkScopes(cr.Credentials, credentials[0]); len(res) != 0 { 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) return nil, http.StatusBadRequest, errorutil.NewInsufficientScopesErr(res)
} }
} }

View File

@ -38,7 +38,7 @@ func mgClient() *v1.MgClient {
} }
func (u *UtilsTest) SetupSuite() { func (u *UtilsTest) SetupSuite() {
logger := logger.NewDefaultText() logger := logger.NewDefault(true)
awsConfig := config.AWS{ awsConfig := config.AWS{
AccessKeyID: "access key id (will be removed)", AccessKeyID: "access key id (will be removed)",
SecretAccessKey: "secret access key", SecretAccessKey: "secret access key",

2
go.mod
View File

@ -28,6 +28,7 @@ require (
github.com/retailcrm/zabbix-metrics-collector v1.0.0 github.com/retailcrm/zabbix-metrics-collector v1.0.0
github.com/stretchr/testify v1.8.3 github.com/stretchr/testify v1.8.3
go.uber.org/atomic v1.10.0 go.uber.org/atomic v1.10.0
go.uber.org/zap v1.26.0
golang.org/x/text v0.14.0 golang.org/x/text v0.14.0
gopkg.in/gormigrate.v1 v1.6.0 gopkg.in/gormigrate.v1 v1.6.0
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
@ -77,6 +78,7 @@ require (
github.com/stretchr/objx v0.5.0 // indirect github.com/stretchr/objx v0.5.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // 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/arch v0.3.0 // indirect
golang.org/x/crypto v0.21.0 // indirect golang.org/x/crypto v0.21.0 // indirect
golang.org/x/net v0.23.0 // indirect golang.org/x/net v0.23.0 // indirect

11
go.sum
View File

@ -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/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/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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.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/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/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= 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/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 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/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 h1:HIn4eorABNfudn7hr5Rd6XYC/ieDTqCkaq6wv0AFTBE=
github.com/retailcrm/mg-transport-api-client-go v1.3.4/go.mod h1:gDe/tj7t3Hr/uwIFSBVgGAmP85PoLajVl1A+skBo1Ro= 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= 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.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 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 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.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 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=