diff --git a/.gitignore b/.gitignore index 28195a9..cec5bc4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea coverage.* +vendor diff --git a/.golangci.yml b/.golangci.yml index 306ec41..e59706b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -163,6 +163,7 @@ issues: - path: _test\.go linters: - lll + - errorlint - bodyclose - errcheck - sqlclosecheck @@ -177,6 +178,8 @@ issues: - gocognit - gocyclo - godot + - path: \.go + text: "Error return value of `io.WriteString` is not checked" exclude-use-default: true exclude-case-sensitive: false max-issues-per-linter: 0 diff --git a/core/domains_test.go b/core/domains_test.go index fc062f6..83e817a 100644 --- a/core/domains_test.go +++ b/core/domains_test.go @@ -1,8 +1,9 @@ package core import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func Test_GetSaasDomains(t *testing.T) { diff --git a/core/engine.go b/core/engine.go index b577e4c..3334d4f 100644 --- a/core/engine.go +++ b/core/engine.go @@ -2,11 +2,13 @@ package core import ( "crypto/x509" + "fmt" "html/template" "io/fs" "net/http" "sync" + "github.com/getsentry/sentry-go" "github.com/gin-gonic/gin" "github.com/gorilla/securecookie" "github.com/gorilla/sessions" @@ -31,9 +33,35 @@ var DefaultHTTPClientConfig = &config.HTTPClientConfig{ SSLVerification: &boolTrue, } +// AppInfo contains information about app version. +type AppInfo struct { + Version string + Commit string + Build string + BuildDate string +} + +// Release information for Sentry. +func (a AppInfo) Release() string { + if a.Version == "" { + a.Version = "" + } + if a.Build == "" { + a.Build = "" + } + if a.BuildDate == "" { + a.BuildDate = "" + } + if a.Commit == "" { + a.Commit = "" + } + return fmt.Sprintf("%s (%s, built %s, commit \"%s\")", a.Version, a.Build, a.BuildDate, a.Commit) +} + // Engine struct. type Engine struct { logger logger.Logger + AppInfo AppInfo Sessions sessions.Store LogFormatter logging.Formatter Config config.Configuration @@ -52,9 +80,10 @@ type Engine struct { // New Engine instance (must be configured manually, gin can be accessed via engine.Router() directly or // engine.ConfigureRouter(...) with callback). -func New() *Engine { +func New(appInfo AppInfo) *Engine { return &Engine{ - Config: nil, + Config: nil, + AppInfo: appInfo, Localizer: Localizer{ i18nStorage: &sync.Map{}, loadMutex: &sync.RWMutex{}, @@ -76,13 +105,16 @@ func (e *Engine) initGin() { } r := gin.New() - r.Use(gin.Recovery()) + + e.buildSentryConfig() + e.InitSentrySDK() + r.Use(e.SentryMiddlewares()...) if e.Config.IsDebug() { r.Use(gin.Logger()) } - r.Use(e.LocalizationMiddleware(), e.ErrorMiddleware()) + r.Use(e.LocalizationMiddleware()) e.ginEngine = r } @@ -116,13 +148,13 @@ func (e *Engine) Prepare() *Engine { } e.CreateDB(e.Config.GetDBConfig()) - e.createRavenClient(e.Config.GetSentryDSN()) e.ResetUtils(e.Config.GetAWSConfig(), e.Config.IsDebug(), 0) e.SetLogger(logger.NewStandard(e.Config.GetTransportInfo().GetCode(), e.Config.GetLogLevel(), e.LogFormatter)) e.Sentry.Localizer = &e.Localizer - e.Sentry.Stacktrace = true e.Utils.Logger = e.Logger() e.Sentry.Logger = e.Logger() + e.buildSentryConfig() + e.Sentry.InitSentrySDK() e.prepared = true return e @@ -325,3 +357,17 @@ func (e *Engine) ConfigureRouter(callback func(*gin.Engine)) *Engine { func (e *Engine) Run() error { return e.Router().Run(e.Config.GetHTTPConfig().Listen) } + +// buildSentryConfig from app configuration. +func (e *Engine) buildSentryConfig() { + if e.AppInfo.Version == "" { + e.AppInfo.Version = e.Config.GetVersion() + } + e.SentryConfig = sentry.ClientOptions{ + Dsn: e.Config.GetSentryDSN(), + ServerName: e.Config.GetHTTPConfig().Host, + Release: e.AppInfo.Release(), + AttachStacktrace: true, + Debug: e.Config.IsDebug(), + } +} diff --git a/core/engine_test.go b/core/engine_test.go index 487e640..259ef92 100644 --- a/core/engine_test.go +++ b/core/engine_test.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto/x509" "database/sql" + "fmt" "html/template" "io/ioutil" "net/http" @@ -25,18 +26,34 @@ import ( "github.com/retailcrm/mg-transport-core/v2/core/logger" ) +// TestSentryDSN is a fake Sentry DSN in valid format. +const TestSentryDSN = "https://9f4719e96b0fc2422c05a2f745f214d5@a000000.ingest.sentry.io/0000000" + type EngineTest struct { suite.Suite engine *Engine } +type AppInfoTest struct { + suite.Suite +} + +func (e *EngineTest) appInfo() AppInfo { + return AppInfo{ + Version: "v0.0", + Commit: "commit message", + Build: "build", + BuildDate: "01.01.1970", + } +} + func (e *EngineTest) SetupTest() { var ( db *sql.DB err error ) - e.engine = New() + e.engine = New(e.appInfo()) require.NotNil(e.T(), e.engine) db, _, err = sqlmock.New() @@ -55,7 +72,7 @@ func (e *EngineTest) SetupTest() { MaxIdleConnections: 10, ConnectionLifetime: 60, }, - SentryDSN: "sentry dsn", + SentryDSN: TestSentryDSN, HTTPServer: config.HTTPServerConfig{ Host: "0.0.0.0", Listen: ":3001", @@ -78,7 +95,7 @@ func (e *EngineTest) Test_Prepare_Twice() { assert.Equal(e.T(), "engine already initialized", r.(string)) }() - engine := New() + engine := New(e.appInfo()) engine.prepared = true engine.Prepare() } @@ -90,7 +107,7 @@ func (e *EngineTest) Test_Prepare_NoConfig() { assert.Equal(e.T(), "engine.Config must be loaded before initializing", r.(string)) }() - engine := New() + engine := New(e.appInfo()) engine.prepared = false engine.Config = nil engine.Prepare() @@ -110,7 +127,7 @@ func (e *EngineTest) Test_Prepare() { assert.NotEmpty(e.T(), e.engine.LocaleMatcher) assert.False(e.T(), e.engine.isUnd(e.engine.Localizer.LanguageTag)) assert.NotNil(e.T(), e.engine.DB) - assert.NotNil(e.T(), e.engine.Client) + assert.NotEmpty(e.T(), e.engine.SentryConfig.Dsn) assert.NotNil(e.T(), e.engine.logger) assert.NotNil(e.T(), e.engine.Sentry.Localizer) assert.NotNil(e.T(), e.engine.Sentry.Logger) @@ -118,7 +135,7 @@ func (e *EngineTest) Test_Prepare() { } func (e *EngineTest) Test_initGin_Release() { - engine := New() + engine := New(e.appInfo()) engine.Config = config.Config{Debug: false} engine.initGin() assert.NotNil(e.T(), engine.ginEngine) @@ -145,7 +162,7 @@ func (e *EngineTest) Test_Router_Fail() { assert.Equal(e.T(), "prepare engine first", r.(string)) }() - engine := New() + engine := New(e.appInfo()) engine.Router() } @@ -366,13 +383,37 @@ func (e *EngineTest) Test_Run_Fail() { assert.NotNil(e.T(), recover()) }() - _ = New().Run() + _ = New(e.appInfo()).Run() +} + +func (t *AppInfoTest) Test_Release_NoData() { + a := AppInfo{} + + t.Assert().Equal( + " (, built , commit \"\")", + a.Release()) +} + +func (t *AppInfoTest) Test_Release() { + a := AppInfo{ + Version: "1647352938", + Commit: "cb03e2f - replace old Sentry client with the new SDK ", + Build: "v0.0-cb03e2f", + BuildDate: "Вт 15 мар 2022 17:03:43 MSK", + } + + t.Assert().Equal(fmt.Sprintf("%s (%s, built %s, commit \"%s\")", a.Version, a.Build, a.BuildDate, a.Commit), + a.Release()) } func TestEngine_Suite(t *testing.T) { suite.Run(t, new(EngineTest)) } +func TestAppInfo_Suite(t *testing.T) { + suite.Run(t, new(AppInfoTest)) +} + func boolPtr(val bool) *bool { b := val return &b diff --git a/core/logger/prefix_decorator.go b/core/logger/prefix_decorator.go index d9d9572..2e83912 100644 --- a/core/logger/prefix_decorator.go +++ b/core/logger/prefix_decorator.go @@ -5,6 +5,7 @@ import "github.com/op/go-logging" // PrefixAware is implemented if the logger allows you to change the prefix. type PrefixAware interface { SetPrefix(string) + Prefix() string } // PrefixedLogger is a base interface for the logger with prefix. @@ -41,6 +42,10 @@ func (p *PrefixDecorator) getFormat(fmt string) string { return p.prefix[0].(string) + " " + fmt } +func (p *PrefixDecorator) Prefix() string { + return p.prefix[0].(string) +} + func (p *PrefixDecorator) Fatal(args ...interface{}) { p.backend.Fatal(append(p.prefix, args...)...) } diff --git a/core/logger/prefix_decorator_test.go b/core/logger/prefix_decorator_test.go index 274b727..5b66ec5 100644 --- a/core/logger/prefix_decorator_test.go +++ b/core/logger/prefix_decorator_test.go @@ -44,6 +44,7 @@ func (t *PrefixDecoratorTest) SetupTest() { func (t *PrefixDecoratorTest) Test_SetPrefix() { t.logger.Info("message") + t.Assert().Equal(testPrefix, t.logger.Prefix()) t.Assert().Contains(t.buf.String(), "INFO") t.Assert().Contains(t.buf.String(), testPrefix+" message") diff --git a/core/sentry.go b/core/sentry.go index ad9c5ea..c4c0540 100644 --- a/core/sentry.go +++ b/core/sentry.go @@ -2,21 +2,29 @@ package core import ( "fmt" + "net" "net/http" + "net/http/httputil" + "os" "reflect" - "runtime/debug" "strconv" + "strings" + "sync" + "time" + "github.com/getsentry/sentry-go" + sentrygin "github.com/getsentry/sentry-go/gin" "github.com/pkg/errors" "github.com/retailcrm/mg-transport-core/v2/core/logger" - "github.com/retailcrm/mg-transport-core/v2/core/stacktrace" - "github.com/getsentry/raven-go" "github.com/gin-gonic/gin" ) +// reset is borrowed directly from the gin. +const reset = "\033[0m" + // ErrorHandlerFunc will handle errors. type ErrorHandlerFunc func(recovery interface{}, c *gin.Context) @@ -36,12 +44,15 @@ type SentryTagged interface { // Sentry struct. Holds SentryTaggedStruct list. type Sentry struct { - Logger logger.Logger - Client stacktrace.RavenClientInterface - Localizer *Localizer - DefaultError string - TaggedTypes SentryTaggedTypes - Stacktrace bool + SentryConfig sentry.ClientOptions + Logger logger.Logger + Localizer *Localizer + AppInfo AppInfo + SentryLoggerConfig SentryLoggerConfig + ServerName string + DefaultError string + TaggedTypes SentryTaggedTypes + init sync.Once } // SentryTaggedStruct holds information about type, it's key in gin.Context (for middleware), and it's properties. @@ -57,23 +68,25 @@ type SentryTaggedScalar struct { Name string } -// NewSentry constructor. -func NewSentry( - sentryDSN string, - defaultError string, - taggedTypes SentryTaggedTypes, - logger logger.Logger, - localizer *Localizer, -) *Sentry { - sentry := &Sentry{ - DefaultError: defaultError, - TaggedTypes: taggedTypes, - Localizer: localizer, - Logger: logger, - Stacktrace: true, - } - sentry.createRavenClient(sentryDSN) - return sentry +// SentryLoggerConfig configures how Sentry component will create account-scoped logger for recovery. +type SentryLoggerConfig struct { + TagForConnection string + TagForAccount string +} + +// sentryTag contains sentry tag name and corresponding value from context. +type sentryTag struct { + Name string + Value string +} + +// InitSentrySDK globally in the app. Only works once per component (you really shouldn't call this twice). +func (s *Sentry) InitSentrySDK() { + s.init.Do(func() { + if err := sentry.Init(s.SentryConfig); err != nil { + panic(err) + } + }) } // NewTaggedStruct constructor. @@ -102,23 +115,114 @@ func NewTaggedScalar(sample interface{}, ginCtxKey string, name string) *SentryT } } -// createRavenClient will init raven.Client. -func (s *Sentry) createRavenClient(sentryDSN string) { - client, _ := raven.New(sentryDSN) - s.Client = client +// CaptureException and send it to Sentry. +// Use stacktrace.ErrorWithStack to append the stacktrace to the errors without it! +func (s *Sentry) CaptureException(c *gin.Context, exception error) { + if exception == nil { + return + } + if hub := sentrygin.GetHubFromContext(c); hub != nil { + s.setScopeTags(c, hub.Scope()) + hub.CaptureException(exception) + return + } + _ = c.Error(exception) } -// combineGinErrorHandlers calls several error handlers simultaneously. -func (s *Sentry) combineGinErrorHandlers(handlers ...ErrorHandlerFunc) gin.HandlerFunc { +// SentryMiddlewares contain all the middlewares required to process errors and panics and send them to the Sentry. +// It also logs those with account identifiers. +func (s *Sentry) SentryMiddlewares() []gin.HandlerFunc { + return []gin.HandlerFunc{ + s.tagsSetterMiddleware(), + s.exceptionCaptureMiddleware(), + s.recoveryMiddleware(), + sentrygin.New(sentrygin.Options{Repanic: true}), + } +} + +// obtainErrorLogger extracts logger from the context or builds it right here from tags used in Sentry events +// Those tags can be configured with SentryLoggerConfig field. +func (s *Sentry) obtainErrorLogger(c *gin.Context) logger.AccountLogger { + if item, ok := c.Get("logger"); ok { + if accountLogger, ok := item.(logger.AccountLogger); ok { + return accountLogger + } + } + + connectionID := "{no connection ID}" + accountID := "{no account ID}" + if s.SentryLoggerConfig.TagForConnection == "" && s.SentryLoggerConfig.TagForAccount == "" { + return logger.DecorateForAccount(s.Logger, "Sentry", connectionID, accountID) + } + + for tag := range s.tagsFromContext(c) { + if s.SentryLoggerConfig.TagForConnection != "" && s.SentryLoggerConfig.TagForConnection == tag.Name { + connectionID = tag.Value + } + if s.SentryLoggerConfig.TagForAccount != "" && s.SentryLoggerConfig.TagForAccount == tag.Name { + accountID = tag.Value + } + } + + return logger.DecorateForAccount(s.Logger, "Sentry", connectionID, accountID) +} + +// tagsSetterMiddleware sets event tags into Sentry events. +func (s *Sentry) tagsSetterMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + if hub := sentry.GetHubFromContext(c.Request.Context()); hub != nil { + s.setScopeTags(c, hub.Scope()) + } + } +} + +// exceptionCaptureMiddleware captures exceptions and sends a proper JSON response for them. +func (s *Sentry) exceptionCaptureMiddleware() gin.HandlerFunc { // nolint:gocognit return func(c *gin.Context) { defer func() { - rec := recover() - for _, handler := range handlers { - handler(rec, c) + recovery := recover() + publicErrors := c.Errors.ByType(gin.ErrorTypePublic) + privateErrors := c.Errors.ByType(gin.ErrorTypePrivate) + publicLen := len(publicErrors) + privateLen := len(privateErrors) + + if privateLen == 0 && publicLen == 0 && recovery == nil { + return } - if rec != nil || len(c.Errors) > 0 { - c.Abort() + messagesLen := publicLen + if privateLen > 0 || recovery != nil { + messagesLen++ + } + + l := s.obtainErrorLogger(c) + messages := make([]string, messagesLen) + index := 0 + for _, err := range publicErrors { + messages[index] = err.Error() + s.CaptureException(c, err) + l.Error(err) + index++ + } + + for _, err := range privateErrors { + s.CaptureException(c, err) + l.Error(err) + } + + if privateLen > 0 || recovery != nil { + if s.Localizer == nil { + messages[index] = s.DefaultError + } else { + messages[index] = s.Localizer.GetLocalizedMessage(s.DefaultError) + } + } + + c.JSON(http.StatusInternalServerError, gin.H{"error": messages}) + + // will be caught by Sentry middleware + if recovery != nil { + panic(recovery) } }() @@ -126,135 +230,102 @@ func (s *Sentry) combineGinErrorHandlers(handlers ...ErrorHandlerFunc) gin.Handl } } -// ErrorMiddleware returns error handlers, attachable to gin.Engine. -func (s *Sentry) ErrorMiddleware() gin.HandlerFunc { - defaultHandlers := []ErrorHandlerFunc{ - s.ErrorResponseHandler(), - s.PanicLogger(), - s.ErrorLogger(), - } +// recoveryMiddleware is mostly borrowed from the gin itself. It only contains several modifications to add logger +// prefixes to all newlines in the log. The amount of changes is infinitesimal in comparison to the original code. +func (s *Sentry) recoveryMiddleware() gin.HandlerFunc { // nolint + return func(c *gin.Context) { + defer func() { + if err := recover(); err != nil { // nolint:nestif + l := s.obtainErrorLogger(c) - if s.Client != nil { - defaultHandlers = append(defaultHandlers, s.ErrorCaptureHandler()) - } + // Check for a broken connection, as it is not really a + // condition that warrants a panic stack trace. + var brokenPipe bool + if ne, ok := err.(*net.OpError); ok { + if se, ok := ne.Err.(*os.SyscallError); ok { // nolint:errorlint + if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || + strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { + brokenPipe = true + } + } + } + if l != nil { + stack := stacktrace.FormattedStack(3, l.Prefix()+" ") + formattedErr := fmt.Sprintf("%s %s", l.Prefix(), err) + httpRequest, _ := httputil.DumpRequest(c.Request, false) + headers := strings.Split(string(httpRequest), "\r\n") + for idx, header := range headers { + current := strings.Split(header, ":") + if current[0] == "Authorization" { + headers[idx] = current[0] + ": *" + } + headers[idx] = l.Prefix() + " " + headers[idx] + } + headersToStr := strings.Join(headers, "\r\n") + switch { + case brokenPipe: + l.Errorf("%s\n%s%s", formattedErr, headersToStr, reset) + case gin.IsDebugging(): + l.Errorf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", + timeFormat(time.Now()), headersToStr, formattedErr, stack, reset) + default: + l.Errorf("[Recovery] %s panic recovered:\n%s\n%s%s", + timeFormat(time.Now()), formattedErr, stack, reset) + } + } + if brokenPipe { + // If the connection is dead, we can't write a status to it. + c.Error(err.(error)) // nolint: errcheck + c.Abort() + } else { + if s.Localizer == nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": []string{s.DefaultError}}) + return + } - return s.combineGinErrorHandlers(defaultHandlers...) -} - -// PanicLogger logs panic. -func (s *Sentry) PanicLogger() ErrorHandlerFunc { - return func(recovery interface{}, c *gin.Context) { - if recovery != nil { - if s.Logger != nil { - s.Logger.Error(c.Request.RequestURI, recovery) - } else { - fmt.Print("ERROR =>", c.Request.RequestURI, recovery) + c.JSON(http.StatusInternalServerError, gin.H{ + "error": []string{s.Localizer.GetLocalizedMessage(s.DefaultError)}, + }) + } } - debug.PrintStack() - } + }() + c.Next() } } -// ErrorLogger logs basic errors. -func (s *Sentry) ErrorLogger() ErrorHandlerFunc { - return func(recovery interface{}, c *gin.Context) { - for _, err := range c.Errors { - if s.Logger != nil { - s.Logger.Error(c.Request.RequestURI, err.Err) - } else { - fmt.Print("ERROR =>", c.Request.RequestURI, err.Err) - } - } +// setScopeTags sets Sentry tags into scope using component configuration. +func (s *Sentry) setScopeTags(c *gin.Context, scope *sentry.Scope) { + scope.SetTag("endpoint", c.Request.RequestURI) + + for tag := range s.tagsFromContext(c) { + scope.SetTag(tag.Name, tag.Value) } } -// ErrorResponseHandler will be executed in case of any unexpected error. -func (s *Sentry) ErrorResponseHandler() ErrorHandlerFunc { - return func(recovery interface{}, c *gin.Context) { - publicErrors := c.Errors.ByType(gin.ErrorTypePublic) - privateLen := len(c.Errors.ByType(gin.ErrorTypePrivate)) - publicLen := len(publicErrors) - - if privateLen == 0 && publicLen == 0 && recovery == nil { - return - } - - messagesLen := publicLen - if privateLen > 0 || recovery != nil { - messagesLen++ - } - - messages := make([]string, messagesLen) - index := 0 - for _, err := range publicErrors { - messages[index] = err.Error() - index++ - } - - if privateLen > 0 || recovery != nil { - if s.Localizer == nil { - messages[index] = s.DefaultError - } else { - messages[index] = s.Localizer.GetLocalizedMessage(s.DefaultError) - } - } - - c.JSON(http.StatusInternalServerError, gin.H{"error": messages}) - } -} - -// ErrorCaptureHandler will generate error data and send it to sentry. -func (s *Sentry) ErrorCaptureHandler() ErrorHandlerFunc { // nolint:gocognit - return func(recovery interface{}, c *gin.Context) { - tags := map[string]string{ - "endpoint": c.Request.RequestURI, - } +// tagsFromContext extracts tags from context using component configuration. +func (s *Sentry) tagsFromContext(c *gin.Context) chan sentryTag { + ch := make(chan sentryTag) + go func(ch chan sentryTag) { if len(s.TaggedTypes) > 0 { for _, tagged := range s.TaggedTypes { if item, ok := c.Get(tagged.GetContextKey()); ok && item != nil { if itemTags, err := tagged.BuildTags(item); err == nil { for tagName, tagValue := range itemTags { - tags[tagName] = tagValue + ch <- sentryTag{ + Name: tagName, + Value: tagValue, + } } } } } } - if recovery != nil { - stack := raven.NewStacktrace(4, 3, nil) - recStr := fmt.Sprint(recovery) - err := errors.New(recStr) - go s.Client.CaptureMessageAndWait( - recStr, - tags, - raven.NewException(err, stack), - raven.NewHttp(c.Request), - ) - } + close(ch) + }(ch) - for _, err := range c.Errors { - if s.Stacktrace { - stackBuilder := stacktrace.GetStackBuilderByErrorType(err.Err) - stackBuilder.SetClient(s.Client) - stack, buildErr := stackBuilder.Build().GetResult() - if buildErr != nil { - go s.Client.CaptureErrorAndWait(buildErr, tags) - stack = stacktrace.GenericStack(s.Client) - } - - go s.Client.CaptureMessageAndWait( - err.Error(), - tags, - raven.NewException(err.Err, stack), - raven.NewHttp(c.Request), - ) - } else { - go s.Client.CaptureErrorAndWait(err.Err, tags) - } - } - } + return ch } // AddTag will add tag with property name which holds tag in object. @@ -396,3 +467,8 @@ func (t *SentryTaggedScalar) BuildTags(v interface{}) (items map[string]string, } return } + +// timeFormat is a time format helper, borrowed from gin without any changes. +func timeFormat(t time.Time) string { + return t.Format("2006/01/02 - 15:04:05") +} diff --git a/core/sentry_test.go b/core/sentry_test.go index 40f3d34..9a8d319 100644 --- a/core/sentry_test.go +++ b/core/sentry_test.go @@ -1,23 +1,24 @@ package core import ( - "encoding/json" "errors" - "math/rand" + "fmt" "net/http" "net/http/httptest" - "strconv" "sync" "testing" "time" - "github.com/getsentry/raven-go" + "github.com/getsentry/sentry-go" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - "github.com/retailcrm/mg-transport-core/v2/core/util/errorutil" + "github.com/retailcrm/mg-transport-core/v2/core/db/models" + "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/util/testutil" ) type sampleStruct struct { @@ -26,117 +27,33 @@ type sampleStruct struct { ID int } -type ravenPacket struct { - EventID string - Message string - Tags map[string]string - Interfaces []raven.Interface +type sentryMockTransport struct { + lastEvent *sentry.Event + sending sync.RWMutex } -func (r ravenPacket) getInterface(class string) (raven.Interface, bool) { - for _, v := range r.Interfaces { - if v.Class() == class { - return v, true - } - } - - return nil, false +func (s *sentryMockTransport) Flush(timeout time.Duration) bool { + // noop + return true } -func (r ravenPacket) getException() (*raven.Exception, bool) { - if i, ok := r.getInterface("exception"); ok { - if r, ok := i.(*raven.Exception); ok { - return r, true - } - } - - return nil, false +func (s *sentryMockTransport) Configure(options sentry.ClientOptions) { + // noop } -type ravenClientMock struct { - captured []ravenPacket - raven.Client - mu sync.RWMutex - wg sync.WaitGroup +func (s *sentryMockTransport) SendEvent(event *sentry.Event) { + defer s.sending.Unlock() + s.sending.Lock() + s.lastEvent = event } -func newRavenMock() *ravenClientMock { - rand.Seed(time.Now().UnixNano()) - return &ravenClientMock{captured: []ravenPacket{}} -} - -func (r *ravenClientMock) Reset() { - r.mu.Lock() - defer r.mu.Unlock() - r.captured = []ravenPacket{} -} - -func (r *ravenClientMock) last() (ravenPacket, error) { - r.mu.RLock() - defer r.mu.RUnlock() - - if len(r.captured) > 0 { - return r.captured[len(r.captured)-1], nil - } - - return ravenPacket{}, errors.New("empty packet list") -} - -func (r *ravenClientMock) CaptureMessageAndWait(message string, tags map[string]string, interfaces ...raven.Interface) string { - r.mu.Lock() - defer r.mu.Unlock() - defer r.wg.Done() - eventID := strconv.FormatUint(rand.Uint64(), 10) // nolint:gosec - r.captured = append(r.captured, ravenPacket{ - EventID: eventID, - Message: message, - Tags: tags, - Interfaces: interfaces, - }) - return eventID -} - -func (r *ravenClientMock) CaptureErrorAndWait(err error, tags map[string]string, interfaces ...raven.Interface) string { - return r.CaptureMessageAndWait(err.Error(), tags, interfaces...) -} - -func (r *ravenClientMock) IncludePaths() []string { - return []string{} -} - -// simpleError is a simplest error implementation possible. The only reason why it's here is tests. -type simpleError struct { - msg string -} - -func newSimpleError(msg string) error { - return &simpleError{msg: msg} -} - -func (n *simpleError) Error() string { - return n.msg -} - -// wrappableError is a simple implementation of wrappable error. -type wrappableError struct { - err error - msg string -} - -func newWrappableError(msg string, child error) error { - return &wrappableError{msg: msg, err: child} -} - -func (e *wrappableError) Error() string { - return e.msg -} - -func (e *wrappableError) Unwrap() error { - return e.err +func newSentryMockTransport() *sentryMockTransport { + return &sentryMockTransport{} } type SentryTest struct { suite.Suite + logger testutil.BufferedLogger sentry *Sentry gin *gin.Engine structTags *SentryTaggedStruct @@ -151,10 +68,61 @@ func (s *SentryTest) SetupSuite() { require.Equal(s.T(), "", s.structTags.GetName()) require.Equal(s.T(), "Scalar", s.scalarTags.GetName()) s.structTags.Tags = map[string]string{} - s.sentry = NewSentry("dsn", "unknown_error", SentryTaggedTypes{}, nil, nil) - s.sentry.Client = newRavenMock() + s.logger = testutil.NewBufferedLogger() + appInfo := AppInfo{ + Version: "test_version", + Commit: "test_commit", + Build: "test_build", + BuildDate: "test_build_date", + } + s.sentry = &Sentry{ + init: sync.Once{}, + SentryConfig: sentry.ClientOptions{ + Dsn: TestSentryDSN, + Debug: true, + AttachStacktrace: true, + Release: appInfo.Release(), + }, + ServerName: "test", + AppInfo: appInfo, + Logger: s.logger, + SentryLoggerConfig: SentryLoggerConfig{ + TagForConnection: "url", + TagForAccount: "name", + }, + Localizer: nil, + DefaultError: "error_save", + TaggedTypes: SentryTaggedTypes{ + NewTaggedStruct(models.Connection{}, "connection", map[string]string{ + "url": "URL", + }), + NewTaggedStruct(models.Account{}, "account", map[string]string{ + "name": "Name", + }), + }, + } + s.sentry.InitSentrySDK() s.gin = gin.New() - s.gin.Use(s.sentry.ErrorMiddleware()) + s.gin.Use(s.sentry.SentryMiddlewares()...) +} + +func (s *SentryTest) hubMock() (hub *sentry.Hub, transport *sentryMockTransport) { + client, err := sentry.NewClient(s.sentry.SentryConfig) + if err != nil { + panic(err) + } + transport = newSentryMockTransport() + client.Transport = transport + hub = sentry.NewHub(client, sentry.NewScope()) + return +} + +func (s *SentryTest) ginCtxMock() (ctx *gin.Context, transport *sentryMockTransport) { + req, _ := http.NewRequest(http.MethodGet, "/", nil) + ctx = &gin.Context{Request: req} + hub, transport := s.hubMock() + ctx.Set("sentry", hub) + return } func (s *SentryTest) TestStruct_AddTag() { @@ -272,146 +240,112 @@ func (s *SentryTest) TestScalar_BuildTags() { } func (s *SentryTest) TestSentry_ErrorMiddleware() { - assert.NotNil(s.T(), s.sentry.ErrorMiddleware()) + assert.NotNil(s.T(), s.sentry.SentryMiddlewares()) + assert.NotEmpty(s.T(), s.sentry.SentryMiddlewares()) } -func (s *SentryTest) TestSentry_PanicLogger() { - assert.NotNil(s.T(), s.sentry.PanicLogger()) +func (s *SentryTest) TestSentry_CaptureException_Nil() { + defer func() { + s.Assert().Nil(recover()) + }() + s.sentry.CaptureException(&gin.Context{}, nil) } -func (s *SentryTest) TestSentry_ErrorLogger() { - assert.NotNil(s.T(), s.sentry.ErrorLogger()) +func (s *SentryTest) TestSentry_CaptureException_Error() { + ctx, transport := s.ginCtxMock() + ctx.Keys = make(map[string]interface{}) + s.sentry.CaptureException(ctx, errors.New("test error")) + + s.Require().Nil(transport.lastEvent) + s.Require().Len(ctx.Errors, 1) } -func (s *SentryTest) TestSentry_ErrorResponseHandler() { - assert.NotNil(s.T(), s.sentry.ErrorResponseHandler()) +func (s *SentryTest) TestSentry_CaptureException() { + ctx, transport := s.ginCtxMock() + s.sentry.CaptureException(ctx, stacktrace.AppendToError(errors.New("test error"))) + + s.Require().NotNil(transport.lastEvent) + s.Require().Equal( + "test_version (test_build, built test_build_date, commit \"test_commit\")", transport.lastEvent.Release) + s.Require().Len(transport.lastEvent.Exception, 2) + s.Assert().Equal(transport.lastEvent.Exception[0].Type, "*errors.errorString") + s.Assert().Equal(transport.lastEvent.Exception[0].Value, "test error") + s.Assert().Nil(transport.lastEvent.Exception[0].Stacktrace) + s.Assert().Equal(transport.lastEvent.Exception[1].Type, "*stacktrace.withStack") + s.Assert().Equal(transport.lastEvent.Exception[1].Value, "test error") + s.Assert().NotNil(transport.lastEvent.Exception[1].Stacktrace) } -func (s *SentryTest) TestSentry_ErrorCaptureHandler() { - assert.NotNil(s.T(), s.sentry.ErrorCaptureHandler()) +func (s *SentryTest) TestSentry_obtainErrorLogger_Existing() { + ctx, _ := s.ginCtxMock() + log := logger.DecorateForAccount(testutil.NewBufferedLogger(), "component", "conn", "acc") + ctx.Set("logger", log) + + s.Assert().Equal(log, s.sentry.obtainErrorLogger(ctx)) } -func (s *SentryTest) TestSentry_CaptureRegularError() { - s.gin.GET("/test_regularError", func(c *gin.Context) { - c.Error(newSimpleError("test")) - }) +func (s *SentryTest) TestSentry_obtainErrorLogger_Constructed() { + ctx, _ := s.ginCtxMock() + ctx.Set("connection", &models.Connection{URL: "conn_url"}) + ctx.Set("account", &models.Account{Name: "acc_name"}) - var resp errorutil.ListResponse - req, err := http.NewRequest(http.MethodGet, "/test_regularError", nil) - require.NoError(s.T(), err) - - ravenMock := s.sentry.Client.(*ravenClientMock) - ravenMock.wg.Add(1) - rec := httptest.NewRecorder() - s.gin.ServeHTTP(rec, req) - require.NoError(s.T(), json.Unmarshal(rec.Body.Bytes(), &resp)) - assert.NotEmpty(s.T(), resp.Error) - assert.Equal(s.T(), s.sentry.DefaultError, resp.Error[0]) - - ravenMock.wg.Wait() - last, err := ravenMock.last() - require.NoError(s.T(), err) - assert.Equal(s.T(), "test", last.Message) - - exception, ok := last.getException() - require.True(s.T(), ok, "cannot find exception") - require.NotNil(s.T(), exception.Stacktrace) - assert.NotEmpty(s.T(), exception.Stacktrace.Frames) -} - -// TestSentry_CaptureWrappedError is used to check if Sentry component calls stacktrace builders properly -// Actual stacktrace builder tests can be found in the corresponding package. -func (s *SentryTest) TestSentry_CaptureWrappedError() { - third := newWrappableError("third", nil) - second := newWrappableError("second", third) - first := newWrappableError("first", second) - - s.gin.GET("/test_wrappableError", func(c *gin.Context) { - c.Error(first) - }) - - var resp errorutil.ListResponse - req, err := http.NewRequest(http.MethodGet, "/test_wrappableError", nil) - require.NoError(s.T(), err) - - ravenMock := s.sentry.Client.(*ravenClientMock) - ravenMock.wg.Add(1) - rec := httptest.NewRecorder() - s.gin.ServeHTTP(rec, req) - require.NoError(s.T(), json.Unmarshal(rec.Body.Bytes(), &resp)) - assert.NotEmpty(s.T(), resp.Error) - assert.Equal(s.T(), s.sentry.DefaultError, resp.Error[0]) - - ravenMock.wg.Wait() - last, err := ravenMock.last() - require.NoError(s.T(), err) - assert.Equal(s.T(), "first", last.Message) - - exception, ok := last.getException() - require.True(s.T(), ok, "cannot find exception") - require.NotNil(s.T(), exception.Stacktrace) - assert.NotEmpty(s.T(), exception.Stacktrace.Frames) - assert.Len(s.T(), exception.Stacktrace.Frames, 3) - - // Error messages will be put into function names by parser - assert.Contains(s.T(), exception.Stacktrace.Frames[0].Function, third.Error()) - assert.Contains(s.T(), exception.Stacktrace.Frames[1].Function, second.Error()) - assert.Contains(s.T(), exception.Stacktrace.Frames[2].Function, first.Error()) -} - -func (s *SentryTest) TestSentry_CaptureTags() { - s.gin.GET("/test_taggedError", func(c *gin.Context) { - var intPointer = 147 - c.Set("text_tag", "text contents") - c.Set("sample_struct", sampleStruct{ - ID: 12, - Pointer: &intPointer, - Field: "field content", - }) - }, func(c *gin.Context) { - c.Error(newSimpleError("test")) - }) - - s.sentry.TaggedTypes = SentryTaggedTypes{ - NewTaggedScalar("", "text_tag", "TextTag"), - NewTaggedStruct(sampleStruct{}, "sample_struct", map[string]string{ - "id": "ID", - "pointer": "Pointer", - "field item": "Field", - }), + s.sentry.SentryLoggerConfig = SentryLoggerConfig{} + logNoConfig := s.sentry.obtainErrorLogger(ctx) + s.sentry.SentryLoggerConfig = SentryLoggerConfig{ + TagForConnection: "url", + TagForAccount: "name", } + log := s.sentry.obtainErrorLogger(ctx) - var resp errorutil.ListResponse - req, err := http.NewRequest(http.MethodGet, "/test_taggedError", nil) - require.NoError(s.T(), err) + s.Assert().NotNil(log) + s.Assert().NotNil(logNoConfig) + s.Assert().Implements((*logger.AccountLogger)(nil), log) + s.Assert().Implements((*logger.AccountLogger)(nil), logNoConfig) + s.Assert().Equal( + fmt.Sprintf(logger.DefaultAccountLoggerFormat, "Sentry", "{no connection ID}", "{no account ID}"), + logNoConfig.Prefix()) + s.Assert().Equal(fmt.Sprintf(logger.DefaultAccountLoggerFormat, "Sentry", "conn_url", "acc_name"), log.Prefix()) +} - ravenMock := s.sentry.Client.(*ravenClientMock) - ravenMock.wg.Add(1) - rec := httptest.NewRecorder() - s.gin.ServeHTTP(rec, req) - require.NoError(s.T(), json.Unmarshal(rec.Body.Bytes(), &resp)) - assert.NotEmpty(s.T(), resp.Error) - assert.Equal(s.T(), s.sentry.DefaultError, resp.Error[0]) +func (s *SentryTest) TestSentry_MiddlewaresError() { + var transport *sentryMockTransport + g := gin.New() + g.Use(s.sentry.SentryMiddlewares()...) + g.Use(func(c *gin.Context) { + hub, t := s.hubMock() + transport = t + c.Set("sentry", hub) + c.Set("connection", &models.Connection{URL: "conn_url"}) + c.Set("account", &models.Account{Name: "acc_name"}) + }) - ravenMock.wg.Wait() - last, err := ravenMock.last() - require.NoError(s.T(), err) - assert.Equal(s.T(), "test", last.Message) + g.GET("/", func(c *gin.Context) { + c.Error(stacktrace.AppendToError(errors.New("test error"))) + }) - exception, ok := last.getException() - require.True(s.T(), ok, "cannot find exception") - require.NotNil(s.T(), exception.Stacktrace) - assert.NotEmpty(s.T(), exception.Stacktrace.Frames) + req, _ := http.NewRequest(http.MethodGet, "/", nil) + g.ServeHTTP(httptest.NewRecorder(), req) - // endpoint tag is present by default - require.NotEmpty(s.T(), last.Tags) - assert.True(s.T(), len(last.Tags) == 5) - assert.Equal(s.T(), "text contents", last.Tags["TextTag"]) - assert.Equal(s.T(), "12", last.Tags["id"]) - assert.Equal(s.T(), "147", last.Tags["pointer"]) - assert.Equal(s.T(), "field content", last.Tags["field item"]) + s.Require().NotNil(transport) + s.Require().NotNil(transport.lastEvent) + s.Require().Equal( + "test_version (test_build, built test_build_date, commit \"test_commit\")", transport.lastEvent.Release) + s.Require().Len(transport.lastEvent.Exception, 3) + s.Assert().Equal(transport.lastEvent.Exception[0].Type, "*errors.errorString") + s.Assert().Equal(transport.lastEvent.Exception[0].Value, "test error") + s.Assert().Nil(transport.lastEvent.Exception[0].Stacktrace) + s.Assert().Equal(transport.lastEvent.Exception[1].Type, "*stacktrace.withStack") + s.Assert().Equal(transport.lastEvent.Exception[1].Value, "test error") + s.Assert().NotNil(transport.lastEvent.Exception[1].Stacktrace) + s.Assert().Equal(transport.lastEvent.Exception[2].Type, "*gin.Error") + s.Assert().Equal(transport.lastEvent.Exception[2].Value, "test error") + s.Assert().NotNil(transport.lastEvent.Exception[2].Stacktrace) } func TestSentry_Suite(t *testing.T) { suite.Run(t, new(SentryTest)) } + +func Test_timeFormat(t *testing.T) { + assert.Regexp(t, `^\d{4}\/\d{2}\/\d{2} \- \d{2}\:\d{2}\:\d{2}$`, timeFormat(time.Unix(1647515788, 0))) +} diff --git a/core/stacktrace/abstract_stack_builder.go b/core/stacktrace/abstract_stack_builder.go deleted file mode 100644 index 66d32d1..0000000 --- a/core/stacktrace/abstract_stack_builder.go +++ /dev/null @@ -1,52 +0,0 @@ -package stacktrace - -import ( - "github.com/getsentry/raven-go" - "github.com/pkg/errors" -) - -// ErrUnfeasibleBuilder will be returned if builder for stacktrace was chosen incorrectly. -var ErrUnfeasibleBuilder = errors.New("unfeasible builder for this error type") - -// StackBuilderInterface is an interface for every stacktrace builder. -type StackBuilderInterface interface { - SetClient(RavenClientInterface) StackBuilderInterface - SetError(error) StackBuilderInterface - Build() StackBuilderInterface - GetResult() (*raven.Stacktrace, error) -} - -// AbstractStackBuilder contains methods, which would be implemented in every builder anyway. -type AbstractStackBuilder struct { - err error - buildErr error - client RavenClientInterface - stack *raven.Stacktrace -} - -// SetClient sets *raven.Client into builder. RavenClientInterface is used, so, any client might be used via facade. -func (a *AbstractStackBuilder) SetClient(client RavenClientInterface) StackBuilderInterface { - a.client = client - return a -} - -// SetError sets error in builder, which will be processed. -func (a *AbstractStackBuilder) SetError(err error) StackBuilderInterface { - a.err = err - return a -} - -// Build stacktrace. Only implemented in the children. -func (a *AbstractStackBuilder) Build() StackBuilderInterface { - panic("not implemented") -} - -// GetResult returns builder result. -func (a *AbstractStackBuilder) GetResult() (*raven.Stacktrace, error) { - return a.stack, a.buildErr -} - -// FallbackToGeneric fallbacks to GenericStackBuilder method. -func (a *AbstractStackBuilder) FallbackToGeneric() { - a.stack, a.err = GenericStack(a.client), nil -} diff --git a/core/stacktrace/abstract_stack_builder_test.go b/core/stacktrace/abstract_stack_builder_test.go deleted file mode 100644 index 69b775a..0000000 --- a/core/stacktrace/abstract_stack_builder_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package stacktrace - -import ( - "errors" - "testing" - - "github.com/getsentry/raven-go" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" -) - -type AbstractStackBuilderSuite struct { - builder *AbstractStackBuilder - suite.Suite -} - -func TestAbstractStackBuilder(t *testing.T) { - suite.Run(t, new(AbstractStackBuilderSuite)) -} - -func (s *AbstractStackBuilderSuite) SetupSuite() { - s.builder = &AbstractStackBuilder{} -} - -func (s *AbstractStackBuilderSuite) Test_SetClient() { - require.Nil(s.T(), s.builder.client) - client, _ := raven.New("fake dsn") - s.builder.SetClient(client) - assert.NotNil(s.T(), s.builder.client) -} - -func (s *AbstractStackBuilderSuite) Test_SetError() { - require.Nil(s.T(), s.builder.err) - s.builder.SetError(errors.New("test err")) - assert.NotNil(s.T(), s.builder.err) -} - -func (s *AbstractStackBuilderSuite) Test_Build() { - defer func() { - r := recover() - require.NotNil(s.T(), r) - require.IsType(s.T(), "", r) - assert.Equal(s.T(), "not implemented", r.(string)) - }() - - s.builder.Build() -} - -func (s *AbstractStackBuilderSuite) Test_GetResult() { - buildErr := errors.New("build err") - stack := raven.NewStacktrace(0, 3, []string{}) - s.builder.buildErr = buildErr - s.builder.stack = stack - resultStack, resultErr := s.builder.GetResult() - - assert.Error(s.T(), resultErr) - assert.Equal(s.T(), buildErr, resultErr) - assert.Equal(s.T(), *stack, *resultStack) -} diff --git a/core/stacktrace/err_collector_builder.go b/core/stacktrace/err_collector_builder.go deleted file mode 100644 index 5861061..0000000 --- a/core/stacktrace/err_collector_builder.go +++ /dev/null @@ -1,57 +0,0 @@ -package stacktrace - -import ( - "path/filepath" - - "github.com/getsentry/raven-go" - - "github.com/retailcrm/mg-transport-core/v2/core/util/errorutil" -) - -// ErrorNodesList is the interface for the errorutil.errList. -type ErrorNodesList interface { - Iterate() <-chan errorutil.Node - Len() int -} - -// ErrCollectorBuilder builds stacktrace from the list of errors collected by errorutil.Collector. -type ErrCollectorBuilder struct { - AbstractStackBuilder -} - -// IsErrorNodesList returns true if error contains error nodes. -func IsErrorNodesList(err error) bool { - _, ok := err.(ErrorNodesList) // nolint:errorlint - return ok -} - -// AsErrorNodesList returns ErrorNodesList instance from the error. -func AsErrorNodesList(err error) ErrorNodesList { - return err.(ErrorNodesList) // nolint:errorlint -} - -// Build stacktrace. -func (b *ErrCollectorBuilder) Build() StackBuilderInterface { - if !IsErrorNodesList(b.err) { - b.buildErr = ErrUnfeasibleBuilder - return b - } - - i := 0 - errs := AsErrorNodesList(b.err) - frames := make([]*raven.StacktraceFrame, errs.Len()) - - for err := range errs.Iterate() { - frames[i] = raven.NewStacktraceFrame( - err.PC, filepath.Base(err.File), err.File, err.Line, 3, b.client.IncludePaths()) - i++ - } - - if len(frames) <= 1 { - b.buildErr = ErrUnfeasibleBuilder - return b - } - - b.stack = &raven.Stacktrace{Frames: frames} - return b -} diff --git a/core/stacktrace/err_collector_builder_test.go b/core/stacktrace/err_collector_builder_test.go deleted file mode 100644 index a7f6b6b..0000000 --- a/core/stacktrace/err_collector_builder_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package stacktrace - -import ( - "errors" - "path/filepath" - "runtime" - "strings" - "testing" - - "github.com/getsentry/raven-go" - "github.com/stretchr/testify/suite" - - "github.com/retailcrm/mg-transport-core/v2/core/util/errorutil" -) - -type ErrCollectorBuilderTest struct { - builder *ErrCollectorBuilder - c *errorutil.Collector - suite.Suite -} - -func TestErrCollectorBuilder(t *testing.T) { - suite.Run(t, new(ErrCollectorBuilderTest)) -} - -func (t *ErrCollectorBuilderTest) SetupTest() { - t.c = errorutil.NewCollector() - client, _ := raven.New("fake dsn") - t.builder = &ErrCollectorBuilder{AbstractStackBuilder{ - client: client, - err: t.c, - }} -} - -func (t *ErrCollectorBuilderTest) TestBuild() { - t.c.Do( - errors.New("first"), - errors.New("second"), - errors.New("third")) - - stack, err := t.builder.Build().GetResult() - _, file, _, _ := runtime.Caller(0) - - t.Require().NoError(err) - t.Require().NotZero(stack) - t.Assert().Len(stack.Frames, 3) - - for _, frame := range stack.Frames { - t.Assert().Equal(file, frame.Filename) - t.Assert().Equal(file, frame.AbsolutePath) - t.Assert().Equal("go", frame.Function) - t.Assert().Equal(strings.TrimSuffix(filepath.Base(file), ".go"), frame.Module) - t.Assert().NotZero(frame.Lineno) - } -} diff --git a/core/stacktrace/error.go b/core/stacktrace/error.go new file mode 100644 index 0000000..9936e28 --- /dev/null +++ b/core/stacktrace/error.go @@ -0,0 +1,57 @@ +// Package stacktrace contains code borrowed from the github.com/pkg/errors +package stacktrace + +import ( + "fmt" + "io" +) + +// withStack is an error with stacktrace. +type withStack struct { + error + *stack +} + +// AppendToError populates err with a stack trace at the point WithStack was called. +// If err is nil, WithStack returns nil. +func AppendToError(err error, skip ...int) error { + if err == nil { + return nil + } + if _, hasTrace := err.(interface { // nolint:errorlint + StackTrace() StackTrace + }); hasTrace { + return err + } + framesToSkip := 3 + if len(skip) > 0 { + framesToSkip = skip[0] + } + return &withStack{ + err, + callers(framesToSkip), + } +} + +// Cause of error. +func (w *withStack) Cause() error { return w.error } + +// Unwrap provides compatibility for Go 1.13 error chains. +func (w *withStack) Unwrap() error { return w.error } + +// Format the error. +func (w *withStack) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + _, _ = fmt.Fprintf(s, "%+v", w.Cause()) + w.stack.Format(s, verb) + return + } + fallthrough + case 's': + _, _ = io.WriteString(s, w.Error()) + case 'q': + _, _ = fmt.Fprintf(s, "%q", w.Error()) + } +} diff --git a/core/stacktrace/error_test.go b/core/stacktrace/error_test.go new file mode 100644 index 0000000..fdc205e --- /dev/null +++ b/core/stacktrace/error_test.go @@ -0,0 +1,51 @@ +package stacktrace + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/suite" +) + +type ErrorTest struct { + suite.Suite +} + +func TestError(t *testing.T) { + suite.Run(t, new(ErrorTest)) +} + +func (t *ErrorTest) TestAppendToError() { + err := errors.New("test error") + _, ok := err.(StackTraced) + + t.Assert().False(ok) + + withTrace := AppendToError(err) + twiceTrace := AppendToError(withTrace) + + t.Assert().Nil(AppendToError(nil)) + t.Assert().Implements((*StackTraced)(nil), withTrace) + t.Assert().Implements((*StackTraced)(nil), twiceTrace) + t.Assert().Equal(withTrace.(StackTraced).StackTrace(), twiceTrace.(StackTraced).StackTrace()) +} + +func (t *ErrorTest) TestCauseUnwrap() { + err := errors.New("test error") + wrapped := AppendToError(err) + + t.Assert().Equal(err, wrapped.(*withStack).Cause()) + t.Assert().Equal(err, errors.Unwrap(wrapped)) + t.Assert().Equal(wrapped.(*withStack).Cause(), errors.Unwrap(wrapped)) +} + +func (t *ErrorTest) TestFormat() { + wrapped := AppendToError(errors.New("test error")) + + t.Assert().Equal("\""+wrapped.Error()+"\"", fmt.Sprintf("%q", wrapped)) + t.Assert().Equal(wrapped.Error(), fmt.Sprintf("%s", wrapped)) + t.Assert().Equal(wrapped.Error(), fmt.Sprintf("%v", wrapped)) + t.Assert().NotEqual(wrapped.Error(), fmt.Sprintf("%+v", wrapped)) + t.Assert().Contains(fmt.Sprintf("%+v", wrapped), "TestFormat") +} diff --git a/core/stacktrace/generic_stack_builder.go b/core/stacktrace/generic_stack_builder.go deleted file mode 100644 index 3d9e251..0000000 --- a/core/stacktrace/generic_stack_builder.go +++ /dev/null @@ -1,21 +0,0 @@ -package stacktrace - -import ( - "github.com/getsentry/raven-go" -) - -// GenericStackBuilder uses raven.NewStacktrace to build stacktrace. Only client is needed here. -type GenericStackBuilder struct { - AbstractStackBuilder -} - -// Build returns generic stacktrace. -func (b *GenericStackBuilder) Build() StackBuilderInterface { - b.stack = GenericStack(b.client) - return b -} - -// GenericStack returns generic stacktrace. -func GenericStack(client RavenClientInterface) *raven.Stacktrace { - return raven.NewStacktrace(0, 3, client.IncludePaths()) -} diff --git a/core/stacktrace/generic_stack_builder_test.go b/core/stacktrace/generic_stack_builder_test.go deleted file mode 100644 index 10a445a..0000000 --- a/core/stacktrace/generic_stack_builder_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package stacktrace - -import ( - "testing" - - "github.com/getsentry/raven-go" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" -) - -type GenericStackBuilderSuite struct { - builder *GenericStackBuilder - suite.Suite -} - -func TestGenericStack(t *testing.T) { - suite.Run(t, new(GenericStackBuilderSuite)) -} - -func (s *GenericStackBuilderSuite) SetupSuite() { - client, _ := raven.New("fake dsn") - s.builder = &GenericStackBuilder{AbstractStackBuilder{ - client: client, - }} -} - -func (s *GenericStackBuilderSuite) Test_Build() { - stack, err := s.builder.Build().GetResult() - require.Nil(s.T(), err) - assert.NotEmpty(s.T(), stack) -} diff --git a/core/stacktrace/pkg_errors_builder.go b/core/stacktrace/pkg_errors_builder.go deleted file mode 100644 index 996e73f..0000000 --- a/core/stacktrace/pkg_errors_builder.go +++ /dev/null @@ -1,96 +0,0 @@ -package stacktrace - -import ( - pkgErrors "github.com/pkg/errors" -) - -// PkgErrorCauseable is an interface for checking Cause() method existence in the error. -type PkgErrorCauseable interface { - Cause() error -} - -// PkgErrorTraceable is an interface for checking StackTrace() method existence in the error. -type PkgErrorTraceable interface { - StackTrace() pkgErrors.StackTrace -} - -// IsPkgErrorsError returns true if passed error might be github.com/pkg/errors error. -func IsPkgErrorsError(err error) bool { - _, okTraceable := err.(PkgErrorTraceable) // nolint:errorlint - _, okCauseable := err.(PkgErrorCauseable) // nolint:errorlint - return okTraceable || okCauseable -} - -// PkgErrorsStackTransformer transforms stack data from github.com/pkg/errors error to stacktrace.Stacktrace. -type PkgErrorsStackTransformer struct { - stack pkgErrors.StackTrace -} - -// NewPkgErrorsStackTransformer is a PkgErrorsStackTransformer constructor. -func NewPkgErrorsStackTransformer(stack pkgErrors.StackTrace) *PkgErrorsStackTransformer { - return &PkgErrorsStackTransformer{stack: stack} -} - -// Stack returns stacktrace (which is []uintptr internally, each uintptc is a pc). -func (p *PkgErrorsStackTransformer) Stack() Stacktrace { - if p.stack == nil { - return Stacktrace{} - } - - result := make(Stacktrace, len(p.stack)) - for i, frame := range p.stack { - result[i] = Frame(uintptr(frame) - 1) - } - return result -} - -// PkgErrorsBuilder builds stacktrace with data from github.com/pkg/errors error. -type PkgErrorsBuilder struct { - AbstractStackBuilder -} - -// Build stacktrace. -func (b *PkgErrorsBuilder) Build() StackBuilderInterface { - if !IsPkgErrorsError(b.err) { - b.buildErr = ErrUnfeasibleBuilder - return b - } - - var stack pkgErrors.StackTrace - err := b.err - - for err != nil { - s := b.getErrorStack(err) - if s != nil { - stack = s - } - err = b.getErrorCause(err) - } - - if len(stack) > 0 { - b.stack = NewRavenStacktraceBuilder(NewPkgErrorsStackTransformer(stack)).Build(3, b.client.IncludePaths()) - } else { - b.buildErr = ErrUnfeasibleBuilder - } - - return b -} - -// getErrorCause will try to extract original error from wrapper - it is used only if stacktrace is not present. -func (b *PkgErrorsBuilder) getErrorCause(err error) error { - causeable, ok := err.(PkgErrorCauseable) // nolint:errorlint - if !ok { - return nil - } - return causeable.Cause() -} - -// getErrorStackTrace will try to extract stacktrace from error using StackTrace method -// (default errors doesn't have it). -func (b *PkgErrorsBuilder) getErrorStack(err error) pkgErrors.StackTrace { - traceable, ok := err.(PkgErrorTraceable) // nolint:errorlint - if !ok { - return nil - } - return traceable.StackTrace() -} diff --git a/core/stacktrace/pkg_errors_builder_test.go b/core/stacktrace/pkg_errors_builder_test.go deleted file mode 100644 index 7379afe..0000000 --- a/core/stacktrace/pkg_errors_builder_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package stacktrace - -import ( - "errors" - "testing" - - "github.com/getsentry/raven-go" - pkgErrors "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" -) - -// errorWithCause has Cause() method, but doesn't have StackTrace() method. -type errorWithCause struct { - cause error - msg string -} - -func newErrorWithCause(msg string, cause error) error { - return &errorWithCause{ - msg: msg, - cause: cause, - } -} - -func (e *errorWithCause) Error() string { - return e.msg -} - -func (e *errorWithCause) Cause() error { - return e.cause -} - -type PkgErrorsStackProviderSuite struct { - transformer *PkgErrorsStackTransformer - suite.Suite -} - -func (s *PkgErrorsStackProviderSuite) SetupSuite() { - s.transformer = &PkgErrorsStackTransformer{} -} - -func (s *PkgErrorsStackProviderSuite) Test_Nil() { - s.transformer.stack = nil - assert.Empty(s.T(), s.transformer.Stack()) -} - -func (s *PkgErrorsStackProviderSuite) Test_Empty() { - s.transformer.stack = pkgErrors.StackTrace{} - assert.Empty(s.T(), s.transformer.Stack()) -} - -func (s *PkgErrorsStackProviderSuite) Test_Full() { - testErr := pkgErrors.New("test") - s.transformer.stack = testErr.(PkgErrorTraceable).StackTrace() // nolint:errorlint - assert.NotEmpty(s.T(), s.transformer.Stack()) -} - -type PkgErrorsBuilderSuite struct { - builder *PkgErrorsBuilder - suite.Suite -} - -func (s *PkgErrorsBuilderSuite) SetupTest() { - s.builder = &PkgErrorsBuilder{} - client, _ := raven.New("fake dsn") - s.builder.SetClient(client) -} - -func (s *PkgErrorsBuilderSuite) Test_Stackless() { - s.builder.SetError(errors.New("simple")) - stack, err := s.builder.Build().GetResult() - require.Error(s.T(), err) - assert.Equal(s.T(), ErrUnfeasibleBuilder, err) - assert.Empty(s.T(), stack) -} - -func (s *PkgErrorsBuilderSuite) Test_WithStack() { - s.builder.SetError(pkgErrors.New("with stack")) - stack, err := s.builder.Build().GetResult() - require.NoError(s.T(), err) - require.NotEmpty(s.T(), stack) - assert.NotEmpty(s.T(), stack.Frames) -} - -func (s *PkgErrorsBuilderSuite) Test_CauseWithStack() { - s.builder.SetError(newErrorWithCause("cause with stack", pkgErrors.New("with stack"))) - stack, err := s.builder.Build().GetResult() - require.NoError(s.T(), err) - require.NotEmpty(s.T(), stack) - assert.NotEmpty(s.T(), stack.Frames) -} - -func TestPkgErrorsStackProvider(t *testing.T) { - suite.Run(t, new(PkgErrorsStackProviderSuite)) -} - -func TestPkgErrorsBuilder(t *testing.T) { - suite.Run(t, new(PkgErrorsBuilderSuite)) -} diff --git a/core/stacktrace/raven_client_interface.go b/core/stacktrace/raven_client_interface.go deleted file mode 100644 index ec281d8..0000000 --- a/core/stacktrace/raven_client_interface.go +++ /dev/null @@ -1,31 +0,0 @@ -package stacktrace - -import "github.com/getsentry/raven-go" - -// RavenClientInterface includes all necessary calls from *raven.Client. Therefore, it can be mocked or replaced. -type RavenClientInterface interface { - SetIgnoreErrors(errs []string) error - SetDSN(dsn string) error - SetRelease(release string) - SetEnvironment(environment string) - SetDefaultLoggerName(name string) - SetSampleRate(rate float32) error - Capture(packet *raven.Packet, captureTags map[string]string) (eventID string, ch chan error) - CaptureMessage(message string, tags map[string]string, interfaces ...raven.Interface) string - CaptureMessageAndWait(message string, tags map[string]string, interfaces ...raven.Interface) string - CaptureError(err error, tags map[string]string, interfaces ...raven.Interface) string - CaptureErrorAndWait(err error, tags map[string]string, interfaces ...raven.Interface) string - CapturePanic(f func(), tags map[string]string, interfaces ...raven.Interface) (err interface{}, errorID string) - CapturePanicAndWait(f func(), tags map[string]string, interfaces ...raven.Interface) (err interface{}, errorID string) - Close() - Wait() - URL() string - ProjectID() string - Release() string - IncludePaths() []string - SetIncludePaths(p []string) - SetUserContext(u *raven.User) - SetHttpContext(h *raven.Http) - SetTagsContext(t map[string]string) - ClearContext() -} diff --git a/core/stacktrace/raven_stacktrace_builder.go b/core/stacktrace/raven_stacktrace_builder.go deleted file mode 100644 index 44978e1..0000000 --- a/core/stacktrace/raven_stacktrace_builder.go +++ /dev/null @@ -1,68 +0,0 @@ -package stacktrace - -import ( - "path" - "runtime" - - "github.com/getsentry/raven-go" -) - -// Frame is a program counter inside a stack frame. -type Frame uintptr - -// Stacktrace is stack of Frames. -type Stacktrace []Frame - -// RavenStackTransformer is an interface for any component, which will transform some -// unknown stacktrace data to stacktrace.Stacktrace. -type RavenStackTransformer interface { - Stack() Stacktrace -} - -// RavenStacktraceBuilder builds *raven.Stacktrace for any generic stack data. -type RavenStacktraceBuilder struct { - transformer RavenStackTransformer -} - -// NewRavenStacktraceBuilder is a RavenStacktraceBuilder constructor. -func NewRavenStacktraceBuilder(p RavenStackTransformer) *RavenStacktraceBuilder { - return (&RavenStacktraceBuilder{}).SetTransformer(p) -} - -// SetTransformer sets stack transformer into stacktrace builder. -func (b *RavenStacktraceBuilder) SetTransformer(p RavenStackTransformer) *RavenStacktraceBuilder { - b.transformer = p - return b -} - -// Build converts generic stacktrace to to github.com/getsentry/raven-go.Stacktrace. -func (b *RavenStacktraceBuilder) Build(context int, appPackagePrefixes []string) *raven.Stacktrace { - // This code is borrowed from github.com/getsentry/raven-go.NewStacktrace(). - var frames []*raven.StacktraceFrame - for _, f := range b.transformer.Stack() { - frame := b.convertFrame(f, context, appPackagePrefixes) - if frame != nil { - frames = append(frames, frame) - } - } - if len(frames) == 0 { - return nil - } - for i, j := 0, len(frames)-1; i < j; i, j = i+1, j-1 { - frames[i], frames[j] = frames[j], frames[i] - } - return &raven.Stacktrace{Frames: frames} -} - -// convertFrame converts single generic stacktrace frame to github.com/pkg/errors.Frame. -func (b *RavenStacktraceBuilder) convertFrame( - f Frame, context int, appPackagePrefixes []string) *raven.StacktraceFrame { - // This code is borrowed from github.com/pkg/errors.Frame. - pc := uintptr(f) - 1 - line := 0 - file := "unknown" - if fn := runtime.FuncForPC(pc); fn != nil { - file, line = fn.FileLine(pc) - } - return raven.NewStacktraceFrame(pc, path.Dir(file), file, line, context, appPackagePrefixes) -} diff --git a/core/stacktrace/raven_stacktrace_builder_test.go b/core/stacktrace/raven_stacktrace_builder_test.go deleted file mode 100644 index a4a0fee..0000000 --- a/core/stacktrace/raven_stacktrace_builder_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package stacktrace - -import ( - "runtime" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" -) - -type ravenMockTransformer struct { - mock.Mock -} - -func (r *ravenMockTransformer) Stack() Stacktrace { - args := r.Called() - return args.Get(0).(Stacktrace) -} - -type RavenStacktraceBuilderSuite struct { - suite.Suite -} - -func (s *RavenStacktraceBuilderSuite) callers() Stacktrace { - const depth = 32 - var pcs [depth]uintptr - n := runtime.Callers(3, pcs[:]) - st := make(Stacktrace, n) - for i := 0; i < n; i++ { - st[i] = Frame(pcs[i]) - } - return st -} - -func (s *RavenStacktraceBuilderSuite) Test_BuildEmpty() { - testTransformer := new(ravenMockTransformer) - testTransformer.On("Stack", mock.Anything).Return(Stacktrace{}) - - assert.Nil(s.T(), NewRavenStacktraceBuilder(testTransformer).Build(3, []string{})) -} - -func (s *RavenStacktraceBuilderSuite) Test_BuildActual() { - testTransformer := new(ravenMockTransformer) - testTransformer.On("Stack", mock.Anything).Return(s.callers()) - stack := NewRavenStacktraceBuilder(testTransformer).Build(3, []string{}) - - require.NotNil(s.T(), stack) - assert.NotEmpty(s.T(), stack.Frames) -} - -func TestRavenStacktraceBuilder(t *testing.T) { - suite.Run(t, new(RavenStacktraceBuilderSuite)) -} diff --git a/core/stacktrace/stack.go b/core/stacktrace/stack.go new file mode 100644 index 0000000..b6262a2 --- /dev/null +++ b/core/stacktrace/stack.go @@ -0,0 +1,250 @@ +// Package stacktrace contains code borrowed from the github.com/pkg/errors +package stacktrace + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "path" + "runtime" + "strconv" + "strings" +) + +const unknown = "unknown" + +var ( + dunno = []byte("???") + centerDot = []byte("·") + dot = []byte(".") + slash = []byte("/") +) + +// Frame represents a program counter inside a stack frame. +// For historical reasons if Frame is interpreted as an uintptr +// its value represents the program counter + 1. +type Frame uintptr + +// pc returns the program counter for this frame; +// multiple frames may have the same PC value. +func (f Frame) pc() uintptr { return uintptr(f) - 1 } + +// file returns the full path to the file that contains the +// function for this Frame's pc. +func (f Frame) file() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return unknown + } + file, _ := fn.FileLine(f.pc()) + return file +} + +// line returns the line number of source code of the +// function for this Frame's pc. +func (f Frame) line() int { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return 0 + } + _, line := fn.FileLine(f.pc()) + return line +} + +// name returns the name of this function, if known. +func (f Frame) name() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return unknown + } + return fn.Name() +} + +// Format formats the frame according to the fmt.Formatter interface. +// +// %s source file +// %d source line +// %n function name +// %v equivalent to %s:%d +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+s function name and path of source file relative to the compile time +// GOPATH separated by \n\t (\n\t) +// %+v equivalent to %+s:%d +func (f Frame) Format(s fmt.State, verb rune) { + switch verb { + case 's': + switch { + case s.Flag('+'): + io.WriteString(s, f.name()) + io.WriteString(s, "\n\t") + io.WriteString(s, f.file()) + default: + io.WriteString(s, path.Base(f.file())) + } + case 'd': + io.WriteString(s, strconv.Itoa(f.line())) + case 'n': + io.WriteString(s, funcname(f.name())) + case 'v': + f.Format(s, 's') + io.WriteString(s, ":") + f.Format(s, 'd') + } +} + +// MarshalText formats a stacktrace Frame as a text string. The output is the +// same as that of fmt.Sprintf("%+v", f), but without newlines or tabs. +func (f Frame) MarshalText() ([]byte, error) { + name := f.name() + if name == unknown { + return []byte(name), nil + } + return []byte(fmt.Sprintf("%s %s:%d", name, f.file(), f.line())), nil +} + +// StackTrace is stack of Frames from innermost (newest) to outermost (oldest). +type StackTrace []Frame + +// Format formats the stack of Frames according to the fmt.Formatter interface. +// +// %s lists source files for each Frame in the stack +// %v lists the source file and line number for each Frame in the stack +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+v Prints filename, function, and line number for each Frame in the stack. +func (st StackTrace) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case s.Flag('+'): + for _, f := range st { + io.WriteString(s, "\n") + f.Format(s, verb) + } + case s.Flag('#'): + fmt.Fprintf(s, "%#v", []Frame(st)) + default: + st.formatSlice(s, verb) + } + case 's': + st.formatSlice(s, verb) + } +} + +// formatSlice will format this StackTrace into the given buffer as a slice of +// Frame, only valid when called with '%s' or '%v'. +func (st StackTrace) formatSlice(s fmt.State, verb rune) { + io.WriteString(s, "[") + for i, f := range st { + if i > 0 { + io.WriteString(s, " ") + } + f.Format(s, verb) + } + io.WriteString(s, "]") +} + +// stack represents a stack of program counters. +type stack []uintptr + +type StackTraced interface { + StackTrace() StackTrace +} + +func (s *stack) Format(st fmt.State, verb rune) { + if verb == 'v' && st.Flag('+') { + for _, pc := range *s { + f := Frame(pc) + fmt.Fprintf(st, "\n%+v", f) + } + } +} + +func (s *stack) StackTrace() StackTrace { + f := make([]Frame, len(*s)) + for i := 0; i < len(f); i++ { + f[i] = Frame((*s)[i]) + } + return f +} + +// FormattedStack returns a nicely formatted stack frame, skipping skip frames. +func FormattedStack(skip int, prefix string) []byte { + buf := new(bytes.Buffer) // the returned data + // As we loop, we open files and read them. These variables record the currently + // loaded file. + var lines [][]byte + var lastFile string + for i := skip; ; i++ { // Skip the expected number of frames + pc, file, line, ok := runtime.Caller(i) + if !ok { + break + } + // Print this much at least. If we can't find the source, it won't show. + fmt.Fprintf(buf, "%s%s:%d (0x%x)\n", prefix, file, line, pc) + if file != lastFile { + data, err := ioutil.ReadFile(file) + if err != nil { + continue + } + lines = bytes.Split(data, []byte{'\n'}) + lastFile = file + } + fmt.Fprintf(buf, "%s\t%s: %s\n", prefix, function(pc), source(lines, line)) + } + return buf.Bytes() +} + +func callers(skip int) *stack { + const depth = 32 + var pcs [depth]uintptr + n := runtime.Callers(skip, pcs[:]) + var st stack = pcs[0:n] + return &st +} + +// function returns, if possible, the name of the function containing the PC. +func function(pc uintptr) []byte { + fn := runtime.FuncForPC(pc) + if fn == nil { + return dunno + } + name := []byte(fn.Name()) + // The name includes the path name to the package, which is unnecessary + // since the file name is already included. Plus, it has center dots. + // That is, we see + // runtime/debug.*T·ptrmethod + // and want + // *T.ptrmethod + // Also the package path might contains dot (e.g. code.google.com/...), + // so first eliminate the path prefix + if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 { + name = name[lastSlash+1:] + } + if period := bytes.Index(name, dot); period >= 0 { + name = name[period+1:] + } + name = bytes.ReplaceAll(name, centerDot, dot) + return name +} + +// source returns a space-trimmed slice of the n'th line. +func source(lines [][]byte, n int) []byte { + n-- // in stack trace, lines are 1-indexed but our array is 0-indexed + if n < 0 || n >= len(lines) { + return dunno + } + return bytes.TrimSpace(lines[n]) +} + +// funcname removes the path prefix component of a function's name reported by func.Name(). +func funcname(name string) string { + i := strings.LastIndex(name, "/") + name = name[i+1:] + i = strings.Index(name, ".") + return name[i+1:] +} diff --git a/core/stacktrace/stack_builder_factory.go b/core/stacktrace/stack_builder_factory.go deleted file mode 100644 index 0d58623..0000000 --- a/core/stacktrace/stack_builder_factory.go +++ /dev/null @@ -1,19 +0,0 @@ -package stacktrace - -// GetStackBuilderByErrorType tries to guess which stacktrace builder would be feasible for passed error. -// For example, errors from github.com/pkg/errors have StackTrace() method, and Go 1.13 errors can be unwrapped. -func GetStackBuilderByErrorType(err error) StackBuilderInterface { - if IsPkgErrorsError(err) { - return &PkgErrorsBuilder{AbstractStackBuilder{err: err}} - } - - if IsUnwrappableError(err) { - return &UnwrapBuilder{AbstractStackBuilder{err: err}} - } - - if IsErrorNodesList(err) { - return &ErrCollectorBuilder{AbstractStackBuilder{err: err}} - } - - return &GenericStackBuilder{AbstractStackBuilder{err: err}} -} diff --git a/core/stacktrace/stack_builder_factory_test.go b/core/stacktrace/stack_builder_factory_test.go deleted file mode 100644 index a543c90..0000000 --- a/core/stacktrace/stack_builder_factory_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package stacktrace - -import ( - "errors" - "testing" - - pkgErrors "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - - "github.com/retailcrm/mg-transport-core/v2/core/util/errorutil" -) - -func TestGetStackBuilderByErrorType_PkgErrors(t *testing.T) { - testErr := pkgErrors.New("pkg/errors err") - builder := GetStackBuilderByErrorType(testErr) - assert.IsType(t, &PkgErrorsBuilder{}, builder) -} - -func TestGetStackBuilderByErrorType_UnwrapBuilder(t *testing.T) { - testErr := newWrappableError("first", newWrappableError("second", errors.New("third"))) - builder := GetStackBuilderByErrorType(testErr) - assert.IsType(t, &UnwrapBuilder{}, builder) -} - -func TestGetStackBuilderByErrorType_ErrCollectorBuilder(t *testing.T) { - testErr := errorutil.NewCollector().Do(errors.New("first"), errors.New("second")).AsError() - builder := GetStackBuilderByErrorType(testErr) - assert.IsType(t, &ErrCollectorBuilder{}, builder) -} - -func TestGetStackBuilderByErrorType_Generic(t *testing.T) { - defaultErr := errors.New("default err") - builder := GetStackBuilderByErrorType(defaultErr) - assert.IsType(t, &GenericStackBuilder{}, builder) -} diff --git a/core/stacktrace/stack_test.go b/core/stacktrace/stack_test.go new file mode 100644 index 0000000..7934114 --- /dev/null +++ b/core/stacktrace/stack_test.go @@ -0,0 +1,114 @@ +package stacktrace + +import ( + "fmt" + "strconv" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type FrameTest struct { + suite.Suite +} + +type stackTest struct { + suite.Suite +} + +type StackTraceTest struct { + suite.Suite +} + +func TestFrame(t *testing.T) { + suite.Run(t, new(FrameTest)) +} + +func Test_stack(t *testing.T) { + suite.Run(t, new(stackTest)) +} + +func TestStackTrace(t *testing.T) { + suite.Run(t, new(StackTraceTest)) +} + +func Test_callers(t *testing.T) { + stack0 := callers(0) + stack1 := callers(1) + stack2 := callers(2) + + assert.Len(t, *stack0, len(*stack1)+1) + assert.Len(t, *stack1, len(*stack2)+1) + assert.Equal(t, "callers", strings.ToLower(string(function((*stack0)[0])))) + assert.Equal(t, "callers", strings.ToLower(string(function((*stack1)[0])))) + assert.Equal(t, "Test_callers", string(function((*stack2)[0]))) +} + +func Test_function(t *testing.T) { + assert.Equal(t, dunno, function(uintptr(9000000000))) + assert.Equal(t, "Test_function", string(function((*callers(2))[0]))) + assert.Equal(t, "tRunner", string(function((*callers(3))[0]))) +} + +func Test_source(t *testing.T) { + assert.Equal(t, dunno, source([][]byte{}, 0)) + assert.Equal(t, dunno, source([][]byte{}, 1)) + assert.Equal(t, []byte("test"), source([][]byte{[]byte("test")}, 1)) +} + +func Test_funcname(t *testing.T) { + assert.Equal(t, "c", funcname("a/b.c")) +} + +func (t *FrameTest) Test_pc() { + t.Assert().Equal(uintptr(0), Frame(uintptr(1)).pc()) +} + +func (t *FrameTest) Test_file() { + t.Assert().Equal(t.fakeFrame().file(), "unknown") + t.Assert().Contains(t.frame().file(), "core/stacktrace/stack_test.go") +} + +func (t *FrameTest) Test_line() { + t.Assert().Equal(0, t.fakeFrame().line()) + t.Assert().True(t.frame().line() > 0) +} + +func (t *FrameTest) Test_name() { + t.Assert().Equal("unknown", t.fakeFrame().name()) + t.Assert().Contains(t.frame().name(), "Test_name") +} + +func (t *FrameTest) Test_Format() { + t.Assert().Equal("stack_test.go", fmt.Sprintf("%s", t.frame())) + t.Assert().Contains(fmt.Sprintf("%+s", t.frame()), "stacktrace.(*FrameTest).Test_Format") + t.Assert().Contains(fmt.Sprintf("%+s", t.frame()), "core/stacktrace/stack_test.go") + t.Assert().Equal(strconv.Itoa(t.frame().line()), fmt.Sprintf("%d", t.frame())) + t.Assert().Equal("(*FrameTest).Test_Format", fmt.Sprintf("%n", t.frame())) + t.Assert().Equal(fmt.Sprintf("stack_test.go:%d", t.frame().line()), fmt.Sprintf("%v", t.frame())) +} + +func (t *FrameTest) Test_MarshalText_Invalid() { + data, err := t.fakeFrame().MarshalText() + + t.Require().NoError(err) + t.Assert().Equal("unknown", string(data)) +} + +func (t *FrameTest) Test_MarshalText() { + data, err := t.frame().MarshalText() + + t.Require().NoError(err) + t.Assert().Contains(string(data), "stacktrace.(*FrameTest).Test_MarshalText") + t.Assert().Contains(string(data), "core/stacktrace/stack_test.go") +} + +func (t *FrameTest) frame() Frame { + return Frame((*callers(3))[0]) +} + +func (t *FrameTest) fakeFrame() Frame { + return Frame(9000000001) +} diff --git a/core/stacktrace/unwrap_builder.go b/core/stacktrace/unwrap_builder.go deleted file mode 100644 index 737cff6..0000000 --- a/core/stacktrace/unwrap_builder.go +++ /dev/null @@ -1,62 +0,0 @@ -package stacktrace - -import ( - "github.com/getsentry/raven-go" -) - -// Unwrappable is the interface for errors with Unwrap() method. -type Unwrappable interface { - Unwrap() error -} - -// UnwrapBuilder builds stacktrace from the chain of wrapped errors. -type UnwrapBuilder struct { - AbstractStackBuilder -} - -// IsUnwrappableError returns true if error can be unwrapped. -func IsUnwrappableError(err error) bool { - _, ok := err.(Unwrappable) // nolint:errorlint - return ok -} - -// Build stacktrace. -func (b *UnwrapBuilder) Build() StackBuilderInterface { - if !IsUnwrappableError(b.err) { - b.buildErr = ErrUnfeasibleBuilder - return b - } - - err := b.err - var frames []*raven.StacktraceFrame - - for err != nil { - frames = append(frames, raven.NewStacktraceFrame( - 0, - ": "+err.Error(), - "", - 0, - 3, - b.client.IncludePaths(), - )) - - if item, ok := err.(Unwrappable); ok { // nolint:errorlint - err = item.Unwrap() - } else { - err = nil - } - } - - if len(frames) <= 1 { - b.buildErr = ErrUnfeasibleBuilder - return b - } - - // Sentry wants the frames with the oldest first, so reverse them - for i, j := 0, len(frames)-1; i < j; i, j = i+1, j-1 { - frames[i], frames[j] = frames[j], frames[i] - } - - b.stack = &raven.Stacktrace{Frames: frames} - return b -} diff --git a/core/stacktrace/unwrap_builder_test.go b/core/stacktrace/unwrap_builder_test.go deleted file mode 100644 index ddfae71..0000000 --- a/core/stacktrace/unwrap_builder_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package stacktrace - -import ( - "errors" - "testing" - - "github.com/getsentry/raven-go" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" -) - -// simpleError is a simplest error implementation possible. The only reason why it's here is tests. -type simpleError struct { - msg string -} - -func newSimpleError(msg string) error { - return &simpleError{msg: msg} -} - -func (n *simpleError) Error() string { - return n.msg -} - -// wrappableError is a simple implementation of wrappable error. -type wrappableError struct { - err error - msg string -} - -func newWrappableError(msg string, child error) error { - return &wrappableError{msg: msg, err: child} -} - -func (e *wrappableError) Error() string { - return e.msg -} - -func (e *wrappableError) Unwrap() error { - return e.err -} - -type UnwrapBuilderSuite struct { - builder *UnwrapBuilder - suite.Suite -} - -func TestUnwrapBuilder(t *testing.T) { - suite.Run(t, new(UnwrapBuilderSuite)) -} - -func (s *UnwrapBuilderSuite) SetupTest() { - client, _ := raven.New("fake dsn") - s.builder = &UnwrapBuilder{} - s.builder.SetClient(client) -} - -func (s *UnwrapBuilderSuite) TestBuild_Nil() { - stack, err := s.builder.Build().GetResult() - require.Error(s.T(), err) - if stack != nil { - assert.Empty(s.T(), stack.Frames) - } - assert.Equal(s.T(), ErrUnfeasibleBuilder, err) -} - -func (s *UnwrapBuilderSuite) TestBuild_NoUnwrap() { - s.builder.SetError(newSimpleError("fake")) - stack, buildErr := s.builder.Build().GetResult() - require.Error(s.T(), buildErr) - require.Equal(s.T(), ErrUnfeasibleBuilder, buildErr) - assert.Empty(s.T(), stack) -} - -func (s *UnwrapBuilderSuite) TestBuild_WrappableHasWrapped() { - testErr := newWrappableError("first", newWrappableError("second", errors.New("third"))) - require.True(s.T(), IsUnwrappableError(testErr)) - - s.builder.SetError(testErr) - stack, buildErr := s.builder.Build().GetResult() - require.NoError(s.T(), buildErr) - require.NotNil(s.T(), stack) - require.NotNil(s.T(), stack.Frames) - assert.NotEmpty(s.T(), stack.Frames) - assert.True(s.T(), len(stack.Frames) > 1) -} diff --git a/go.mod b/go.mod index 77479ba..5083062 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,8 @@ require ( github.com/aws/aws-sdk-go v1.36.30 github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054 // indirect github.com/denisenkom/go-mssqldb v0.0.0-20190830225923-3302f0226fbd // indirect - github.com/getsentry/raven-go v0.2.0 + github.com/getsentry/raven-go v0.2.0 // indirect + github.com/getsentry/sentry-go v0.12.0 github.com/gin-contrib/multitemplate v0.0.0-20190914010127-bba2ccfe37ec github.com/gin-gonic/gin v1.7.2 github.com/go-playground/validator/v10 v10.8.0 @@ -21,7 +22,6 @@ require ( github.com/json-iterator/go v1.1.11 // indirect github.com/kr/text v0.2.0 // indirect github.com/lib/pq v1.9.0 // indirect - github.com/mattn/go-isatty v0.0.13 // indirect github.com/nicksnyder/go-i18n/v2 v2.0.2 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 @@ -30,7 +30,7 @@ require ( github.com/retailcrm/mg-transport-api-client-go v1.1.32 github.com/stretchr/testify v1.7.0 github.com/ugorji/go v1.2.6 // indirect - golang.org/x/text v0.3.6 + golang.org/x/text v0.3.7 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect diff --git a/go.sum b/go.sum index b6e4bf0..e588d81 100644 --- a/go.sum +++ b/go.sum @@ -31,19 +31,27 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= +github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFDnH08= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= +github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/aws/aws-sdk-go v1.36.30 h1:hAwyfe7eZa7sM+S5mIJZFiNFwJMia9Whz6CYblioLoU= github.com/aws/aws-sdk-go v1.36.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054 h1:uH66TXeswKn5PW5zdZ39xEwfS9an067BirqA+P4QaLI= @@ -53,6 +61,11 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -61,18 +74,28 @@ github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f/go.mod h1:xN github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= github.com/denisenkom/go-mssqldb v0.0.0-20190830225923-3302f0226fbd h1:DoaaxHqzWPQCWKSTmsi8UDSiFqxbfue+Xt+qi/BFKb8= github.com/denisenkom/go-mssqldb v0.0.0-20190830225923-3302f0226fbd/go.mod h1:uU0N10vx1abI4qeVe79CxepBP6PPREVTgMS5Gx6/mOk= +github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/getsentry/sentry-go v0.12.0 h1:era7g0re5iY13bHSdN/xMkyV+5zZppjRVQhZrXCaEIk= +github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= github.com/gin-contrib/multitemplate v0.0.0-20190914010127-bba2ccfe37ec h1:mfeHJfPwsXp/iovjrTwxtkTMRAIXZu6Uxg6O95En1+Y= github.com/gin-contrib/multitemplate v0.0.0-20190914010127-bba2ccfe37ec/go.mod h1:2tmLQ8sVzr2XKwquGd7zNq3zB6fGyjJL+47JoxoF8yM= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= @@ -81,11 +104,15 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA= github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= @@ -99,9 +126,13 @@ github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -129,6 +160,7 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -139,6 +171,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -153,19 +186,30 @@ github.com/google/pprof v0.0.0-20200507031123-427632fa3b1c/go.mod h1:ZgVRPoUq/hf github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ= github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= +github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= +github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= +github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= +github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/gorm v1.9.2/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo= @@ -189,8 +233,18 @@ github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMW github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= +github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= +github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE= +github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro= +github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 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= @@ -198,6 +252,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= @@ -205,21 +261,37 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8= github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= -github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= +github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/nicksnyder/go-i18n/v2 v2.0.2 h1:KsHGcTByIM0mHZKQGy0nlJLOjPNjQ6MVib/3PvsBDNY= @@ -228,12 +300,18 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -252,8 +330,21 @@ github.com/retailcrm/api-client-go/v2 v2.0.3/go.mod h1:1yTZl9+gd3+/k0kAJe7sYvC+m 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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -267,10 +358,25 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E= github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= @@ -280,15 +386,20 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -323,16 +434,19 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -344,8 +458,10 @@ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20211008194852-3b03d305991f h1:1scJEYZBaF48BaG6tYbtxmLcXqwYGSfGcMoStTqkkIw= +golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -363,6 +479,7 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -372,7 +489,9 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -391,27 +510,37 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac h1:oN6lz7iLW/YC7un8pq+9bOLyXrprv2+DKfkJY+2LJJw= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -533,12 +662,16 @@ gopkg.in/gormigrate.v1 v1.6.0 h1:XpYM6RHQPmzwY7Uyu+t+xxMXc86JYFJn4nEc9HzQjsI= gopkg.in/gormigrate.v1 v1.6.0/go.mod h1:Lf00lQrHqfSYWiTtPcyQabsDdM6ejZaMgV0OU6JMSlw= gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= +gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=