Skip to content

Commit

Permalink
Add telemetry
Browse files Browse the repository at this point in the history
  • Loading branch information
csweichel committed Nov 7, 2023
1 parent 64e2c68 commit 397903e
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 2 deletions.
43 changes: 43 additions & 0 deletions components/local-app/cmd/login_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) 2023 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License.AGPL.txt in the project root for license information.

package cmd

import (
"context"
"fmt"
"net/http"
"testing"

"github.com/bufbuild/connect-go"
v1 "github.com/gitpod-io/gitpod/components/public-api/go/experimental/v1"
gitpod_experimental_v1connect "github.com/gitpod-io/gitpod/components/public-api/go/experimental/v1/v1connect"
"github.com/gitpod-io/local-app/pkg/config"
)

func TestLoginCmd(t *testing.T) {
RunCommandTests(t, []CommandTest{
{
Name: "test unauthenticated",
Commandline: []string{"login", "--token", "foo"},
Config: &config.Config{
ActiveContext: "test",
},
PrepServer: func(mux *http.ServeMux) {
mux.Handle(gitpod_experimental_v1connect.NewTeamsServiceHandler(&testLoginCmdSrv{
Err: connect.NewError(connect.CodeUnauthenticated, fmt.Errorf("unauthenticated")),
}))
},
},
})
}

type testLoginCmdSrv struct {
Err error
gitpod_experimental_v1connect.UnimplementedTeamsServiceHandler
}

func (srv testLoginCmdSrv) ListTeams(context.Context, *connect.Request[v1.ListTeamsRequest]) (*connect.Response[v1.ListTeamsResponse], error) {
return nil, srv.Err
}
16 changes: 14 additions & 2 deletions components/local-app/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import (
"github.com/gitpod-io/gitpod/components/public-api/go/client"
"github.com/gitpod-io/local-app/pkg/auth"
"github.com/gitpod-io/local-app/pkg/config"
"github.com/gitpod-io/local-app/pkg/constants"
"github.com/gitpod-io/local-app/pkg/prettyprint"
"github.com/gitpod-io/local-app/pkg/telemetry"
"github.com/gookit/color"
"github.com/lmittmann/tint"
"github.com/mattn/go-isatty"
Expand Down Expand Up @@ -88,17 +90,27 @@ var rootCmd = &cobra.Command{
return err
}
cmd.SetContext(config.ToContext(context.Background(), cfg))

telemetry.Init(!telemetry.DoNotTrack() && cfg.Telemetry.Enabled, cfg.Telemetry.Identity, constants.Version)
telemetry.RecordCommand(cmd)

return nil
},
}

func Execute() {
err := rootCmd.Execute()

var exitCode int
if err != nil {
exitCode = 1
prettyprint.PrintError(os.Stderr, os.Args[0], err)

os.Exit(1)
telemetry.RecordError(err)
}

telemetry.Close()
os.Exit(exitCode)
}

func init() {
Expand Down Expand Up @@ -141,7 +153,7 @@ func getGitpodClient(ctx context.Context) (*client.Gitpod, error) {
)
}

if host.String() == "https://testing" && rootTestingOpts.Client != nil {
if rootTestingOpts.Client != nil {
return rootTestingOpts.Client, nil
}

Expand Down
126 changes: 126 additions & 0 deletions components/local-app/pkg/telemetry/telemetry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.

package telemetry

import (
"errors"
"log/slog"
"math/rand"
"os"
"runtime"
"strings"
"time"

"github.com/gitpod-io/local-app/pkg/prettyprint"
segment "github.com/segmentio/analytics-go/v3"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
)

// Injected at build time
var segmentKey = "TgiJIVvFsBGwmxbnnt5NeeDaian9nr3n"

var opts struct {
Enabled bool
Identity string
Version string

client segment.Client
}

// Init initialises the telemetry
func Init(enabled bool, identity, version string) {
opts.Enabled = enabled
if !enabled {
return
}

opts.Version = version
opts.Identity = identity

if segmentKey != "" {
opts.client = segment.New(segmentKey)
}
}

// DoNotTrack returns true if the user opted out of telemetry
// Implements the https://consoledonottrack.com/ proposal.
func DoNotTrack() bool {
return os.Getenv("DO_NOT_TRACK") == "1"
}

// RandomIdentity generates a random identity
func RandomIdentity() string {
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
b := make([]rune, 32)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}

func Close() {
if opts.client != nil {
opts.client.Close()
}
}

// Identity returns the identity
func Identity() string {
return opts.Identity
}

// Enabled returns true if the telemetry is enabled
func Enabled() bool {
return opts.Enabled && opts.Identity != "" && opts.client != nil
}

func track(event string, props segment.Properties) {
if !Enabled() {
return
}
slog.Debug("tracking telemetry", "props", props, "event", event)

err := opts.client.Enqueue(segment.Track{
AnonymousId: opts.Identity,
Event: event,
Timestamp: time.Now(),
Properties: props,
})
if err != nil {
slog.Debug("failed to track telemetry", "err", err)
}
}

// RecordCommand records the execution of a CLI command
func RecordCommand(cmd *cobra.Command) {
var command []string
for c := cmd; c != nil; c = c.Parent() {
command = append(command, c.Name())
}
slices.Reverse(command)

track("gitpodcli_command", defaultProperties().
Set("command", strings.Join(command, " ")))
}

// RecordError records an exception that occurred
func RecordError(err error) {
var exception *prettyprint.ErrSystemException
if !errors.As(err, &exception) {
return
}

track("gitpodcli_exception", defaultProperties().
Set("context", exception.Context).
Set("error", exception.Err.Error()))
}

func defaultProperties() segment.Properties {
return segment.NewProperties().
Set("goos", runtime.GOOS).
Set("goarch", runtime.GOARCH).
Set("version", opts.Version)
}

0 comments on commit 397903e

Please sign in to comment.