Skip to content

Commit d7705c3

Browse files
authored
feat: improve sentry reports (#3190)
1 parent 8c177ff commit d7705c3

File tree

7 files changed

+123
-28
lines changed

7 files changed

+123
-28
lines changed

cmd/scw/main.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ var (
3333
BetaMode = os.Getenv(scw.ScwEnableBeta) == "true"
3434
)
3535

36+
// cleanup does the recover
37+
// If name change, must be reported in internal/sentry
3638
func cleanup(buildInfo *core.BuildInfo) {
3739
if err := recover(); err != nil {
3840
fmt.Println(sentry.ErrorBanner)
@@ -41,7 +43,7 @@ func cleanup(buildInfo *core.BuildInfo) {
4143

4244
// This will send an anonymous report on Scaleway's sentry.
4345
if buildInfo.IsRelease() {
44-
sentry.RecoverPanicAndSendReport(buildInfo, err)
46+
sentry.RecoverPanicAndSendReport(buildInfo.Tags(), buildInfo.Version.String(), err)
4547
}
4648
}
4749
}

go.mod

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ require (
1313
github.com/docker/docker v24.0.2+incompatible
1414
github.com/dustin/go-humanize v1.0.1
1515
github.com/fatih/color v1.15.0
16-
github.com/getsentry/raven-go v0.2.0
16+
github.com/getsentry/sentry-go v0.20.0
1717
github.com/ghodss/yaml v1.0.0
1818
github.com/gorilla/websocket v1.5.0
1919
github.com/hashicorp/go-version v1.6.0
@@ -72,7 +72,6 @@ require (
7272
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
7373
github.com/buildpacks/imgutil v0.0.0-20230428141433-24db5a78c900 // indirect
7474
github.com/buildpacks/lifecycle v0.17.0-pre.2 // indirect
75-
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect
7675
github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 // indirect
7776
github.com/cloudflare/circl v1.3.3 // indirect
7877
github.com/containerd/containerd v1.7.0 // indirect

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,6 @@ github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N
154154
github.com/c-bata/go-prompt v0.2.6 h1:POP+nrHE+DfLYx370bedwNhsqmpCUynWPxuHi0C5vZI=
155155
github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY=
156156
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
157-
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s=
158-
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
159157
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
160158
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
161159
github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
@@ -242,12 +240,13 @@ github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdk
242240
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
243241
github.com/gdamore/tcell/v2 v2.6.0 h1:OKbluoP9VYmJwZwq/iLb4BxwKcwGthaa1YNBJIyCySg=
244242
github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y=
245-
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
246-
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
243+
github.com/getsentry/sentry-go v0.20.0 h1:bwXW98iMRIWxn+4FgPW7vMrjmbym6HblXALmhjHmQaQ=
244+
github.com/getsentry/sentry-go v0.20.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
247245
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
248246
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
249247
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
250248
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
249+
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
251250
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
252251
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
253252
github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
@@ -472,6 +471,7 @@ github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M5
472471
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
473472
github.com/package-url/packageurl-go v0.1.1-0.20220428063043-89078438f170 h1:DiLBVp4DAcZlBVBEtJpNWZpZVq0AEeCY7Hqk8URVs4o=
474473
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
474+
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
475475
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
476476
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
477477
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

internal/core/build_info.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ func (b *BuildInfo) GetUserAgent() string {
5353
return userAgentPrefix
5454
}
5555

56+
func (b *BuildInfo) Tags() map[string]string {
57+
return map[string]string{
58+
"version": b.Version.String(),
59+
"go_arch": b.GoArch,
60+
"go_os": b.GoOS,
61+
"go_version": b.GoVersion,
62+
}
63+
}
64+
5665
func (b *BuildInfo) checkVersion(ctx context.Context) {
5766
if !b.IsRelease() || ExtractEnv(ctx, scwDisableCheckVersionEnv) == "true" {
5867
ExtractLogger(ctx).Debug("skipping check version")

internal/core/cobra_utils.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strings"
99

1010
"github.com/scaleway/scaleway-cli/v2/internal/args"
11+
"github.com/scaleway/scaleway-cli/v2/internal/sentry"
1112
"github.com/spf13/cobra"
1213
)
1314

@@ -19,6 +20,8 @@ func cobraRun(ctx context.Context, cmd *Command) func(*cobra.Command, []string)
1920
meta := extractMeta(ctx)
2021
meta.command = cmd
2122

23+
sentry.AddCommandContext(cmd.GetCommandLine("scw"))
24+
2225
// If command requires authentication and the client was not directly provided in the bootstrap config, we create a new client and overwrite the existing one
2326
if !cmd.AllowAnonymousClient && !meta.isClientFromBootstrapConfig {
2427
client, err := meta.Platform.CreateClient(meta.httpClient, ExtractConfigPath(ctx), ExtractProfileName(ctx))
@@ -100,6 +103,8 @@ func run(ctx context.Context, cobraCmd *cobra.Command, cmd *Command, rawArgs []s
100103
// unmarshalled arguments will be store in this interface
101104
cmdArgs := reflect.New(cmd.ArgsType).Interface()
102105

106+
sentry.AddArgumentsContext(args.SplitRaw(rawArgs))
107+
103108
// Unmarshal args.
104109
// After that we are done working with rawArgs
105110
// and will be working with cmdArgs.

internal/core/shell.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/c-bata/go-prompt"
1515
"github.com/scaleway/scaleway-cli/v2/internal/cache"
1616
"github.com/scaleway/scaleway-cli/v2/internal/interactive"
17+
"github.com/scaleway/scaleway-cli/v2/internal/sentry"
1718
"github.com/spf13/cobra"
1819
)
1920

@@ -255,6 +256,9 @@ func NewShellCompleter(ctx context.Context) *Completer {
255256
func shellExecutor(rootCmd *cobra.Command, printer *Printer, meta *meta) func(s string) {
256257
return func(s string) {
257258
args := strings.Fields(s)
259+
260+
sentry.AddCommandContext(strings.Join(removeOptions(args), " "))
261+
258262
rootCmd.SetArgs(meta.CliConfig.Alias.ResolveAliases(args))
259263

260264
err := rootCmd.Execute()

internal/sentry/sentry.go

Lines changed: 97 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ package sentry
22

33
import (
44
"fmt"
5+
"strings"
6+
"time"
57

6-
"github.com/getsentry/raven-go"
7-
"github.com/scaleway/scaleway-cli/v2/internal/core"
8+
"github.com/getsentry/sentry-go"
89
"github.com/scaleway/scaleway-sdk-go/logger"
910
)
1011

@@ -17,44 +18,119 @@ An error occurred, we are sorry, please consider opening a ticket on github usin
1718
Give us as many details as possible so we can reproduce the error and fix it.
1819
---------------------------------------------------------------------------------------`
1920

20-
// RecoverPanicAndSendReport will recover error if any, log them, and send them to sentry.
21-
// It must be called with the defer built-in.
22-
func RecoverPanicAndSendReport(buildInfo *core.BuildInfo, e interface{}) {
23-
sentryClient, err := newSentryClient(buildInfo)
21+
// RecoverPanicAndSendReport is to be called after recover.
22+
// It expects tags returned by core.BuildInfo and the recovered error
23+
func RecoverPanicAndSendReport(tags map[string]string, version string, e interface{}) {
24+
sentryHub, err := sentryHub(tags, version)
2425
if err != nil {
25-
logger.Debugf("cannot create sentry client: %s", err)
26+
logger.Debugf("cannot get sentry hub: %s", err)
2627
}
2728

2829
err, isError := e.(error)
2930
if isError {
30-
logAndSentry(sentryClient, err)
31+
logAndSentry(sentryHub, err)
3132
} else {
32-
logAndSentry(sentryClient, fmt.Errorf("unknownw error: %v", e))
33+
logAndSentry(sentryHub, fmt.Errorf("unknown error: %v", e))
3334
}
3435
}
3536

36-
func logAndSentry(sentryClient *raven.Client, err error) {
37+
func logAndSentry(sentryHub *sentry.Hub, err error) {
3738
logger.Errorf("%s", err)
38-
if sentryClient != nil {
39-
logger.Debugf("sending sentry report: %s", sentryClient.CaptureErrorAndWait(err, nil))
39+
if sentryHub != nil {
40+
event := sentryHub.Recover(err)
41+
if event == nil {
42+
logger.Debugf("failed to capture exception with sentry")
43+
return
44+
}
45+
logger.Debugf("sending sentry report: %s", *event)
46+
if !sentry.Flush(time.Second * 2) {
47+
logger.Debugf("failed to send report")
48+
}
4049
}
4150
}
4251

4352
// newSentryClient create a sentry client with build info tags.
44-
func newSentryClient(buildInfo *core.BuildInfo) (*raven.Client, error) {
45-
client, err := raven.New(dsn)
53+
func newSentryClient(version string) (*sentry.Client, error) {
54+
client, err := sentry.NewClient(sentry.ClientOptions{
55+
Dsn: dsn,
56+
AttachStacktrace: true,
57+
BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
58+
filterStackFrames(event)
59+
return event
60+
},
61+
Release: version,
62+
})
4663
if err != nil {
4764
return nil, err
4865
}
4966

50-
tagsContext := map[string]string{
51-
"version": buildInfo.Version.String(),
52-
"go_arch": buildInfo.GoArch,
53-
"go_os": buildInfo.GoOS,
54-
"go_version": buildInfo.GoVersion,
67+
return client, nil
68+
}
69+
70+
func configureSentryScope(tags map[string]string) {
71+
sentry.ConfigureScope(func(scope *sentry.Scope) {
72+
scope.SetTags(tags)
73+
})
74+
}
75+
76+
// AddCommandContext is used to pass executed command
77+
func AddCommandContext(line string) {
78+
sentry.ConfigureScope(func(scope *sentry.Scope) {
79+
scope.SetContext("command", map[string]interface{}{
80+
"line": line,
81+
})
82+
})
83+
}
84+
85+
func AddArgumentsContext(args [][2]string) {
86+
sentry.ConfigureScope(func(scope *sentry.Scope) {
87+
argMap := map[string]interface{}{}
88+
89+
for _, arg := range args {
90+
argMap[arg[0]] = len(arg[1])
91+
}
92+
93+
scope.SetContext("arguments", argMap)
94+
})
95+
}
96+
97+
func sentryHub(tags map[string]string, version string) (*sentry.Hub, error) {
98+
hub := sentry.CurrentHub()
99+
100+
if hub.Client() == nil {
101+
client, err := newSentryClient(version)
102+
if err != nil {
103+
return nil, fmt.Errorf("cannot create sentry client: %w", err)
104+
}
105+
hub.BindClient(client)
106+
configureSentryScope(tags)
55107
}
56108

57-
client.SetTagsContext(tagsContext)
109+
return hub, nil
110+
}
58111

59-
return client, nil
112+
// Filter the stack frames so that the top frame is the one causing panic.
113+
// On top of the culprit one there should be
114+
// - the deferred recover function
115+
// - The two functions called in this package
116+
func filterStackFrames(event *sentry.Event) {
117+
for _, e := range event.Exception {
118+
if e.Stacktrace == nil {
119+
continue
120+
}
121+
frames := e.Stacktrace.Frames[:0]
122+
for _, frame := range e.Stacktrace.Frames {
123+
if frame.Module == "main" && strings.HasPrefix(frame.Function, "cleanup") {
124+
continue
125+
}
126+
if frame.Module == "github.com/scaleway/scaleway-cli/v2/internal/sentry" && strings.HasPrefix(frame.Function, "RecoverPanicAndSendReport") {
127+
continue
128+
}
129+
if frame.Module == "github.com/scaleway/scaleway-cli/v2/internal/sentry" && strings.HasPrefix(frame.Function, "logAndSentry") {
130+
continue
131+
}
132+
frames = append(frames, frame)
133+
}
134+
e.Stacktrace.Frames = frames
135+
}
60136
}

0 commit comments

Comments
 (0)