Replace old Sentry client with the new SDK

* replace old Sentry client with the new SDK
* do not initialize Sentry SDK twice, correct panic processing
* account prefix for panics
* log errors with account context data
* tests
* linter fixes
* removed 1.18 from available versions
This commit is contained in:
Pavel 2022-03-18 10:46:22 +03:00 committed by GitHub
parent bcef82e969
commit 627ceae0bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1109 additions and 1227 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
.idea
coverage.*
vendor

View File

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

View File

@ -1,8 +1,9 @@
package core
import (
"github.com/stretchr/testify/assert"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_GetSaasDomains(t *testing.T) {

View File

@ -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 = "<unknown version>"
}
if a.Build == "" {
a.Build = "<unknown build>"
}
if a.BuildDate == "" {
a.BuildDate = "<unknown build date>"
}
if a.Commit == "" {
a.Commit = "<no commit info>"
}
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,
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(),
}
}

View File

@ -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(
"<unknown version> (<unknown build>, built <unknown build date>, commit \"<no commit info>\")",
a.Release())
}
func (t *AppInfoTest) Test_Release() {
a := AppInfo{
Version: "1647352938",
Commit: "cb03e2f - replace old Sentry client with the new SDK <Neur0toxine>",
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

View File

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

View File

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

View File

@ -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 {
SentryConfig sentry.ClientOptions
Logger logger.Logger
Client stacktrace.RavenClientInterface
Localizer *Localizer
AppInfo AppInfo
SentryLoggerConfig SentryLoggerConfig
ServerName string
DefaultError string
TaggedTypes SentryTaggedTypes
Stacktrace bool
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,
// 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)
}
sentry.createRavenClient(sentryDSN)
return sentry
})
}
// NewTaggedStruct constructor.
@ -102,78 +115,76 @@ 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)
}
if rec != nil || len(c.Errors) > 0 {
c.Abort()
}
}()
c.Next()
}
}
// ErrorMiddleware returns error handlers, attachable to gin.Engine.
func (s *Sentry) ErrorMiddleware() gin.HandlerFunc {
defaultHandlers := []ErrorHandlerFunc{
s.ErrorResponseHandler(),
s.PanicLogger(),
s.ErrorLogger(),
}
if s.Client != nil {
defaultHandlers = append(defaultHandlers, s.ErrorCaptureHandler())
}
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)
}
debug.PrintStack()
}
}
}
// 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)
}
}
}
}
// ErrorResponseHandler will be executed in case of any unexpected error.
func (s *Sentry) ErrorResponseHandler() ErrorHandlerFunc {
return func(recovery interface{}, c *gin.Context) {
recovery := recover()
publicErrors := c.Errors.ByType(gin.ErrorTypePublic)
privateLen := len(c.Errors.ByType(gin.ErrorTypePrivate))
privateErrors := c.Errors.ByType(gin.ErrorTypePrivate)
publicLen := len(publicErrors)
privateLen := len(privateErrors)
if privateLen == 0 && publicLen == 0 && recovery == nil {
return
@ -184,13 +195,21 @@ func (s *Sentry) ErrorResponseHandler() ErrorHandlerFunc {
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
@ -200,61 +219,113 @@ func (s *Sentry) ErrorResponseHandler() ErrorHandlerFunc {
}
c.JSON(http.StatusInternalServerError, gin.H{"error": messages})
// will be caught by Sentry middleware
if recovery != nil {
panic(recovery)
}
}()
c.Next()
}
}
// 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,
// 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)
// 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
}
c.JSON(http.StatusInternalServerError, gin.H{
"error": []string{s.Localizer.GetLocalizedMessage(s.DefaultError)},
})
}
}
}()
c.Next()
}
}
// 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)
}
}
// 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")
}

View File

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

View File

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

View File

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

View File

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

View File

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

57
core/stacktrace/error.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

250
core/stacktrace/stack.go Normal file
View File

@ -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 (<funcname>\n\t<path>)
// %+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:]
}

View File

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

View File

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

View File

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

View File

@ -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,
"<message>: "+err.Error(),
"<wrapped>",
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
}

View File

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

6
go.mod
View File

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

145
go.sum
View File

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