-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Telemetry (turn off with DO_NOT_TRACK env var) (#671)
Introducing anonymous telemetry that respects common ENV var `DO_NOT_TRACK=true` or `DO_NOT_TRACK=1`.
- Loading branch information
1 parent
54acee3
commit ca6efae
Showing
4 changed files
with
187 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package telemetry | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
"os" | ||
"strings" | ||
"time" | ||
|
||
"github.com/pkg/errors" | ||
"go.uber.org/zap" | ||
) | ||
|
||
const ( | ||
base = "https://home.runme.dev/" | ||
client = "Kernel" | ||
) | ||
|
||
type LookupEnv func(key string) (string, bool) | ||
|
||
// Returns true if telemetry reporting is enabled, false otherwise. | ||
func ReportUnlessNoTracking(logger *zap.Logger) bool { | ||
if v := os.Getenv("DO_NOT_TRACK"); v != "" && v != "0" && v != "false" { | ||
logger.Info("Telemetry reporting is disabled with DO_NOT_TRACK") | ||
return false | ||
} | ||
|
||
if v := os.Getenv("SCARF_NO_ANALYTICS"); v != "" && v != "0" && v != "false" { | ||
logger.Info("Telemetry reporting is disabled with SCARF_NO_ANALYTICS") | ||
return false | ||
} | ||
|
||
logger.Info("Telemetry reporting is enabled") | ||
|
||
go func() { | ||
err := report() | ||
if err != nil { | ||
logger.Warn("Error reporting telemetry", zap.Error(err)) | ||
} | ||
}() | ||
|
||
return true | ||
} | ||
|
||
func report() error { | ||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) | ||
defer cancel() | ||
|
||
encodedURL, err := buildURL(os.LookupEnv, client) | ||
if err != nil { | ||
return errors.Wrapf(err, "Error building telemtry URL") | ||
} | ||
|
||
req, err := http.NewRequestWithContext(ctx, "GET", encodedURL.String(), nil) | ||
if err != nil { | ||
return errors.Wrapf(err, "Error creating telemetry request") | ||
} | ||
|
||
resp, err := http.DefaultClient.Do(req) | ||
if err != nil { | ||
return errors.Wrapf(err, "Error sending telemetry request") | ||
} | ||
defer resp.Body.Close() | ||
|
||
if resp.StatusCode >= 400 { | ||
return fmt.Errorf("error sending telemetry request: status_code=%d, status=%s", resp.StatusCode, resp.Status) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func buildURL(lookup LookupEnv, client string) (*url.URL, error) { | ||
baseAndClient := base + client | ||
|
||
props := []string{ | ||
"extname", | ||
"extversion", | ||
"remotename", | ||
"appname", | ||
"product", | ||
"platform", | ||
"uikind", | ||
} | ||
|
||
params := url.Values{} | ||
for _, p := range props { | ||
addValue(lookup, ¶ms, p) | ||
} | ||
|
||
// until we have a non-extension-bundled reporting strategy, lets error | ||
if len(params) == 0 { | ||
return nil, fmt.Errorf("no telemetry properties provided") | ||
} | ||
|
||
dst, err := url.Parse(baseAndClient) | ||
if err != nil { | ||
return nil, err | ||
} | ||
dst.RawQuery = params.Encode() | ||
|
||
return dst, nil | ||
} | ||
|
||
func addValue(lookup LookupEnv, params *url.Values, prop string) { | ||
if v, ok := lookup(fmt.Sprintf("TELEMETRY_%s", strings.ToUpper(prop))); ok { | ||
params.Add(strings.ToLower(prop), v) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package telemetry | ||
|
||
import ( | ||
"os" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
"go.uber.org/zap" | ||
) | ||
|
||
func TestReportUnlessNoTracking(t *testing.T) { | ||
t.Run("Track", func(t *testing.T) { | ||
logger := zap.NewNop() | ||
require.True(t, ReportUnlessNoTracking(logger)) | ||
}) | ||
|
||
t.Run("DO_NOT_TRACK", func(t *testing.T) { | ||
logger := zap.NewNop() | ||
t.Setenv("DO_NOT_TRACK", "true") | ||
require.False(t, ReportUnlessNoTracking(logger)) | ||
}) | ||
|
||
t.Run("SCARF_NO_ANALYTICS", func(t *testing.T) { | ||
logger := zap.NewNop() | ||
t.Setenv("SCARF_NO_ANALYTICS", "true") | ||
defer os.Unsetenv("SCARF_NO_ANALYTICS") | ||
require.False(t, ReportUnlessNoTracking(logger)) | ||
}) | ||
} | ||
|
||
func TestUrlBuilder(t *testing.T) { | ||
t.Parallel() | ||
|
||
t.Run("Full", func(t *testing.T) { | ||
lookupEnv := createLookup(map[string]string{ | ||
"TELEMETRY_EXTNAME": "stateful.runme", | ||
"TELEMETRY_EXTVERSION": "3.7.7-dev.10", | ||
"TELEMETRY_REMOTENAME": "none", | ||
"TELEMETRY_APPNAME": "Visual Studio Code", | ||
"TELEMETRY_PRODUCT": "desktop", | ||
"TELEMETRY_PLATFORM": "darwin_arm64", | ||
"TELEMETRY_UIKIND": "desktop", | ||
}) | ||
dst, err := buildURL(lookupEnv, "Kernel") | ||
require.NoError(t, err) | ||
require.Equal(t, "https://home.runme.dev/Kernel?appname=Visual+Studio+Code&extname=stateful.runme&extversion=3.7.7-dev.10&platform=darwin_arm64&product=desktop&remotename=none&uikind=desktop", dst.String()) | ||
}) | ||
|
||
t.Run("Partial", func(t *testing.T) { | ||
lookupEnv := createLookup(map[string]string{ | ||
"TELEMETRY_EXTNAME": "stateful.runme", | ||
"TELEMETRY_PLATFORM": "linux_x64", | ||
}) | ||
dst, err := buildURL(lookupEnv, "Kernel") | ||
require.NoError(t, err) | ||
require.Equal(t, "https://home.runme.dev/Kernel?extname=stateful.runme&platform=linux_x64", dst.String()) | ||
}) | ||
|
||
t.Run("Empty", func(t *testing.T) { | ||
lookupEnv := createLookup(map[string]string{}) | ||
_, err := buildURL(lookupEnv, "Kernel") | ||
require.Error(t, err, "no telemetry properties provided") | ||
}) | ||
} | ||
|
||
func createLookup(fixture map[string]string) func(string) (string, bool) { | ||
return func(key string) (string, bool) { | ||
value, ok := fixture[key] | ||
return value, ok | ||
} | ||
} |