Skip to content

Commit 73f3900

Browse files
Merge pull request cli#13191 from cli/wm/add-telemetry-to-cli
Add sampled command telemetry
2 parents bc9ba9b + 17776ca commit 73f3900

56 files changed

Lines changed: 4268 additions & 658 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,7 @@
3939

4040
vendor/
4141
gh
42+
43+
# Test coverage artifacts
44+
coverage.out
45+
lcov.info

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ completions: bin/gh$(EXE)
3838
bin/gh$(EXE) completion -s fish > ./share/fish/vendor_completions.d/gh.fish
3939
bin/gh$(EXE) completion -s zsh > ./share/zsh/site-functions/_gh
4040

41+
.PHONY: lint
42+
lint:
43+
golangci-lint run ./...
44+
4145
# just convenience tasks around `go test`
4246
.PHONY: test
4347
test:

acceptance/acceptance_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,15 @@ func TestWorkflows(t *testing.T) {
182182
testscript.Run(t, testScriptParamsFor(tsEnv, "workflow"))
183183
}
184184

185+
func TestTelemetry(t *testing.T) {
186+
var tsEnv testScriptEnv
187+
if err := tsEnv.fromEnv(); err != nil {
188+
t.Fatal(err)
189+
}
190+
191+
testscript.Run(t, testScriptParamsFor(tsEnv, "telemetry"))
192+
}
193+
185194
func testScriptParamsFor(tsEnv testScriptEnv, command string) testscript.Params {
186195
var files []string
187196
if tsEnv.script != "" {
@@ -226,6 +235,8 @@ func sharedSetup(tsEnv testScriptEnv) func(ts *testscript.Env) error {
226235

227236
ts.Setenv("RANDOM_STRING", randomString(10))
228237

238+
ts.Setenv("GH_TELEMETRY", "false")
239+
229240
// The sandbox overrides HOME, so git cannot find the user's global
230241
// config. Write a minimal identity so commits inside the sandbox
231242
// don't fail with "Author identity unknown".
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Telemetry log mode outputs command invocation event to stderr
2+
env GH_PRIVATE_ENABLE_TELEMETRY=1
3+
env GH_TELEMETRY=log
4+
env GH_TELEMETRY_SAMPLE_RATE=100
5+
6+
exec gh version
7+
stderr 'Telemetry payload:'
8+
stderr '"type": "command_invocation"'
9+
stderr '"command": "gh version"'
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# The completion command should not generate a telemetry event
2+
env GH_PRIVATE_ENABLE_TELEMETRY=1
3+
env GH_TELEMETRY=log
4+
env GH_TELEMETRY_SAMPLE_RATE=100
5+
6+
exec gh completion -s bash
7+
! stderr 'Telemetry payload:'
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Extensions should not generate telemetry events
2+
[!exec:bash] skip
3+
4+
env GH_PRIVATE_ENABLE_TELEMETRY=1
5+
env GH_TELEMETRY=log
6+
env GH_TELEMETRY_SAMPLE_RATE=100
7+
8+
# Create a local shell extension repository
9+
exec git init gh-hello
10+
cp gh-hello.sh gh-hello/gh-hello
11+
chmod 755 gh-hello/gh-hello
12+
exec git -C gh-hello add gh-hello
13+
exec git -C gh-hello commit -m 'init'
14+
15+
# Install it locally
16+
cd gh-hello
17+
exec gh ext install .
18+
cd $WORK
19+
20+
# Run the extension and verify no telemetry is logged
21+
exec gh hello
22+
stdout 'hello from extension'
23+
! stderr 'Telemetry payload:'
24+
25+
-- gh-hello.sh --
26+
#!/usr/bin/env bash
27+
echo "hello from extension"
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# GHES users should not get telemetry even when telemetry is enabled
2+
env GH_PRIVATE_ENABLE_TELEMETRY=1
3+
env GH_TELEMETRY=log
4+
env GH_TELEMETRY_SAMPLE_RATE=100
5+
env GH_ENTERPRISE_TOKEN=fake-enterprise-token
6+
7+
exec gh version
8+
! stderr 'Telemetry payload:'
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# The send-telemetry command should not itself generate a telemetry event
2+
env GH_PRIVATE_ENABLE_TELEMETRY=1
3+
env GH_TELEMETRY=log
4+
env GH_TELEMETRY_SAMPLE_RATE=100
5+
env GH_TELEMETRY_ENDPOINT_URL=http://localhost:1
6+
7+
# Provide a minimal valid payload on stdin so the command can run.
8+
# It will fail to connect but that's fine — we only care about telemetry logging.
9+
stdin payload.json
10+
! exec gh send-telemetry
11+
! stderr 'Telemetry payload:'
12+
13+
-- payload.json --
14+
{"events":[{"type":"test","dimensions":{},"measures":{}}]}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Command completes successfully even when telemetry endpoint is unreachable
2+
env GH_PRIVATE_ENABLE_TELEMETRY=1
3+
env GH_TELEMETRY=enabled
4+
env GH_TELEMETRY_SAMPLE_RATE=100
5+
env GH_TELEMETRY_ENDPOINT_URL=http://localhost:1
6+
7+
exec gh version
8+
stdout 'gh version'

api/http_client.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"strings"
88
"time"
99

10+
"github.com/cli/cli/v2/internal/gh/ghtelemetry"
1011
"github.com/cli/cli/v2/utils"
1112
ghAPI "github.com/cli/go-gh/v2/pkg/api"
1213
ghauth "github.com/cli/go-gh/v2/pkg/auth"
@@ -26,6 +27,7 @@ type HTTPClientOptions struct {
2627
LogColorize bool
2728
LogVerboseHTTP bool
2829
SkipDefaultHeaders bool
30+
TelemetryDisabler ghtelemetry.Disabler
2931
}
3032

3133
func NewHTTPClient(opts HTTPClientOptions) (*http.Client, error) {
@@ -74,6 +76,13 @@ func NewHTTPClient(opts HTTPClientOptions) (*http.Client, error) {
7476
client.Transport = AddAuthTokenHeader(client.Transport, opts.Config)
7577
}
7678

79+
if opts.TelemetryDisabler != nil {
80+
client.Transport = telemetryDisablerTransport{
81+
wrappedTransport: client.Transport,
82+
telemetryDisabler: opts.TelemetryDisabler,
83+
}
84+
}
85+
7786
return client, nil
7887
}
7988

@@ -147,3 +156,15 @@ func getHost(r *http.Request) string {
147156
}
148157
return r.URL.Host
149158
}
159+
160+
type telemetryDisablerTransport struct {
161+
wrappedTransport http.RoundTripper
162+
telemetryDisabler ghtelemetry.Disabler
163+
}
164+
165+
func (t telemetryDisablerTransport) RoundTrip(req *http.Request) (*http.Response, error) {
166+
if ghauth.IsEnterprise(getHost(req)) {
167+
t.telemetryDisabler.Disable()
168+
}
169+
return t.wrappedTransport.RoundTrip(req)
170+
}

0 commit comments

Comments
 (0)