Path: blob/main/components/local-app/pkg/telemetry/telemetry.go
2500 views
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License-AGPL.txt in the project root for license information.34package telemetry56import (7"crypto/sha256"8"errors"9"fmt"10"io"11"log"12"log/slog"13"math/rand"14"net"15"os"16"runtime"17"strings"18"time"1920"github.com/gitpod-io/local-app/pkg/prettyprint"21segment "github.com/segmentio/analytics-go/v3"22"github.com/spf13/cobra"23"golang.org/x/exp/slices"24)2526var opts struct {27Enabled bool28Identity string29Version string3031client segment.Client32}3334// Init initializes the telemetry35func Init(enabled bool, identity, version string, logLevel slog.Level, host string) {36opts.Enabled = enabled37if !enabled {38return39}4041opts.Version = version42opts.Identity = identity4344var logger segment.Logger45if logLevel == slog.LevelDebug {46logger = segment.StdLogger(log.New(os.Stderr, "telemetry ", log.LstdFlags))47} else {48// we don't want to log anything49log := log.New(os.Stderr, "telemetry ", log.LstdFlags)50log.SetOutput(io.Discard)51logger = segment.StdLogger(log)52}53opts.client, _ = segment.NewWithConfig("untrusted-dummy-key", segment.Config{54Logger: logger,55Endpoint: host + "/analytics",56})57}5859// DoNotTrack returns true if the user opted out of telemetry60// Implements the https://consoledonottrack.com/ proposal.61func DoNotTrack() bool {62return os.Getenv("DO_NOT_TRACK") == "1"63}6465// GenerateIdentity generates an identity using the machine's MAC address or a random value66func GenerateIdentity() string {67var addr string68interfaces, err := net.Interfaces()69if err == nil {70for _, i := range interfaces {71addr = i.HardwareAddr.String()72if i.Flags&net.FlagUp != 0 && addr != "" {73break74}75}76}77if addr == "" {78letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")79b := make([]rune, 32)80for i := range b {81b[i] = letters[rand.Intn(len(letters))]82}83addr = string(b)84}85return fmt.Sprintf("%x", sha256.Sum256([]byte(addr)))86}8788func Close() {89if opts.client != nil {90opts.client.Close()91}92}9394// Identity returns the identity95func Identity() string {96return opts.Identity97}9899// Enabled returns true if the telemetry is enabled100func Enabled() bool {101return opts.Enabled && opts.Identity != "" && opts.client != nil102}103104func track(event string, props segment.Properties) {105if !Enabled() {106return107}108slog.Debug("tracking telemetry", "props", props, "event", event, "identity", opts.Identity)109110err := opts.client.Enqueue(segment.Track{111AnonymousId: opts.Identity,112Event: event,113Timestamp: time.Now(),114Properties: props,115})116if err != nil {117slog.Debug("failed to track telemetry", "err", err)118}119}120121// RecordCommand records the execution of a CLI command122func RecordCommand(cmd *cobra.Command) {123var command []string124for c := cmd; c != nil; c = c.Parent() {125command = append(command, c.Name())126}127slices.Reverse(command)128129track("gitpodcli_command", defaultProperties().130Set("command", strings.Join(command, " ")))131}132133// RecordError records an exception that occurred134func RecordError(err error) {135var exception *prettyprint.ErrSystemException136if !errors.As(err, &exception) {137return138}139140track("gitpodcli_exception", defaultProperties().141Set("context", exception.Context).142Set("error", exception.Err.Error()))143}144145func defaultProperties() segment.Properties {146return segment.NewProperties().147Set("goos", runtime.GOOS).148Set("goarch", runtime.GOARCH).149Set("version", opts.Version)150}151152153