From b566825d961a788b5c1ce4aead43f8f90b019299 Mon Sep 17 00:00:00 2001 From: Apoorv Deshmukh Date: Tue, 2 Jan 2024 20:52:54 +0530 Subject: [PATCH 1/3] Add initial telelmetry skeleton --- cmd/modern/main.go | 6 ++- cmd/sqlcmd/sqlcmd.go | 13 +++--- go.mod | 3 ++ go.sum | 16 +++++++ internal/telemetry/appinsights.go | 75 +++++++++++++++++++++++++++++++ pkg/sqlcmd/sqlcmd.go | 3 +- 6 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 internal/telemetry/appinsights.go diff --git a/cmd/modern/main.go b/cmd/modern/main.go index 4b358b48..f6397cda 100644 --- a/cmd/modern/main.go +++ b/cmd/modern/main.go @@ -12,6 +12,8 @@ package main import ( + "path" + "github.com/microsoft/go-sqlcmd/internal" "github.com/microsoft/go-sqlcmd/internal/cmdparser" "github.com/microsoft/go-sqlcmd/internal/cmdparser/dependency" @@ -20,9 +22,9 @@ import ( "github.com/microsoft/go-sqlcmd/internal/output" "github.com/microsoft/go-sqlcmd/internal/output/verbosity" "github.com/microsoft/go-sqlcmd/internal/pal" + "github.com/microsoft/go-sqlcmd/internal/telemetry" "github.com/microsoft/go-sqlcmd/pkg/sqlcmd" "github.com/spf13/cobra" - "path" "os" @@ -38,6 +40,8 @@ var version = "local-build" // overridden in pipeline builds with: -ldflags="-X // If the first argument is a modern CLI subcommand, the modern CLI is // executed. Otherwise, the legacy CLI is executed. func main() { + telemetry.InitializeAppInsights() + telemetry.TrackEvent("sqlcmd.version", map[string]string{"version": version}) dependencies := dependency.Options{ Output: output.New(output.Options{ StandardWriter: os.Stdout, diff --git a/cmd/sqlcmd/sqlcmd.go b/cmd/sqlcmd/sqlcmd.go index 1b9d7b20..973a3fb6 100644 --- a/cmd/sqlcmd/sqlcmd.go +++ b/cmd/sqlcmd/sqlcmd.go @@ -19,6 +19,7 @@ import ( "github.com/microsoft/go-mssqldb/azuread" "github.com/microsoft/go-mssqldb/msdsn" "github.com/microsoft/go-sqlcmd/internal/localizer" + "github.com/microsoft/go-sqlcmd/internal/telemetry" "github.com/microsoft/go-sqlcmd/pkg/console" "github.com/microsoft/go-sqlcmd/pkg/sqlcmd" "github.com/spf13/cobra" @@ -221,11 +222,11 @@ func Execute(version string) { fmt.Println(localizer.Sprintf("Servers:")) } listLocalServers() - os.Exit(0) + telemetry.CloseTelemetryAndExit(0) } if len(argss) > 0 { fmt.Printf("%s'%s': Unknown command. Enter '--help' for command help.", sqlcmdErrorPrefix, argss[0]) - os.Exit(1) + telemetry.CloseTelemetryAndExit(1) } vars := sqlcmd.InitializeVariables(args.useEnvVars()) @@ -238,16 +239,16 @@ func Execute(version string) { fmt.Println() fmt.Println(localizer.Sprintf("Legal docs and information: aka.ms/SqlcmdLegal")) fmt.Println(localizer.Sprintf("Third party notices: aka.ms/SqlcmdNotices")) - os.Exit(0) + telemetry.CloseTelemetryAndExit(0) } if args.Help { fmt.Print(cmd.UsageString()) - os.Exit(0) + telemetry.CloseTelemetryAndExit(0) } exitCode, _ := run(vars, &args) - os.Exit(exitCode) + telemetry.CloseTelemetryAndExit(exitCode) }, } @@ -271,7 +272,7 @@ func Execute(version string) { }) rootCmd.SetArgs(convertOsArgs(os.Args[1:])) if err := rootCmd.Execute(); err != nil { - os.Exit(1) + telemetry.CloseTelemetryAndExit(1) } } diff --git a/go.mod b/go.mod index 48c347e6..95b7d08f 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/docker/go-connections v0.4.0 github.com/golang-sql/sqlexp v0.1.0 github.com/google/uuid v1.3.0 + github.com/microsoft/ApplicationInsights-Go v0.4.4 github.com/microsoft/go-mssqldb v1.6.0 github.com/opencontainers/image-spec v1.0.2 github.com/peterh/liner v1.2.2 @@ -25,6 +26,7 @@ require ( ) require ( + code.cloudfoundry.org/clock v0.0.0-20180518195852-02e53af36e6c // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect @@ -40,6 +42,7 @@ require ( github.com/docker/go-units v0.5.0 // indirect github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gofrs/uuid v3.3.0+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v5 v5.0.0 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect diff --git a/go.sum b/go.sum index f272a5c4..25cf032f 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +code.cloudfoundry.org/clock v0.0.0-20180518195852-02e53af36e6c h1:5eeuG0BHx1+DHeT3AP+ISKZ2ht1UjGhm581ljqYpVeQ= +code.cloudfoundry.org/clock v0.0.0-20180518195852-02e53af36e6c/go.mod h1:QD9Lzhd/ux6eNQVUDVRJX/RKTigpewimNYBi7ivZKY8= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1 h1:/iHxaJhsFr0+xVFfbMr5vxz848jyiWuIEDhYq3y5odY= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= @@ -105,6 +107,7 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -117,6 +120,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84= +github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -196,6 +201,7 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= @@ -228,6 +234,8 @@ github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8Bz github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/microsoft/ApplicationInsights-Go v0.4.4 h1:G4+H9WNs6ygSCe6sUyxRc2U81TI5Es90b2t/MwX5KqY= +github.com/microsoft/ApplicationInsights-Go v0.4.4/go.mod h1:fKRUseBqkw6bDiXTs3ESTiU/4YTIHsQS4W3fP2ieF4U= github.com/microsoft/go-mssqldb v1.6.0 h1:mM3gYdVwEPFrlg/Dvr2DNVEgYFG7L42l+dGc67NNNpc= github.com/microsoft/go-mssqldb v1.6.0/go.mod h1:00mDtPbeQCRGC1HwOOR5K/gr30P1NcEG0vx6Kbv2aJU= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -242,6 +250,9 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= @@ -317,6 +328,7 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tedsuo/ifrit v0.0.0-20180802180643-bea94bb476cc/go.mod h1:eyZnKCc955uh98WQvzOm0dgAeLnf2O0Rz0LPoC5ze+0= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -374,6 +386,7 @@ golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -429,6 +442,7 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -643,8 +657,10 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/telemetry/appinsights.go b/internal/telemetry/appinsights.go new file mode 100644 index 00000000..9db609e4 --- /dev/null +++ b/internal/telemetry/appinsights.go @@ -0,0 +1,75 @@ +package telemetry + +import ( + "fmt" + "os" + "strings" + "time" + + "github.com/microsoft/ApplicationInsights-Go/appinsights" +) + +var TelemetryClient appinsights.TelemetryClient + +func init() { + telemetryEnabled := strings.ToLower(os.Getenv("SQLCMD_TELEMETRY")) + if telemetryEnabled == "true" { + InitializeAppInsights() + } +} + +func InitializeTelemetryLogging() { + telemetryEnabled := strings.ToLower(os.Getenv("SQLCMD_TELEMETRY")) + loggingEnabled := strings.ToLower(os.Getenv("SQLCMD_TELEMETRY_LOGGING")) + if telemetryEnabled == "true" && loggingEnabled == "true" { + // Add a diagnostics listener for printing telemetry messages + appinsights.NewDiagnosticsMessageListener(func(msg string) error { + fmt.Printf("[%s] %s\n", time.Now().Format(time.UnixDate), msg) + return nil + }) + } +} + +func InitializeAppInsights() { + instrumentationKey := "f305b208-557d-4fba-bf06-25345c4dfdbc" + config := appinsights.NewTelemetryConfiguration(instrumentationKey) + TelemetryClient = appinsights.NewTelemetryClientFromConfig(config) + InitializeTelemetryLogging() +} + +func TrackEvent(eventName string, properties map[string]string) { + event := appinsights.NewEventTelemetry(eventName) + for key, value := range properties { + event.Properties[key] = value + } + TelemetryClient.Track(event) +} + +func CloseTelemetry() { + select { + case <-TelemetryClient.Channel().Close(10 * time.Second): + // Ten second timeout for retries. + + // If we got here, then all telemetry was submitted + // successfully, and we can proceed to exiting. + case <-time.After(30 * time.Second): + // Thirty second absolute timeout. This covers any + // previous telemetry submission that may not have + // completed before Close was called. + + // There are a number of reasons we could have + // reached here. We gave it a go, but telemetry + // submission failed somewhere. Perhaps old events + // were still retrying, or perhaps we're throttled. + // Either way, we don't want to wait around for it + // to complete, so let's just exit. + } +} + +func CloseTelemetryAndExit(exitcode int) { + telemetryEnabled := strings.ToLower(os.Getenv("SQLCMD_TELEMETRY")) + if telemetryEnabled == "true" { + CloseTelemetry() + } + os.Exit(exitcode) +} diff --git a/pkg/sqlcmd/sqlcmd.go b/pkg/sqlcmd/sqlcmd.go index 3293253b..2c4c0fb3 100644 --- a/pkg/sqlcmd/sqlcmd.go +++ b/pkg/sqlcmd/sqlcmd.go @@ -27,6 +27,7 @@ import ( "github.com/microsoft/go-mssqldb/msdsn" "github.com/microsoft/go-sqlcmd/internal/color" "github.com/microsoft/go-sqlcmd/internal/localizer" + "github.com/microsoft/go-sqlcmd/internal/telemetry" "golang.org/x/text/encoding/unicode" "golang.org/x/text/transform" ) @@ -571,7 +572,7 @@ func (s *Sqlcmd) SetupCloseHandler() { if s.lineIo != nil { s.lineIo.Close() } - os.Exit(0) + telemetry.CloseTelemetryAndExit(0) }() } From 49972d33a31817fe38b9e2b3ae871fe3fff57b05 Mon Sep 17 00:00:00 2001 From: Apoorv Deshmukh Date: Tue, 23 Jan 2024 20:33:06 +0530 Subject: [PATCH 2/3] Generate persistent unique userId for all events --- cmd/modern/main.go | 2 +- cmd/modern/root.go | 4 +++ cmd/modern/root/config/add-context.go | 20 +++++++++++++ cmd/modern/root/config/add-endpoint.go | 20 +++++++++++++ cmd/modern/root/config/add-user.go | 21 +++++++++++++ cmd/modern/root/config/view.go | 14 +++++++++ go.mod | 1 + go.sum | 3 ++ internal/cmdparser/interface.go | 4 +++ internal/telemetry/appinsights.go | 41 ++++++++++++++++++-------- 10 files changed, 117 insertions(+), 13 deletions(-) diff --git a/cmd/modern/main.go b/cmd/modern/main.go index f6397cda..eeeadb9d 100644 --- a/cmd/modern/main.go +++ b/cmd/modern/main.go @@ -41,7 +41,7 @@ var version = "local-build" // overridden in pipeline builds with: -ldflags="-X // executed. Otherwise, the legacy CLI is executed. func main() { telemetry.InitializeAppInsights() - telemetry.TrackEvent("sqlcmd.version", map[string]string{"version": version}) + dependencies := dependency.Options{ Output: output.New(output.Options{ StandardWriter: os.Stdout, diff --git a/cmd/modern/root.go b/cmd/modern/root.go index 8a83f02b..26d78951 100644 --- a/cmd/modern/root.go +++ b/cmd/modern/root.go @@ -142,3 +142,7 @@ func (c *Root) addGlobalFlags() { Usage: localizer.Sprintf("log level, error=0, warn=1, info=2, debug=3, trace=4"), }) } + +func (c *Root) LogTelemtry() { + /* Dummy implementation for now */ +} diff --git a/cmd/modern/root/config/add-context.go b/cmd/modern/root/config/add-context.go index 351bb0c8..0ae101ea 100644 --- a/cmd/modern/root/config/add-context.go +++ b/cmd/modern/root/config/add-context.go @@ -10,6 +10,7 @@ import ( "github.com/microsoft/go-sqlcmd/internal/cmdparser" "github.com/microsoft/go-sqlcmd/internal/config" "github.com/microsoft/go-sqlcmd/internal/localizer" + "github.com/microsoft/go-sqlcmd/internal/telemetry" ) // AddContext implements the `sqlcmd config add-context` command @@ -93,4 +94,23 @@ func (c *AddContext) run() { {localizer.Sprintf("To start interactive query session"), "sqlcmd query"}, {localizer.Sprintf("To run a query"), "sqlcmd query \"SELECT @@version\""}, }, localizer.Sprintf("Current Context '%v'", context.Name)) + c.LogTelemtry() +} + +func (c *AddContext) LogTelemtry() { + eventName := "config-add-context" + properties := map[string]string{} + properties["Command"] = "config" + properties["SubCommand"] = "add-context" + if c.name != "" { + properties["name"] = "set" + } + if c.endpointName != "" { + properties["endpoint"] = "set" + } + if c.userName != "" { + properties["user"] = "set" + } + telemetry.TrackEvent(eventName, properties) + telemetry.CloseTelemetry() } diff --git a/cmd/modern/root/config/add-endpoint.go b/cmd/modern/root/config/add-endpoint.go index e11126e8..96ab7614 100644 --- a/cmd/modern/root/config/add-endpoint.go +++ b/cmd/modern/root/config/add-endpoint.go @@ -11,6 +11,7 @@ import ( "github.com/microsoft/go-sqlcmd/internal/cmdparser" "github.com/microsoft/go-sqlcmd/internal/config" "github.com/microsoft/go-sqlcmd/internal/localizer" + "github.com/microsoft/go-sqlcmd/internal/telemetry" ) // AddEndpoint implements the `sqlcmd config add-endpoint` command @@ -82,4 +83,23 @@ func (c *AddEndpoint) run() { {localizer.Sprintf("Delete this endpoint"), fmt.Sprintf("sqlcmd config delete-endpoint %v", uniqueEndpointName)}, }, localizer.Sprintf("Endpoint '%v' added (address: '%v', port: '%v')", uniqueEndpointName, c.address, c.port)) + c.LogTelemtry() +} + +func (c *AddEndpoint) LogTelemtry() { + eventName := "config-add-endpoint" + properties := map[string]string{} + properties["Command"] = "Config" + properties["SubCommand"] = "add-endpoint" + if c.name != "" { + properties["name"] = "set" + } + if c.address != "" { + properties["address"] = "set" + } + if c.port != 0 { + properties["port"] = "set" + } + telemetry.TrackEvent(eventName, properties) + telemetry.CloseTelemetry() } diff --git a/cmd/modern/root/config/add-user.go b/cmd/modern/root/config/add-user.go index f0232670..3a9a43bb 100644 --- a/cmd/modern/root/config/add-user.go +++ b/cmd/modern/root/config/add-user.go @@ -11,6 +11,7 @@ import ( "github.com/microsoft/go-sqlcmd/cmd/modern/sqlconfig" "github.com/microsoft/go-sqlcmd/internal/localizer" "github.com/microsoft/go-sqlcmd/internal/pal" + "github.com/microsoft/go-sqlcmd/internal/telemetry" "github.com/microsoft/go-sqlcmd/internal/cmdparser" "github.com/microsoft/go-sqlcmd/internal/config" @@ -170,3 +171,23 @@ func (c *AddUser) run() { uniqueUserName := config.AddUser(user) output.Info(localizer.Sprintf("User '%v' added", uniqueUserName)) } + +func (c *AddUser) LogTelemtry() { + eventName := "config-add-user" + properties := map[string]string{} + if c.username != "" { + properties["username"] = "set" + } + if c.authType != "" { + properties["authtype"] = c.authType + } + if c.name != "" { + properties["name"] = "set" + } + + if c.passwordEncryption != "" { + properties["passwordEncryption"] = "set" + } + telemetry.TrackEvent(eventName, properties) + telemetry.CloseTelemetry() +} diff --git a/cmd/modern/root/config/view.go b/cmd/modern/root/config/view.go index a5a2c6e2..d7151235 100644 --- a/cmd/modern/root/config/view.go +++ b/cmd/modern/root/config/view.go @@ -4,9 +4,12 @@ package config import ( + "strconv" + "github.com/microsoft/go-sqlcmd/internal/cmdparser" "github.com/microsoft/go-sqlcmd/internal/config" "github.com/microsoft/go-sqlcmd/internal/localizer" + "github.com/microsoft/go-sqlcmd/internal/telemetry" ) // View implements the `sqlcmd config view` command @@ -48,4 +51,15 @@ func (c *View) run() { contents := config.RedactedConfig(c.raw) output.Struct(contents) + c.LogTelemtry() +} + +func (c *View) LogTelemtry() { + eventName := "config-view" + properties := map[string]string{} + properties["Command"] = "Config" + properties["SubCommand"] = "View" + properties["Flag"] = strconv.FormatBool(c.raw) + telemetry.TrackEvent(eventName, properties) + telemetry.CloseTelemetry() } diff --git a/go.mod b/go.mod index dc4f9e43..974c8378 100644 --- a/go.mod +++ b/go.mod @@ -37,6 +37,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/denisbrodbeck/machineid v1.0.1 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.5.0 // indirect diff --git a/go.sum b/go.sum index 25cf032f..cc21e9e5 100644 --- a/go.sum +++ b/go.sum @@ -84,6 +84,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ= +github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI= github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= @@ -317,6 +319,7 @@ github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+eg github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= diff --git a/internal/cmdparser/interface.go b/internal/cmdparser/interface.go index 23793e79..6a408120 100644 --- a/internal/cmdparser/interface.go +++ b/internal/cmdparser/interface.go @@ -42,4 +42,8 @@ type Command interface { // the Command object to have access to the dependencies it needs, without // having to manage them directly. SetCrossCuttingConcerns(dependency.Options) + + // LogTelemtry is used to track the useful telemetry for the command + // To be enforced after the review + // LogTelemtry() } diff --git a/internal/telemetry/appinsights.go b/internal/telemetry/appinsights.go index 9db609e4..006edf31 100644 --- a/internal/telemetry/appinsights.go +++ b/internal/telemetry/appinsights.go @@ -3,25 +3,40 @@ package telemetry import ( "fmt" "os" + "os/user" "strings" "time" + "github.com/denisbrodbeck/machineid" "github.com/microsoft/ApplicationInsights-Go/appinsights" ) -var TelemetryClient appinsights.TelemetryClient +var telemetryClient appinsights.TelemetryClient +var isTelemetryEnabled string = "true" +var uniqTelemetryID string = "" + +func initUniqUserId() { + if uniqTelemetryID == "" { + user, _ := user.Current() + id, _ := machineid.ProtectedID(user.Username) + uniqTelemetryID = id + } +} func init() { telemetryEnabled := strings.ToLower(os.Getenv("SQLCMD_TELEMETRY")) - if telemetryEnabled == "true" { + if telemetryEnabled == "false" { + isTelemetryEnabled = "false" + } + if isTelemetryEnabled == "true" { InitializeAppInsights() + initUniqUserId() } } func InitializeTelemetryLogging() { - telemetryEnabled := strings.ToLower(os.Getenv("SQLCMD_TELEMETRY")) loggingEnabled := strings.ToLower(os.Getenv("SQLCMD_TELEMETRY_LOGGING")) - if telemetryEnabled == "true" && loggingEnabled == "true" { + if isTelemetryEnabled == "true" && loggingEnabled == "true" { // Add a diagnostics listener for printing telemetry messages appinsights.NewDiagnosticsMessageListener(func(msg string) error { fmt.Printf("[%s] %s\n", time.Now().Format(time.UnixDate), msg) @@ -33,21 +48,24 @@ func InitializeTelemetryLogging() { func InitializeAppInsights() { instrumentationKey := "f305b208-557d-4fba-bf06-25345c4dfdbc" config := appinsights.NewTelemetryConfiguration(instrumentationKey) - TelemetryClient = appinsights.NewTelemetryClientFromConfig(config) + telemetryClient = appinsights.NewTelemetryClientFromConfig(config) InitializeTelemetryLogging() } func TrackEvent(eventName string, properties map[string]string) { - event := appinsights.NewEventTelemetry(eventName) - for key, value := range properties { - event.Properties[key] = value + if isTelemetryEnabled == "true" { + event := appinsights.NewEventTelemetry(eventName) + event.Properties["userId"] = uniqTelemetryID + for key, value := range properties { + event.Properties[key] = value + } + telemetryClient.Track(event) } - TelemetryClient.Track(event) } func CloseTelemetry() { select { - case <-TelemetryClient.Channel().Close(10 * time.Second): + case <-telemetryClient.Channel().Close(10 * time.Second): // Ten second timeout for retries. // If we got here, then all telemetry was submitted @@ -67,8 +85,7 @@ func CloseTelemetry() { } func CloseTelemetryAndExit(exitcode int) { - telemetryEnabled := strings.ToLower(os.Getenv("SQLCMD_TELEMETRY")) - if telemetryEnabled == "true" { + if isTelemetryEnabled == "true" { CloseTelemetry() } os.Exit(exitcode) From 12039f3b7732570ce21f33637187a190f2d56be2 Mon Sep 17 00:00:00 2001 From: Apoorv Deshmukh Date: Tue, 27 Feb 2024 20:37:20 +0530 Subject: [PATCH 3/3] Add event names and properties for telemetry --- go.mod | 2 +- go.sum | 5 +- internal/localizer/localizer.go | 9 +- internal/telemetry/appinsights.go | 230 +++++++++++++++++++++++++-- internal/telemetry/userid_darwin.go | 13 ++ internal/telemetry/userid_linux.go | 13 ++ internal/telemetry/userid_windows.go | 29 ++++ 7 files changed, 283 insertions(+), 18 deletions(-) create mode 100644 internal/telemetry/userid_darwin.go create mode 100644 internal/telemetry/userid_linux.go create mode 100644 internal/telemetry/userid_windows.go diff --git a/go.mod b/go.mod index 6977f8f3..96a465ad 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.21 require ( github.com/alecthomas/chroma/v2 v2.5.0 github.com/billgraziano/dpapi v0.4.0 + github.com/denisbrodbeck/machineid v1.0.1 github.com/docker/distribution v2.8.2+incompatible github.com/docker/docker v24.0.9+incompatible github.com/docker/go-connections v0.4.0 @@ -37,7 +38,6 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/denisbrodbeck/machineid v1.0.1 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.5.0 // indirect diff --git a/go.sum b/go.sum index 5c67b569..b51d56c7 100644 --- a/go.sum +++ b/go.sum @@ -113,8 +113,8 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -210,8 +210,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= @@ -330,7 +330,6 @@ github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+eg github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= diff --git a/internal/localizer/localizer.go b/internal/localizer/localizer.go index ebd6fd68..0ffe3586 100644 --- a/internal/localizer/localizer.go +++ b/internal/localizer/localizer.go @@ -19,6 +19,7 @@ import ( ) var Translator *message.Printer +var LocaleName = "en-us" var supportedLanguages = map[string]string{ "de-de": "de-DE", @@ -40,11 +41,11 @@ var supportedLanguages = map[string]string{ // based on env var SQLCMD_LANG which expects language // tag such as en-us, de-de, fr-ch, etc. func init() { - localeName := strings.ToLower(os.Getenv("SQLCMD_LANG")) - if _, ok := supportedLanguages[localeName]; !ok { - localeName = "en-us" + LocaleName = strings.ToLower(os.Getenv("SQLCMD_LANG")) + if _, ok := supportedLanguages[LocaleName]; !ok { + LocaleName = "en-us" } - Translator = message.NewPrinter(language.MustParse(supportedLanguages[localeName])) + Translator = message.NewPrinter(language.MustParse(supportedLanguages[LocaleName])) } // Errorf() is wrapper function to create localized errors diff --git a/internal/telemetry/appinsights.go b/internal/telemetry/appinsights.go index 006edf31..9c14ad12 100644 --- a/internal/telemetry/appinsights.go +++ b/internal/telemetry/appinsights.go @@ -3,24 +3,235 @@ package telemetry import ( "fmt" "os" - "os/user" + "runtime" "strings" "time" - "github.com/denisbrodbeck/machineid" + "github.com/google/uuid" "github.com/microsoft/ApplicationInsights-Go/appinsights" + "github.com/microsoft/go-sqlcmd/internal/localizer" ) var telemetryClient appinsights.TelemetryClient var isTelemetryEnabled string = "true" -var uniqTelemetryID string = "" -func initUniqUserId() { - if uniqTelemetryID == "" { - user, _ := user.Current() - id, _ := machineid.ProtectedID(user.Username) - uniqTelemetryID = id +type TelemetrySqlcmd struct { + EventName string + Properties map[string]string +} + +// Event Names +const ( + Legacy = "sqlcmd/legacy" + Config = "sqlcmd/config" + ConfigAddContext = "sqlcmd/config/addcontext" + ConfigAddEndpoint = "sqlcmd/config/addendpoint" + ConfigAddUser = "sqlcmd/config/adduser" + ConfigConnectionStrings = "sqlcmd/config/connectionstrings" + ConfigCurrentContexts = "sqlcmd/config/currentcontexts" + ConfigDeleteContext = "sqlcmd/config/deletecontext" + ConfigDeleteEndpoint = "sqlcmd/config/deleteendpoint" + ConfigDeleteUser = "sqlcmd/config/deleteuser" + ConfigGetContexts = "sqlcmd/config/getcontexts" + ConfigGetEndpoints = "sqlcmd/config/getendpoints" + ConfigGetUsers = "sqlcmd/config/getusers" + ConfigUseContext = "sqlcmd/config/usecontext" + ConfigView = "sqlcmd/config/view" + Create = "sqlcmd/create" + CreateAzsqlEdge = "sqlcmd/create/azsqledge" + CreateMssql = "sqlcmd/create/mssql" + Delete = "sqlcmd/delete" + SqlcmdHelp = "sqlcmd/help" + Open = "sqlcmd/open" + SqlcmdQuery = "sqlcmd/query" + Start = "sqlcmd/start" + Stop = "sqlcmd/stop" +) + +// Property Names +var ( + // Common Properties + userId string + sessionId string + locLang string + userOs string +) + +const ( + + //Common Properties + UserId = "Sqlcmd.userId" + SessionId = "Sqlcmd.sessionId" + LocLang = "Sqlcmd.locLang" + UserOs = "Sqlcmd.userOs" + + // Legacy Flags + HelpSymbol = "Sqlcmd.Legacy.?" + Helpflag = "Sqlcmd.Legacy.help.h" + Sqlconfig = "Sqlcmd.sqlconfig" + Verbosity = "Sqlcmd.verbosity" + Version = "Sqlcmd.version" + ApplicationIntent = "Sqlcmd.Legacy.application-intent.K" + AuthenticationMethod = "Sqlcmd.Legacy.authentication-method" + BatchTerminator = "Sqlcmd.Legacy.batch-terminator.c" + ChangePassword = "Sqlcmd.Legacy.change-password.z" + ChangePasswordExit = "Sqlcmd.Legacy.change-password-exit.Z" + ClientRegionalSetting = "Sqlcmd.Legacy.client-regional-setting.R" + ColumnSeparator = "Sqlcmd.Legacy.column-separator.s" + DatabaseName = "Sqlcmd.Legacy.database-name.d" + DedicatedAdminConnection = "Sqlcmd.Legacy.dedicated-admin-connection.A" + DisableCmdAndWarn = "Sqlcmd.Legacy.disable-cmd-and-warn.X" + DisableVariableSubstitution = "Sqlcmd.Legacy.disable-variable-substitution.x" + DriverLoggingLevel = "Sqlcmd.Legacy.driver-logging-level" + EchoInput = "Sqlcmd.Legacy.echo-input.e" + EnableColumnEncryption = "Sqlcmd.Legacy.enable-column-encryption.g" + EnableQuotedIdentifiers = "Sqlcmd.Legacy.enable-quoted-identifiers.I" + EncryptConnection = "Sqlcmd.Legacy.encrypt-connection.N" + ErrorLevel = "Sqlcmd.Legacy.error-level.m" + ErrorSeverityLevel = "Sqlcmd.Legacy.error-severity-level.V" + ErrorsToStderr = "Sqlcmd.Legacy.errors-to-stderr.r" + ExitOnError = "Sqlcmd.Legacy.exit-on-error.b" + FixedTypeWidth = "Sqlcmd.Legacy.fixed-type-width.Y" + Format = "Sqlcmd.Legacy.format.F" + Headers = "Sqlcmd.Legacy.headers.h" + InitialQuery = "Sqlcmd.Legacy.initial-query.q" + InputFile = "Sqlcmd.Legacy.input-file.i" + ListServers = "Sqlcmd.Legacy.list-servers.L" + LoginTimeOut = "Sqlcmd.Legacy.login-timeOut.l" + MultiSubnetFailover = "Sqlcmd.Legacy.multi-subnet-failover.M" + OutputFile = "Sqlcmd.Legacy.output-file.o" + PacketSize = "Sqlcmd.Legacy.packet-size.a" + Password = "Sqlcmd.Legacy.password.P" + Query = "Sqlcmd.Legacy.query.Q" + QueryTimeout = "Sqlcmd.Legacy.query-timeout.t" + RemoveControlCharacters = "Sqlcmd.Legacy.remove-control-characters.k" + ScreenWidth = "Sqlcmd.Legacy.screen-width.w" + Server = "Sqlcmd.Legacy.server.S" + TrimSpaces = "Sqlcmd.Legacy.trim-spaces.W" + TrustServerCertificate = "Sqlcmd.Legacy.trust-server-certificate.C" + UnicodeOutputFile = "Sqlcmd.Legacy.unicode-output-file.u" + UseAad = "Sqlcmd.Legacy.use-aad.G" + UseTrustedConnection = "Sqlcmd.Legacy.use-trusted-connection.E" + UserName = "Sqlcmd.Legacy.user-name.U" + VariableTypeWidth = "Sqlcmd.Legacy.variable-type-width.y" + Variables = "Sqlcmd.Legacy.variables.v" + WorkstationName = "Sqlcmd.Legacy.workstation-name.H" + + // Modern Flags + ConfigAddContextHelp = "Sqlcmd.Config.AddContext.help" + ConfigAddContextEndpoint = "Sqlcmd.Config.AddContext.Endpoint" + ConfigAddContextName = "Sqlcmd.Config.AddContext.Name" + ConfigAddContextUser = "Sqlcmd.Config.AddContext.User" + ConfigAddEndpointHelp = "Sqlcmd.Config.AddEndpoint.help" + ConfigAddEndpointAddress = "Sqlcmd.Config.AddEndpoint.Address" + ConfigAddEndpointName = "Sqlcmd.Config.AddEndpoint.Name" + ConfigAddEndpointPort = "Sqlcmd.Config.AddEndpoint.Port" + ConfigAddUserHelp = "Sqlcmd.Config.AddUser.help" + ConfigAddUserAuthType = "Sqlcmd.Config.AddUser.AuthType" + ConfigAddUserName = "Sqlcmd.Config.AddUser.Name" + ConfigAddUserPasswordEncryption = "Sqlcmd.Config.AddUser.PasswordEncryption" + ConfigAddUserUsername = "Sqlcmd.Config.AddUser.Username" + ConfigAddUserPassword = "Sqlcmd.Config.AddUser.Password" + ConfigConnectionStringsHelp = "Sqlcmd.Config.ConnectionStrings.help" + ConfigConnectionStringsDatabase = "Config.ConnectionStrings.database.d" + ConfigCurrentContextsHelp = "Sqlcmd.Config.CurrentContexts.help" + ConfigDeleteContextHelp = "Sqlcmd.Config.DeleteContext.help" + ConfigDeleteContextCascade = "Sqlcmd.Config.DeleteContext.Cascade" + ConfigDeleteContextname = "Sqlcmd.Config.DeleteContext.name" + ConfigDeleteEndpointHelp = "Sqlcmd.Config.DeleteEndpoint.help" + ConfigDeleteEndpointName = "Sqlcmd.Config.DeleteEndpoint.Name" + ConfigDeleteUserHelp = "Sqlcmd.Config.DeleteUser.help" + ConfigDeleteUserName = "Sqlcmd.Config.DeleteUser.Name" + ConfigGetContextsHelp = "Sqlcmd.Config.GetContexts.help" + ConfigGetContextsDetailed = "Sqlcmd.Config.GetContexts.Detailed" + ConfigGetContextsName = "Sqlcmd.Config.GetContexts.Name" + ConfigGetEndpointsHelp = "Sqlcmd.Config.GetEndpoints.help" + ConfigGetEndpointsDetailed = "Sqlcmd.Config.GetEndpoints.Detailed" + ConfigGetEndpointsName = "Sqlcmd.Config.GetEndpoints.Name" + ConfigGetUsersHelp = "Sqlcmd.Config.GetUsers.help" + ConfigGetUsersDetailed = "Sqlcmd.Config.GetUsers.Detailed" + ConfigGetUsersName = "Sqlcmd.Config.GetUsers.Name" + ConfigUseContextHelp = "Sqlcmd.Config.UseContext.help" + ConfigUseContextname = "Sqlcmd.Config.UseContext.name" + ConfigViewHelp = "Sqlcmd.Config.View.help" + ConfigViewRaw = "Sqlcmd.Config.View.Raw" + ConfigHelp = "Sqlcmd.Config.help" + + CreateHelp = "Sqlcmd.Create.help" + CreateAzsqlEdgeHelp = "Sqlcmd.Create.AzsqlEdge.help" + CreateAzsqlEdgeGetTagsHelp = "Sqlcmd.Create.AzsqlEdge.GetTags.help" + CreateAzsqlEdgeAcceptEula = "Sqlcmd.Create.AzsqlEdge.AcceptEula" + CreateAzsqlEdgeArchitecture = "Sqlcmd.Create.AzsqlEdge.Architecture" + CreateAzsqlEdgeCached = "Sqlcmd.Create.AzsqlEdge.Cached" + CreateAzsqlEdgeCollation = "Sqlcmd.Create.AzsqlEdge.Collation" + CreateAzsqlEdgeContextName = "Sqlcmd.Create.AzsqlEdge.ContextName.c" + CreateAzsqlEdgeErrorlogWaitLine = "Sqlcmd.Create.AzsqlEdge.ErrorlogWaitLine" + CreateAzsqlEdgeHostname = "Sqlcmd.Create.AzsqlEdge.Hostname" + CreateAzsqlEdgeName = "Sqlcmd.Create.AzsqlEdge.Name" + CreateAzsqlEdgeOs = "Sqlcmd.Create.AzsqlEdge.Os" + CreateAzsqlEdgePasswordEncryption = "Sqlcmd.Create.AzsqlEdge.PasswordEncryption" + CreateAzsqlEdgePasswordLength = "Sqlcmd.Create.AzsqlEdge.PasswordLength" + CreateAzsqlEdgePasswordMinNumber = "Sqlcmd.Create.AzsqlEdge.PasswordMinNumber" + CreateAzsqlEdgePasswordMinSpecial = "Sqlcmd.Create.AzsqlEdge.PasswordMinSpecial" + CreateAzsqlEdgePasswordMinUpper = "Sqlcmd.Create.AzsqlEdge.PasswordMinUpper" + CreateAzsqlEdgePasswordSpecialChars = "Sqlcmd.Create.AzsqlEdge.PasswordSpecialChars" + CreateAzsqlEdgePort = "Sqlcmd.Create.AzsqlEdge.Port" + CreateAzsqlEdgeRegistry = "Sqlcmd.Create.AzsqlEdge.Registry" + CreateAzsqlEdgeRepo = "Sqlcmd.Create.AzsqlEdge.Repo" + CreateAzsqlEdgeTag = "Sqlcmd.Create.AzsqlEdge.Tag" + CreateAzsqlEdgeUserDatabase = "Sqlcmd.Create.AzsqlEdge.UserDatabase.u" + CreateAzsqlEdgeUsing = "Sqlcmd.Create.AzsqlEdge.Using" + + CreateMssqlHelp = "Sqlcmd.Create.Mssql.help" + CreateMssqlGetTagsHelp = "Sqlcmd.Create.Mssql.GetTags.help" + CreateMssqlAcceptEula = "Sqlcmd.Create.Mssql.AcceptEula" + CreateMssqlArchitecture = "Sqlcmd.Create.Mssql.Architecture" + CreateMssqlCached = "Sqlcmd.Create.Mssql.Cached" + CreateMssqlCollation = "Sqlcmd.Create.Mssql.Collation" + CreateMssqlContextName = "Sqlcmd.Create.Mssql.ContextName.c" + CreateMssqlErrorlogWaitLine = "Sqlcmd.Create.Mssql.ErrorlogWaitLine" + CreateMssqlHostname = "Sqlcmd.Create.Mssql.Hostname" + CreateMssqlName = "Sqlcmd.Create.Mssql.Name" + CreateMssqlOs = "Sqlcmd.Create.Mssql.Os" + CreateMssqlPasswordEncryption = "Sqlcmd.Create.Mssql.PasswordEncryption" + CreateMssqlPasswordLength = "Sqlcmd.Create.Mssql.PasswordLength" + CreateMssqlPasswordMinNumber = "Sqlcmd.Create.Mssql.PasswordMinNumber" + CreateMssqlPasswordMinSpecial = "Sqlcmd.Create.Mssql.PasswordMinSpecial" + CreateMssqlPasswordMinUpper = "Sqlcmd.Create.Mssql.PasswordMinUpper" + CreateMssqlPasswordSpecialChars = "Sqlcmd.Create.Mssql.PasswordSpecialChars" + CreateMssqlPort = "Sqlcmd.Create.Mssql.Port" + CreateMssqlRegistry = "Sqlcmd.Create.Mssql.Registry" + CreateMssqlRepo = "Sqlcmd.Create.Mssql.Repo" + CreateMssqlTag = "Sqlcmd.Create.Mssql.Tag" + CreateMssqlUserDatabase = "Sqlcmd.Create.Mssql.UserDatabase.u" + CreateMssqlUsing = "Sqlcmd.Create.Mssql.Using" + + DeleteHelp = "Sqlcmd.Delete.help" + DeleteForce = "Sqlcmd.Delete.Force" + DeleteYes = "Sqlcmd.Delete.Yes" + + Help = "Sqlcmd.help" + + OpenHelp = "Sqlcmd.Open.help" + OpenAdsHelp = "Sqlcmd.Open.Ads.help" + + QueryHelp = "Sqlcmd.Query.help" + QueryDatabase = "Sqlcmd.Query.database.d" + Queryquery = "Sqlcmd.Query.query.q" + QueryText = "Sqlcmd.Query.Text.t" + StartHelp = "Sqlcmd.Start.help" + StopHelp = "Sqlcmd.Stop.help" +) + +func initCommonProps() { + if userId == "" { + userId = getOrCreateUserId() + } + if sessionId == "" { + sessionId = uuid.NewString() } + locLang = localizer.LocaleName + userOs = runtime.GOOS } func init() { @@ -30,7 +241,7 @@ func init() { } if isTelemetryEnabled == "true" { InitializeAppInsights() - initUniqUserId() + initCommonProps() } } @@ -55,7 +266,6 @@ func InitializeAppInsights() { func TrackEvent(eventName string, properties map[string]string) { if isTelemetryEnabled == "true" { event := appinsights.NewEventTelemetry(eventName) - event.Properties["userId"] = uniqTelemetryID for key, value := range properties { event.Properties[key] = value } diff --git a/internal/telemetry/userid_darwin.go b/internal/telemetry/userid_darwin.go new file mode 100644 index 00000000..ea2bd450 --- /dev/null +++ b/internal/telemetry/userid_darwin.go @@ -0,0 +1,13 @@ +package telemetry + +import ( + "os/user" + + "github.com/denisbrodbeck/machineid" +) + +func getOrCreateUserId() string { + user, _ := user.Current() + id, _ := machineid.ProtectedID(user.Username) + return id +} diff --git a/internal/telemetry/userid_linux.go b/internal/telemetry/userid_linux.go new file mode 100644 index 00000000..ea2bd450 --- /dev/null +++ b/internal/telemetry/userid_linux.go @@ -0,0 +1,13 @@ +package telemetry + +import ( + "os/user" + + "github.com/denisbrodbeck/machineid" +) + +func getOrCreateUserId() string { + user, _ := user.Current() + id, _ := machineid.ProtectedID(user.Username) + return id +} diff --git a/internal/telemetry/userid_windows.go b/internal/telemetry/userid_windows.go new file mode 100644 index 00000000..338dcc14 --- /dev/null +++ b/internal/telemetry/userid_windows.go @@ -0,0 +1,29 @@ +package telemetry + +import ( + "log" + "strings" + + "github.com/google/uuid" + "golang.org/x/sys/windows/registry" +) + +func getOrCreateUserId() string { + + k, err := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\SQMClient`, registry.ALL_ACCESS) + if err != nil { + log.Fatal(err) + } + defer k.Close() + + s, _, err := k.GetStringValue("UserId") + if err != nil && strings.Contains(err.Error(), "The system cannot find the file specified") { + id := uuid.New() + k.SetStringValue("UserId", "{"+strings.ToUpper(id.String()+"}")) + s, _, _ = k.GetStringValue("UserId") + } + if strings.Contains(s, "{") { + return s[1 : len(s)-1] + } + return s +}