mirror of
https://github.com/retailcrm/mg-transport-core.git
synced 2024-11-21 20:56:04 +03:00
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:
parent
bcef82e969
commit
627ceae0bc
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
.idea
|
||||
coverage.*
|
||||
vendor
|
||||
|
@ -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
|
||||
|
@ -1,8 +1,9 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_GetSaasDomains(t *testing.T) {
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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...)...)
|
||||
}
|
||||
|
@ -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")
|
||||
|
||||
|
326
core/sentry.go
326
core/sentry.go
@ -2,21 +2,29 @@ package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
sentrygin "github.com/getsentry/sentry-go/gin"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/retailcrm/mg-transport-core/v2/core/logger"
|
||||
|
||||
"github.com/retailcrm/mg-transport-core/v2/core/stacktrace"
|
||||
|
||||
"github.com/getsentry/raven-go"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// reset is borrowed directly from the gin.
|
||||
const reset = "\033[0m"
|
||||
|
||||
// ErrorHandlerFunc will handle errors.
|
||||
type ErrorHandlerFunc func(recovery interface{}, c *gin.Context)
|
||||
|
||||
@ -36,12 +44,15 @@ type SentryTagged interface {
|
||||
|
||||
// Sentry struct. Holds SentryTaggedStruct list.
|
||||
type Sentry struct {
|
||||
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")
|
||||
}
|
||||
|
@ -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)))
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
@ -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
57
core/stacktrace/error.go
Normal 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())
|
||||
}
|
||||
}
|
51
core/stacktrace/error_test.go
Normal file
51
core/stacktrace/error_test.go
Normal 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")
|
||||
}
|
@ -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())
|
||||
}
|
@ -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)
|
||||
}
|
@ -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()
|
||||
}
|
@ -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))
|
||||
}
|
@ -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()
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
250
core/stacktrace/stack.go
Normal 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:]
|
||||
}
|
@ -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}}
|
||||
}
|
@ -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)
|
||||
}
|
114
core/stacktrace/stack_test.go
Normal file
114
core/stacktrace/stack_test.go
Normal 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)
|
||||
}
|
@ -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
|
||||
}
|
@ -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
6
go.mod
@ -8,7 +8,8 @@ require (
|
||||
github.com/aws/aws-sdk-go v1.36.30
|
||||
github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054 // indirect
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20190830225923-3302f0226fbd // indirect
|
||||
github.com/getsentry/raven-go v0.2.0
|
||||
github.com/getsentry/raven-go v0.2.0 // indirect
|
||||
github.com/getsentry/sentry-go v0.12.0
|
||||
github.com/gin-contrib/multitemplate v0.0.0-20190914010127-bba2ccfe37ec
|
||||
github.com/gin-gonic/gin v1.7.2
|
||||
github.com/go-playground/validator/v10 v10.8.0
|
||||
@ -21,7 +22,6 @@ require (
|
||||
github.com/json-iterator/go v1.1.11 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/lib/pq v1.9.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
||||
github.com/nicksnyder/go-i18n/v2 v2.0.2
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
@ -30,7 +30,7 @@ require (
|
||||
github.com/retailcrm/mg-transport-api-client-go v1.1.32
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/ugorji/go v1.2.6 // indirect
|
||||
golang.org/x/text v0.3.6
|
||||
golang.org/x/text v0.3.7
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
|
145
go.sum
145
go.sum
@ -31,19 +31,27 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||
github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
|
||||
github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo=
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFDnH08=
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
|
||||
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/aws/aws-sdk-go v1.36.30 h1:hAwyfe7eZa7sM+S5mIJZFiNFwJMia9Whz6CYblioLoU=
|
||||
github.com/aws/aws-sdk-go v1.36.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054 h1:uH66TXeswKn5PW5zdZ39xEwfS9an067BirqA+P4QaLI=
|
||||
@ -53,6 +61,11 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@ -61,18 +74,28 @@ github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f/go.mod h1:xN
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20190830225923-3302f0226fbd h1:DoaaxHqzWPQCWKSTmsi8UDSiFqxbfue+Xt+qi/BFKb8=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20190830225923-3302f0226fbd/go.mod h1:uU0N10vx1abI4qeVe79CxepBP6PPREVTgMS5Gx6/mOk=
|
||||
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
|
||||
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
|
||||
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
|
||||
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||
github.com/getsentry/sentry-go v0.12.0 h1:era7g0re5iY13bHSdN/xMkyV+5zZppjRVQhZrXCaEIk=
|
||||
github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c=
|
||||
github.com/gin-contrib/multitemplate v0.0.0-20190914010127-bba2ccfe37ec h1:mfeHJfPwsXp/iovjrTwxtkTMRAIXZu6Uxg6O95En1+Y=
|
||||
github.com/gin-contrib/multitemplate v0.0.0-20190914010127-bba2ccfe37ec/go.mod h1:2tmLQ8sVzr2XKwquGd7zNq3zB6fGyjJL+47JoxoF8yM=
|
||||
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||
@ -81,11 +104,15 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm
|
||||
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
|
||||
github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA=
|
||||
github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
|
||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
@ -99,9 +126,13 @@ github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@ -129,6 +160,7 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
@ -139,6 +171,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@ -153,19 +186,30 @@ github.com/google/pprof v0.0.0-20200507031123-427632fa3b1c/go.mod h1:ZgVRPoUq/hf
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ=
|
||||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
|
||||
github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
|
||||
github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
|
||||
github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
|
||||
github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
|
||||
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jinzhu/gorm v1.9.2/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo=
|
||||
@ -189,8 +233,18 @@ github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMW
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
|
||||
github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
|
||||
github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE=
|
||||
github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE=
|
||||
github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro=
|
||||
github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
@ -198,6 +252,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
|
||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
@ -205,21 +261,37 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
|
||||
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
|
||||
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
|
||||
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
|
||||
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
|
||||
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.0.2 h1:KsHGcTByIM0mHZKQGy0nlJLOjPNjQ6MVib/3PvsBDNY=
|
||||
@ -228,12 +300,18 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@ -252,8 +330,21 @@ github.com/retailcrm/api-client-go/v2 v2.0.3/go.mod h1:1yTZl9+gd3+/k0kAJe7sYvC+m
|
||||
github.com/retailcrm/mg-transport-api-client-go v1.1.32 h1:IBPltSoD5q2PPZJbNC/prK5F9rEVPXVx/ZzDpi7HKhs=
|
||||
github.com/retailcrm/mg-transport-api-client-go v1.1.32/go.mod h1:AWV6BueE28/6SCoyfKURTo4lF0oXYoOKmHTzehd5vAI=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@ -267,10 +358,25 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E=
|
||||
github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ=
|
||||
github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
|
||||
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
|
||||
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
|
||||
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
|
||||
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
@ -280,15 +386,20 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@ -323,16 +434,19 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@ -344,8 +458,10 @@ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20211008194852-3b03d305991f h1:1scJEYZBaF48BaG6tYbtxmLcXqwYGSfGcMoStTqkkIw=
|
||||
golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@ -363,6 +479,7 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -372,7 +489,9 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -391,27 +510,37 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac h1:oN6lz7iLW/YC7un8pq+9bOLyXrprv2+DKfkJY+2LJJw=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
@ -533,12 +662,16 @@ gopkg.in/gormigrate.v1 v1.6.0 h1:XpYM6RHQPmzwY7Uyu+t+xxMXc86JYFJn4nEc9HzQjsI=
|
||||
gopkg.in/gormigrate.v1 v1.6.0/go.mod h1:Lf00lQrHqfSYWiTtPcyQabsDdM6ejZaMgV0OU6JMSlw=
|
||||
gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
|
||||
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
|
||||
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
Loading…
Reference in New Issue
Block a user