Skip to content

Commit 9ca32cb

Browse files
authored
[sshshim] add sentry without using middleware (#366)
## Summary This PR is an alternative to both #361 and #363. This PR's approach is lighterweight: 1. refactor sentry code into its new `telemetry` package. 2. directly call that in the sshshim command execution function. The advantages it has are: 1. Major: The implementation in #363 is rather gross because we need to introduce an interface equivalent of cobra.Command. This is really ugly, and not very maintainable. If new middleware uses other functions of cobra commands, then we'll have to add those to the interface and also handle them in the sshshim-command version. 2. Minor: We keep the `midcobra.telemetry` middleware for the regular devbox command. Disadvantages are: 1. We may in the future need to incorporate new middleware into the sshshim command in a non-middleware manner. ## How was it tested? compiles. Tested via: 1. Look up sentryDSN in sentry dashboard > Settings > Project (devbox) > ClientKeys (DSN) 2. Inserted that in `internal/build/build.go` 3. Manually inserted errors in devbox.Open and invokeSSHOrSCPCommand. Ran devbox commands that triggered them. 4. verified in sentry dashboard that the errors were logged.
1 parent bd159a8 commit 9ca32cb

File tree

6 files changed

+143
-63
lines changed

6 files changed

+143
-63
lines changed

internal/boxcli/midcobra/midcobra.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ type Middleware interface {
2525
func New(cmd *cobra.Command) Executable {
2626
return &midcobraExecutable{
2727
cmd: cmd,
28-
executionID: executionID(),
28+
executionID: ExecutionID(),
2929
middlewares: []Middleware{},
3030
}
3131
}
@@ -75,7 +75,7 @@ func (ex *midcobraExecutable) Execute(ctx context.Context, args []string) int {
7575
}
7676
}
7777

78-
func executionID() string {
78+
func ExecutionID() string {
7979
// google/uuid package's String() returns a value of the form:
8080
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
8181
//

internal/boxcli/midcobra/telemetry.go

Lines changed: 9 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,13 @@ import (
99
"log"
1010
"os"
1111
"runtime"
12-
"strconv"
1312
"time"
1413

1514
"github.com/denisbrodbeck/machineid"
16-
"github.com/getsentry/sentry-go"
1715
segment "github.com/segmentio/analytics-go"
1816
"github.com/spf13/cobra"
1917
"go.jetpack.io/devbox"
18+
"go.jetpack.io/devbox/internal/telemetry"
2019
)
2120

2221
// We collect some light telemetry to be able to improve devbox over time.
@@ -26,14 +25,10 @@ import (
2625
// 2. Data is only stored in SOC 2 compliant systems, and we are SOC 2 compliant ourselves.
2726
// 3. Users should always have the ability to opt-out.
2827
func Telemetry(opts *TelemetryOpts) Middleware {
29-
doNotTrack, err := strconv.ParseBool(os.Getenv("DO_NOT_TRACK")) // https://consoledonottrack.com/
30-
if err != nil {
31-
doNotTrack = false
32-
}
3328

3429
return &telemetryMiddleware{
3530
opts: *opts,
36-
disabled: doNotTrack || opts.TelemetryKey == "" || opts.SentryDSN == "",
31+
disabled: telemetry.DoNotTrack() || opts.TelemetryKey == "" || opts.SentryDSN == "",
3732
}
3833
}
3934

@@ -43,6 +38,7 @@ type TelemetryOpts struct {
4338
SentryDSN string // used by error reporting
4439
TelemetryKey string
4540
}
41+
4642
type telemetryMiddleware struct {
4743
// Setup:
4844
opts TelemetryOpts
@@ -65,7 +61,9 @@ func (m *telemetryMiddleware) postRun(cmd *cobra.Command, args []string, runErr
6561
if m.disabled {
6662
return
6763
}
68-
initSentry(m.opts, m.executionID)
64+
65+
sentry := telemetry.NewSentry(m.opts.SentryDSN)
66+
sentry.Init(m.opts.AppName, m.opts.AppVersion, m.executionID)
6967
segmentClient, _ := segment.NewWithConfig(m.opts.TelemetryKey, segment.Config{
7068
BatchSize: 1, /* no batching */
7169
// Discard logs:
@@ -82,13 +80,12 @@ func (m *telemetryMiddleware) postRun(cmd *cobra.Command, args []string, runErr
8280
return // Ignore invalid commands
8381
}
8482

83+
// verified with manual testing that the sentryID returned by CaptureException
84+
// is the same as m.ExecutionID, since we set EventID = m.ExecutionID in sentry.Init
85+
sentry.CaptureException(runErr)
8586
var sentryEventID string
8687
if runErr != nil {
87-
defer sentry.Flush(2 * time.Second)
88-
_ /*eventIDPointer*/ = sentry.CaptureException(runErr)
8988
sentryEventID = m.executionID
90-
// verified with manual testing that the sentryID returned by CaptureException
91-
// is the same as m.executionID, since we set EventID = m.executionID in initSentry()
9289
}
9390

9491
trackEvent(segmentClient, &event{
@@ -109,32 +106,6 @@ func (m *telemetryMiddleware) withExecutionID(execID string) Middleware {
109106
return m
110107
}
111108

112-
func initSentry(opts TelemetryOpts, executionID string) {
113-
sentrySyncTransport := sentry.NewHTTPSyncTransport()
114-
sentrySyncTransport.Timeout = time.Second * 2
115-
release := opts.AppName + "@" + opts.AppVersion
116-
environment := "production"
117-
if opts.AppVersion == "0.0.0-dev" {
118-
environment = "development"
119-
}
120-
121-
_ = sentry.Init(sentry.ClientOptions{
122-
Dsn: opts.SentryDSN,
123-
Environment: environment,
124-
Release: release,
125-
Transport: sentrySyncTransport,
126-
TracesSampleRate: 1,
127-
BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
128-
for i := range event.Exception {
129-
// edit in place and remove error message from tracking
130-
event.Exception[i].Value = ""
131-
}
132-
event.EventID = sentry.EventID(executionID)
133-
return event
134-
},
135-
})
136-
}
137-
138109
func deviceID() string {
139110
salt := "64ee464f-9450-4b14-8d9c-014c0012ac1a"
140111
hashedID, _ := machineid.ProtectedID(salt) // Ensure machine id is hashed and non-identifiable

internal/boxcli/root.go

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package boxcli
55

66
import (
77
"context"
8-
"fmt"
98
"os"
109
"strings"
1110

@@ -61,31 +60,10 @@ func Execute(ctx context.Context, args []string) int {
6160
return exe.Execute(ctx, args)
6261
}
6362

64-
// TODO savil. Add Sentry and other monitoring.
65-
func executeSSH() int {
66-
sshshim.EnableDebug() // Always enable for now.
67-
debug.Log("os.Args: %v", os.Args)
68-
69-
if alive, err := sshshim.EnsureLiveVMOrTerminateMutagenSessions(os.Args[1:]); err != nil {
70-
debug.Log("EnsureLiveVMOrTerminateMutagenSessions error: %v", err)
71-
fmt.Fprintf(os.Stderr, "%v", err)
72-
return 1
73-
} else if !alive {
74-
return 0
75-
}
76-
77-
if err := sshshim.InvokeSSHOrSCPCommand(os.Args); err != nil {
78-
debug.Log("InvokeSSHorSCPCommand error: %v", err)
79-
fmt.Fprintf(os.Stderr, "%v", err)
80-
return 1
81-
}
82-
return 0
83-
}
84-
8563
func Main() {
8664
if strings.HasSuffix(os.Args[0], "ssh") ||
8765
strings.HasSuffix(os.Args[0], "scp") {
88-
code := executeSSH()
66+
code := sshshim.Execute(context.Background(), os.Args)
8967
os.Exit(code)
9068
}
9169
code := Execute(context.Background(), os.Args[1:])
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package sshshim
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
8+
"go.jetpack.io/devbox/internal/boxcli/midcobra"
9+
"go.jetpack.io/devbox/internal/build"
10+
"go.jetpack.io/devbox/internal/debug"
11+
"go.jetpack.io/devbox/internal/telemetry"
12+
)
13+
14+
func Execute(ctx context.Context, args []string) int {
15+
defer debug.Recover()
16+
17+
err := execute(args)
18+
19+
logSentry(err)
20+
21+
if err != nil {
22+
return 1
23+
}
24+
return 0
25+
}
26+
27+
func execute(args []string) error {
28+
EnableDebug() // Always enable for now.
29+
debug.Log("os.Args: %v", args)
30+
31+
if alive, err := EnsureLiveVMOrTerminateMutagenSessions(args[1:]); err != nil {
32+
debug.Log("ensureLiveVMOrTerminateMutagenSessions error: %v", err)
33+
fmt.Fprintf(os.Stderr, "%v", err)
34+
return err
35+
} else if !alive {
36+
return nil
37+
}
38+
39+
if err := InvokeSSHOrSCPCommand(args); err != nil {
40+
debug.Log("InvokeSSHorSCPCommand error: %v", err)
41+
fmt.Fprintf(os.Stderr, "%v", err)
42+
return err
43+
}
44+
return nil
45+
}
46+
47+
func logSentry(runErr error) {
48+
const appName = "devbox-sshshim"
49+
s := telemetry.NewSentry(build.SentryDSN)
50+
s.Init(appName, build.Version, midcobra.ExecutionID())
51+
s.CaptureException(runErr)
52+
}

internal/telemetry/sentry.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package telemetry
2+
3+
import (
4+
"time"
5+
6+
"github.com/getsentry/sentry-go"
7+
)
8+
9+
type Sentry struct {
10+
disabled bool
11+
12+
dsn string
13+
}
14+
15+
func NewSentry(dsn string) *Sentry {
16+
return &Sentry{
17+
disabled: DoNotTrack() || dsn == "",
18+
dsn: dsn,
19+
}
20+
}
21+
22+
func (s *Sentry) Init(appName, appVersion, executionID string) {
23+
if s.disabled {
24+
return
25+
}
26+
27+
sentrySyncTransport := sentry.NewHTTPSyncTransport()
28+
sentrySyncTransport.Timeout = time.Second * 2
29+
release := appName + "@" + appVersion
30+
environment := "production"
31+
if appVersion == "0.0.0-dev" {
32+
environment = "development"
33+
}
34+
35+
_ = sentry.Init(sentry.ClientOptions{
36+
Dsn: s.dsn,
37+
Environment: environment,
38+
Release: release,
39+
Transport: sentrySyncTransport,
40+
TracesSampleRate: 1,
41+
BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
42+
for i := range event.Exception {
43+
// edit in place and remove error message from tracking
44+
event.Exception[i].Value = ""
45+
}
46+
event.EventID = sentry.EventID(executionID)
47+
return event
48+
},
49+
})
50+
}
51+
52+
// CaptureException
53+
func (s *Sentry) CaptureException(runErr error) string {
54+
if s.disabled || runErr == nil {
55+
return ""
56+
}
57+
defer sentry.Flush(2 * time.Second)
58+
59+
eventIDPointer := sentry.CaptureException(runErr)
60+
if eventIDPointer == nil {
61+
return ""
62+
}
63+
return string(*eventIDPointer)
64+
}

internal/telemetry/telemetry.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package telemetry
2+
3+
import (
4+
"os"
5+
"strconv"
6+
)
7+
8+
func DoNotTrack() bool {
9+
// https://consoledonottrack.com/
10+
doNotTrack_, err := strconv.ParseBool(os.Getenv("DO_NOT_TRACK"))
11+
if err != nil {
12+
doNotTrack_ = false
13+
}
14+
return doNotTrack_
15+
}

0 commit comments

Comments
 (0)