databricks-cli/libs/telemetry/logger_test.go

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

156 lines
3.5 KiB
Go
Raw Normal View History

Add synchronous logger for telemetry (#2432) ## Changes This PR adds a synchronous telemetry logger for the CLI with a max timeout of 3 seconds. Due to the 3-second timeout configuration, this is only meant to be used in long-running commands. This is a short-term solution. Eventually, we'd like to transition to a daemon process to upload the telemetry logs to amortise the costs of configuring authentication and maintaining a warm pool of HTTP connections, as well as a better UX for the end user. Note that users can set the `DATABRICKS_CLI_DISABLE_TELEMETRY` environment variable to disable telemetry collection. ## Why To collect telemetry, which was previously inaccessible to us, and answer questions like which templates customers like to use and which DABs features would be safe to deprecate. ## Tests Unit and acceptance tests. Also manually verified that the telemetry upload works: ``` (artifact-playground) ➜ cli git:(sync-logger) cli selftest send-telemetry --debug 15:58:20 Info: start pid=40386 version=0.0.0-dev+a2825ca89a23 args="cli, selftest, send-telemetry, --debug" 15:58:20 Debug: Loading DEFAULT profile from /Users/shreyas.goenka/.databrickscfg pid=40386 sdk=true 15:58:20 Info: completed execution pid=40386 exit_code=0 15:58:21 Debug: POST /telemetry-ext > { > "items": null, > "protoLogs": [ > "{\"frontend_log_event_id\":\"82d29b3a-d5ff-48f3-8a21-dae6e08d2999\",\"entry\":{\"databricks_cli_log\":{\"... (232 more bytes)", > "{\"frontend_log_event_id\":\"d6be8220-7db8-45d9-97d6-4c09c25e2664\",\"entry\":{\"databricks_cli_log\":{\"... (232 more bytes)" > ], > "uploadTime": 1741186700967 > } < HTTP/2.0 200 OK < { < "errors": null, < "numProtoSuccess": 2, < "numSuccess": 0 < } pid=40386 sdk=true ```
2025-03-12 13:05:10 +00:00
package telemetry
import (
"context"
"testing"
"github.com/databricks/cli/libs/command"
"github.com/databricks/cli/libs/telemetry/protos"
"github.com/databricks/cli/libs/testserver"
"github.com/databricks/databricks-sdk-go/apierr"
"github.com/databricks/databricks-sdk-go/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestTelemetryUploadRetriesOnPartialSuccess(t *testing.T) {
server := testserver.New(t)
t.Cleanup(server.Close)
count := 0
server.Handle("POST", "/telemetry-ext", func(req testserver.Request) any {
count++
if count == 1 {
return ResponseBody{
NumProtoSuccess: 1,
}
}
if count == 2 {
return ResponseBody{
NumProtoSuccess: 2,
}
}
return nil
})
ctx := WithNewLogger(context.Background())
Log(ctx, protos.DatabricksCliLog{
CliTestEvent: &protos.CliTestEvent{
Name: protos.DummyCliEnumValue1,
},
})
Log(ctx, protos.DatabricksCliLog{
CliTestEvent: &protos.CliTestEvent{
Name: protos.DummyCliEnumValue2,
},
})
ctx = command.SetConfigUsed(ctx, &config.Config{
Host: server.URL,
Token: "token",
})
err := Upload(ctx, protos.ExecutionContext{})
require.NoError(t, err)
assert.Equal(t, 2, count)
}
func uploadRetriesFor(t *testing.T, statusCode int) {
server := testserver.New(t)
t.Cleanup(server.Close)
count := 0
server.Handle("POST", "/telemetry-ext", func(req testserver.Request) any {
count++
if count == 1 {
return testserver.Response{
StatusCode: statusCode,
Body: apierr.APIError{
StatusCode: statusCode,
Message: "Some error",
},
}
}
if count == 2 {
return ResponseBody{
NumProtoSuccess: 2,
}
}
return nil
})
t.Setenv("DATABRICKS_HOST", server.URL)
t.Setenv("DATABRICKS_TOKEN", "token")
ctx := WithNewLogger(context.Background())
Log(ctx, protos.DatabricksCliLog{
CliTestEvent: &protos.CliTestEvent{
Name: protos.DummyCliEnumValue1,
},
})
Log(ctx, protos.DatabricksCliLog{
CliTestEvent: &protos.CliTestEvent{
Name: protos.DummyCliEnumValue2,
},
})
ctx = command.SetConfigUsed(ctx, &config.Config{
Host: server.URL,
Token: "token",
})
err := Upload(ctx, protos.ExecutionContext{})
require.NoError(t, err)
assert.Equal(t, 2, count)
}
func TestTelemetryUploadRetriesForStatusCodes(t *testing.T) {
// These retries happen in the CLI itself since the SDK does not automatically
// retry for 5xx errors.
uploadRetriesFor(t, 500)
uploadRetriesFor(t, 504)
// These retries happen on the SDK layer.
// ref: https://github.com/databricks/databricks-sdk-go/blob/cdb28002afacb8b762348534a4c4040a9f19c24b/apierr/errors.go#L91
uploadRetriesFor(t, 503)
uploadRetriesFor(t, 429)
}
func TestTelemetryUploadMaxRetries(t *testing.T) {
server := testserver.New(t)
t.Cleanup(server.Close)
count := 0
server.Handle("POST", "/telemetry-ext", func(req testserver.Request) any {
count++
return ResponseBody{
NumProtoSuccess: 1,
}
})
t.Setenv("DATABRICKS_HOST", server.URL)
t.Setenv("DATABRICKS_TOKEN", "token")
ctx := WithNewLogger(context.Background())
Log(ctx, protos.DatabricksCliLog{
CliTestEvent: &protos.CliTestEvent{
Name: protos.DummyCliEnumValue1,
},
})
Log(ctx, protos.DatabricksCliLog{
CliTestEvent: &protos.CliTestEvent{
Name: protos.DummyCliEnumValue2,
},
})
ctx = command.SetConfigUsed(ctx, &config.Config{
Host: server.URL,
Token: "token",
})
err := Upload(ctx, protos.ExecutionContext{})
assert.EqualError(t, err, "failed to upload telemetry logs after three attempts")
assert.Equal(t, 3, count)
}