diff --git a/changelog.md b/changelog.md index a5c6975228..340d02b30b 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,7 @@ ### Changes - [#4357](https://github.com/ignite/cli/pull/4357) Bump chain dependencies (store, ics, log, etc) +- [#4328](https://github.com/ignite/cli/pull/4328) Send ignite bug report to sentry. Opt out the same way as for usage analytics ## [`v28.5.2`](https://github.com/ignite/cli/releases/tag/v28.5.2) diff --git a/go.mod b/go.mod index 8af22cdea6..a18ec491e7 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,7 @@ require ( github.com/cosmos/gogoproto v1.7.0 github.com/emicklei/proto v1.12.2 github.com/emicklei/proto-contrib v0.15.0 + github.com/getsentry/sentry-go v0.27.0 github.com/go-delve/delve v1.21.0 github.com/go-git/go-git/v5 v5.12.0 github.com/go-openapi/analysis v0.23.0 @@ -222,10 +223,9 @@ require ( github.com/firefart/nonamedreturns v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/getsentry/sentry-go v0.27.0 // indirect - github.com/ghostiam/protogetter v0.3.5 // indirect + github.com/ghostiam/protogetter v0.3.6 // indirect github.com/go-chi/chi/v5 v5.0.13 // indirect - github.com/go-critic/go-critic v0.11.2 // indirect + github.com/go-critic/go-critic v0.11.4 // indirect github.com/go-delve/liner v1.2.3-0.20231231155935-4726ab1d7f62 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect diff --git a/go.sum b/go.sum index ef6da055fa..f42b8385d2 100644 --- a/go.sum +++ b/go.sum @@ -488,8 +488,8 @@ github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlya github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghostiam/protogetter v0.3.5 h1:+f7UiF8XNd4w3a//4DnusQ2SZjPkUjxkMEfjbxOK4Ug= -github.com/ghostiam/protogetter v0.3.5/go.mod h1:7lpeDnEJ1ZjL/YtyoN99ljO4z0pd3H0d18/t2dPBxHw= +github.com/ghostiam/protogetter v0.3.6 h1:R7qEWaSgFCsy20yYHNIJsU9ZOb8TziSRRxuAOTVKeOk= +github.com/ghostiam/protogetter v0.3.6/go.mod h1:7lpeDnEJ1ZjL/YtyoN99ljO4z0pd3H0d18/t2dPBxHw= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.7.0 h1:jGB9xAJQ12AIGNB4HguylppmDK1Am9ppF7XnGXXJuoU= @@ -498,8 +498,8 @@ github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= github.com/go-chi/chi/v5 v5.0.13 h1:JlH2F2M8qnwl0N1+JFFzlX9TlKJYas3aPXdiuTmJL+w= github.com/go-chi/chi/v5 v5.0.13/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-critic/go-critic v0.11.2 h1:81xH/2muBphEgPtcwH1p6QD+KzXl2tMSi3hXjBSxDnM= -github.com/go-critic/go-critic v0.11.2/go.mod h1:OePaicfjsf+KPy33yq4gzv6CO7TEQ9Rom6ns1KsJnl8= +github.com/go-critic/go-critic v0.11.4 h1:O7kGOCx0NDIni4czrkRIXTnit0mkyKOCePh3My6OyEU= +github.com/go-critic/go-critic v0.11.4/go.mod h1:2QAdo4iuLik5S9YG0rT4wcZ8QxwHYkrr6/2MWAiv/vc= github.com/go-delve/delve v1.21.0 h1:npcc8TZhdVxaMSJon+zqcE3bXM/ck8SSOOWw/id13jI= github.com/go-delve/delve v1.21.0/go.mod h1:U+OAdfhewudkHsVs/AwhfpSBu7t/NgIXH3+my4T5q78= github.com/go-delve/liner v1.2.3-0.20220127212407-d32d89dd2a5d/go.mod h1:biJCRbqp51wS+I92HMqn5H8/A0PAhxn2vyOT+JqhiGI= diff --git a/ignite/cmd/ignite/main.go b/ignite/cmd/ignite/main.go index 9a6acd8a1f..df414111f5 100644 --- a/ignite/cmd/ignite/main.go +++ b/ignite/cmd/ignite/main.go @@ -19,12 +19,13 @@ import ( "github.com/ignite/cli/v28/ignite/pkg/xstrings" ) +const exitCodeOK, exitCodeError = 0, 1 + func main() { os.Exit(run()) } func run() int { - const exitCodeOK, exitCodeError = 0, 1 ctx := clictx.From(context.Background()) cmd, cleanUp, err := ignitecmd.New(ctx) if err != nil { @@ -41,6 +42,7 @@ func run() int { } var wg sync.WaitGroup analytics.SendMetric(&wg, subCmd) + analytics.EnableSentry(&wg, ctx) err = cmd.ExecuteContext(ctx) if err != nil { @@ -77,7 +79,8 @@ func run() int { return exitCodeError } - wg.Wait() // waits for all metrics to be sent + // waits for analytics to finish + wg.Wait() return exitCodeOK } diff --git a/ignite/internal/analytics/analytics.go b/ignite/internal/analytics/analytics.go index 4b5936e26d..91108368d9 100644 --- a/ignite/internal/analytics/analytics.go +++ b/ignite/internal/analytics/analytics.go @@ -16,6 +16,7 @@ import ( "github.com/ignite/cli/v28/ignite/pkg/gitpod" "github.com/ignite/cli/v28/ignite/pkg/matomo" "github.com/ignite/cli/v28/ignite/pkg/randstr" + "github.com/ignite/cli/v28/ignite/pkg/sentry" "github.com/ignite/cli/v28/ignite/version" ) @@ -99,6 +100,23 @@ func SendMetric(wg *sync.WaitGroup, cmd *cobra.Command) { }() } +// EnableSentry enable errors reporting to Sentry. +func EnableSentry(wg *sync.WaitGroup, ctx context.Context) { + dntInfo, err := checkDNT() + if err != nil || dntInfo.DoNotTrack { + return + } + + closeSentry, err := sentry.InitSentry(ctx) + wg.Add(1) + go func() { + defer wg.Done() + if err == nil { + defer closeSentry() + } + }() +} + // checkDNT check if the user allow to track data or if the DO_NOT_TRACK // env var is set https://consoledonottrack.com/ func checkDNT() (anonIdentity, error) { diff --git a/ignite/pkg/errors/xerrors.go b/ignite/pkg/errors/xerrors.go index a09391020a..bf7815d6af 100644 --- a/ignite/pkg/errors/xerrors.go +++ b/ignite/pkg/errors/xerrors.go @@ -14,23 +14,45 @@ package errors import ( "github.com/cockroachdb/errors" + "github.com/getsentry/sentry-go" ) // New creates an error with a simple error message. // A stack trace is retained. -func New(msg string) error { return errors.New(msg) } +func New(msg string) error { + err := errors.New(msg) + sentry.CaptureException(err) + return err +} // Errorf aliases Newf(). -func Errorf(format string, args ...interface{}) error { return errors.Errorf(format, args...) } +func Errorf(format string, args ...interface{}) error { + err := errors.Errorf(format, args...) + sentry.CaptureException(err) + return err +} + +// WithStack annotates err with a stack trace at the point WithStack was called. +func WithStack(err error) error { + errWithStack := errors.WithStack(err) + sentry.CaptureException(errWithStack) + return errWithStack +} // Wrap wraps an error with a message prefix. A stack trace is retained. -func Wrap(err error, msg string) error { return errors.Wrap(err, msg) } +func Wrap(err error, msg string) error { + errWrap := errors.Wrap(err, msg) + sentry.CaptureException(errWrap) + return errWrap +} // Wrapf wraps an error with a formatted message prefix. A stack // trace is also retained. If the format is empty, no prefix is added, // but the extra arguments are still processed for reportable strings. func Wrapf(err error, format string, args ...interface{}) error { - return errors.Wrapf(err, format, args...) + errWrap := errors.Wrapf(err, format, args...) + sentry.CaptureException(errWrap) + return errWrap } // Unwrap accesses the direct cause of the error if any, otherwise @@ -52,6 +74,3 @@ func Is(err, reference error) bool { return errors.Is(err, reference) } // As(interface{}) bool such that As(target) returns true. As will panic if target // is not a non-nil pointer to a type which implements error or is of interface type. func As(err error, target interface{}) bool { return errors.As(err, target) } - -// WithStack annotates err with a stack trace at the point WithStack was called. -func WithStack(err error) error { return errors.WithStack(err) } diff --git a/ignite/pkg/sentry/sentry.go b/ignite/pkg/sentry/sentry.go new file mode 100644 index 0000000000..ec140410df --- /dev/null +++ b/ignite/pkg/sentry/sentry.go @@ -0,0 +1,48 @@ +package sentry + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/getsentry/sentry-go" + + "github.com/ignite/cli/v28/ignite/pkg/errors" + "github.com/ignite/cli/v28/ignite/version" +) + +const IgniteDNS = "https://bugs.ignite.com" + +func InitSentry(ctx context.Context) (deferMe func(), err error) { + sentrySyncTransport := sentry.NewHTTPSyncTransport() + sentrySyncTransport.Timeout = time.Second * 3 + + igniteInfo, err := version.GetInfo(ctx) + if err != nil { + return nil, errors.Errorf("failed to init sentry: %w", err) + } + + if err := sentry.Init(sentry.ClientOptions{ + Dsn: IgniteDNS, + Transport: sentrySyncTransport, + Environment: getEnvironment(igniteInfo.CLIVersion), + Release: fmt.Sprintf("ignite@%s", igniteInfo.CLIVersion), + SampleRate: 1.0, // get all events + }); err != nil { + return nil, errors.Errorf("failed to init sentry: %w", err) + } + + return func() { + sentry.Recover() + sentry.Flush(time.Second * 2) + }, nil +} + +func getEnvironment(igniteVersion string) string { + if strings.Contains(igniteVersion, "dev") { + return "development" + } + + return "production" +}