From 0adabfdc1c8f31685276fe5fb23a71f142284ef2 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Fri, 13 Sep 2024 15:02:11 +0200 Subject: [PATCH] feat: report bug to sentry (#4328) --- changelog.md | 1 + go.mod | 2 +- ignite/cmd/ignite/main.go | 7 ++-- ignite/internal/analytics/analytics.go | 18 ++++++++++ ignite/pkg/errors/xerrors.go | 33 ++++++++++++++---- ignite/pkg/sentry/sentry.go | 48 ++++++++++++++++++++++++++ 6 files changed, 99 insertions(+), 10 deletions(-) create mode 100644 ignite/pkg/sentry/sentry.go diff --git a/changelog.md b/changelog.md index 9d44908110..d4050f4590 100644 --- a/changelog.md +++ b/changelog.md @@ -40,6 +40,7 @@ - [#4290](https://github.com/ignite/cli/pull/4290) Remove ignite ics logic from ignite cli (this functionality will be in the `consumer` app) - [#4295](https://github.com/ignite/cli/pull/4295) Stop scaffolding `pulsar` files - [#4317](https://github.com/ignite/cli/pull/4317) Remove xchisel dependency +- [#4328](https://github.com/ignite/cli/pull/4328) Send ignite bug report to sentry. Opt out the same way as for usage analytics ### Fixes diff --git a/go.mod b/go.mod index af94c1a938..043ad2d31f 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,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 @@ -214,7 +215,6 @@ require ( github.com/firefart/nonamedreturns v1.0.5 // 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.6 // indirect github.com/go-chi/chi/v5 v5.0.12 // indirect github.com/go-critic/go-critic v0.11.4 // indirect diff --git a/ignite/cmd/ignite/main.go b/ignite/cmd/ignite/main.go index 3b5236bb20..dfeb9a600e 100644 --- a/ignite/cmd/ignite/main.go +++ b/ignite/cmd/ignite/main.go @@ -19,12 +19,13 @@ import ( "github.com/ignite/cli/v29/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 e60bfe7c62..131c895f09 100644 --- a/ignite/internal/analytics/analytics.go +++ b/ignite/internal/analytics/analytics.go @@ -16,6 +16,7 @@ import ( "github.com/ignite/cli/v29/ignite/pkg/gitpod" "github.com/ignite/cli/v29/ignite/pkg/matomo" "github.com/ignite/cli/v29/ignite/pkg/randstr" + "github.com/ignite/cli/v29/ignite/pkg/sentry" "github.com/ignite/cli/v29/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 0ec5ce9728..66ba2c074e 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..3189324291 --- /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/v29/ignite/pkg/errors" + "github.com/ignite/cli/v29/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" +}