diff --git a/acceptance/telemetry/failure-debug-logs/out.requests.txt b/acceptance/telemetry/failure-debug-logs/out.requests.txt new file mode 100644 index 000000000..69633b190 --- /dev/null +++ b/acceptance/telemetry/failure-debug-logs/out.requests.txt @@ -0,0 +1,51 @@ +{ + "headers": { + "Authorization": [ + "Bearer [DATABRICKS_TOKEN]" + ] + }, + "method": "POST", + "path": "/telemetry-ext", + "body": { + "uploadTime": "UNIX_TIME_MILLIS", + "items": [], + "protoLogs": [ + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\"},\"cli_test_event\":{\"name\":\"VALUE1\"}}}}", + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\"},\"cli_test_event\":{\"name\":\"VALUE2\"}}}}" + ] + } +} +{ + "headers": { + "Authorization": [ + "Bearer [DATABRICKS_TOKEN]" + ] + }, + "method": "POST", + "path": "/telemetry-ext", + "body": { + "uploadTime": "UNIX_TIME_MILLIS", + "items": [], + "protoLogs": [ + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\"},\"cli_test_event\":{\"name\":\"VALUE1\"}}}}", + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\"},\"cli_test_event\":{\"name\":\"VALUE2\"}}}}" + ] + } +} +{ + "headers": { + "Authorization": [ + "Bearer [DATABRICKS_TOKEN]" + ] + }, + "method": "POST", + "path": "/telemetry-ext", + "body": { + "uploadTime": "UNIX_TIME_MILLIS", + "items": [], + "protoLogs": [ + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\"},\"cli_test_event\":{\"name\":\"VALUE1\"}}}}", + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\"},\"cli_test_event\":{\"name\":\"VALUE2\"}}}}" + ] + } +} diff --git a/acceptance/telemetry/failure-debug-logs/output.txt b/acceptance/telemetry/failure-debug-logs/output.txt new file mode 100644 index 000000000..3221edf58 --- /dev/null +++ b/acceptance/telemetry/failure-debug-logs/output.txt @@ -0,0 +1,56 @@ + +>>> [CLI] selftest send-telemetry --debug +HH:MM:SS Info: start pid=PID version=[DEV_VERSION] args="[CLI], selftest, send-telemetry, --debug" +HH:MM:SS Info: completed execution pid=PID exit_code=0 +HH:MM:SS Debug: POST /telemetry-ext +> { +> "items": null, +> "protoLogs": [ +> "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"... (210 more bytes)", +> "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"... (210 more bytes)" +> ], +> "uploadTime": "UNIX_TIME_MILLIS" +> } +< HTTP/1.1 501 Not Implemented +< { +< "error_code": "ERROR_CODE", +< "message": "Endpoint not implemented." +< } pid=PID sdk=true +HH:MM:SS Debug: non-retriable error: Endpoint not implemented. pid=PID sdk=true +HH:MM:SS Debug: Attempt 1 failed due to a server side error. Retrying status code: 501 + pid=PID +HH:MM:SS Debug: POST /telemetry-ext +> { +> "items": null, +> "protoLogs": [ +> "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"... (210 more bytes)", +> "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"... (210 more bytes)" +> ], +> "uploadTime": "UNIX_TIME_MILLIS" +> } +< HTTP/1.1 501 Not Implemented +< { +< "error_code": "ERROR_CODE", +< "message": "Endpoint not implemented." +< } pid=PID sdk=true +HH:MM:SS Debug: non-retriable error: Endpoint not implemented. pid=PID sdk=true +HH:MM:SS Debug: Attempt 2 failed due to a server side error. Retrying status code: 501 + pid=PID +HH:MM:SS Debug: POST /telemetry-ext +> { +> "items": null, +> "protoLogs": [ +> "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"... (210 more bytes)", +> "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"... (210 more bytes)" +> ], +> "uploadTime": "UNIX_TIME_MILLIS" +> } +< HTTP/1.1 501 Not Implemented +< { +< "error_code": "ERROR_CODE", +< "message": "Endpoint not implemented." +< } pid=PID sdk=true +HH:MM:SS Debug: non-retriable error: Endpoint not implemented. pid=PID sdk=true +HH:MM:SS Debug: Attempt 3 failed due to a server side error. Retrying status code: 501 + pid=PID +HH:MM:SS Debug: failed to upload telemetry: failed to upload telemetry logs after three attempts pid=PID diff --git a/acceptance/telemetry/failure-debug-logs/script b/acceptance/telemetry/failure-debug-logs/script new file mode 100644 index 000000000..7fe85f0cd --- /dev/null +++ b/acceptance/telemetry/failure-debug-logs/script @@ -0,0 +1 @@ +trace $CLI selftest send-telemetry --debug diff --git a/acceptance/telemetry/failure-debug-logs/test.toml b/acceptance/telemetry/failure-debug-logs/test.toml new file mode 100644 index 000000000..7b441d48d --- /dev/null +++ b/acceptance/telemetry/failure-debug-logs/test.toml @@ -0,0 +1,17 @@ +[[Server]] +Pattern = "POST /telemetry-ext" +Response.Body = ''' +{ + "error_code": "ERROR_CODE", + "message": "Endpoint not implemented." +} +''' +Response.StatusCode = 501 + +[[Repls]] +Old = "(?:[01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]" +New = "HH:MM:SS" + +[[Repls]] +Old = "pid=[0-9]+" +New = "pid=PID" diff --git a/acceptance/telemetry/failure/out.requests.txt b/acceptance/telemetry/failure/out.requests.txt new file mode 100644 index 000000000..69633b190 --- /dev/null +++ b/acceptance/telemetry/failure/out.requests.txt @@ -0,0 +1,51 @@ +{ + "headers": { + "Authorization": [ + "Bearer [DATABRICKS_TOKEN]" + ] + }, + "method": "POST", + "path": "/telemetry-ext", + "body": { + "uploadTime": "UNIX_TIME_MILLIS", + "items": [], + "protoLogs": [ + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\"},\"cli_test_event\":{\"name\":\"VALUE1\"}}}}", + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\"},\"cli_test_event\":{\"name\":\"VALUE2\"}}}}" + ] + } +} +{ + "headers": { + "Authorization": [ + "Bearer [DATABRICKS_TOKEN]" + ] + }, + "method": "POST", + "path": "/telemetry-ext", + "body": { + "uploadTime": "UNIX_TIME_MILLIS", + "items": [], + "protoLogs": [ + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\"},\"cli_test_event\":{\"name\":\"VALUE1\"}}}}", + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\"},\"cli_test_event\":{\"name\":\"VALUE2\"}}}}" + ] + } +} +{ + "headers": { + "Authorization": [ + "Bearer [DATABRICKS_TOKEN]" + ] + }, + "method": "POST", + "path": "/telemetry-ext", + "body": { + "uploadTime": "UNIX_TIME_MILLIS", + "items": [], + "protoLogs": [ + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\"},\"cli_test_event\":{\"name\":\"VALUE1\"}}}}", + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\"},\"cli_test_event\":{\"name\":\"VALUE2\"}}}}" + ] + } +} diff --git a/acceptance/telemetry/failure/output.txt b/acceptance/telemetry/failure/output.txt new file mode 100644 index 000000000..78d1c02c4 --- /dev/null +++ b/acceptance/telemetry/failure/output.txt @@ -0,0 +1,2 @@ + +>>> [CLI] selftest send-telemetry diff --git a/acceptance/telemetry/failure/script b/acceptance/telemetry/failure/script new file mode 100644 index 000000000..a5bda47cf --- /dev/null +++ b/acceptance/telemetry/failure/script @@ -0,0 +1 @@ +trace $CLI selftest send-telemetry diff --git a/acceptance/telemetry/failure/test.toml b/acceptance/telemetry/failure/test.toml new file mode 100644 index 000000000..a7fa942e4 --- /dev/null +++ b/acceptance/telemetry/failure/test.toml @@ -0,0 +1,9 @@ +[[Server]] +Pattern = "POST /telemetry-ext" +Response.Body = ''' +{ + "error_code": "ERROR_CODE", + "message": "Endpoint not implemented." +} +''' +Response.StatusCode = 501 diff --git a/acceptance/telemetry/partial-success-debug-logs/out.requests.txt b/acceptance/telemetry/partial-success-debug-logs/out.requests.txt new file mode 100644 index 000000000..d7b4c5713 --- /dev/null +++ b/acceptance/telemetry/partial-success-debug-logs/out.requests.txt @@ -0,0 +1,51 @@ +{ + "headers": { + "Authorization": [ + "Bearer [DATABRICKS_TOKEN]" + ] + }, + "method": "POST", + "path": "/telemetry-ext", + "body": { + "uploadTime": "UNIX_TIME_MILLIS", + "items": [], + "protoLogs": [ + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\",\"execution_time_ms\":1},\"cli_test_event\":{\"name\":\"VALUE1\"}}}}", + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\",\"execution_time_ms\":1},\"cli_test_event\":{\"name\":\"VALUE2\"}}}}" + ] + } +} +{ + "headers": { + "Authorization": [ + "Bearer [DATABRICKS_TOKEN]" + ] + }, + "method": "POST", + "path": "/telemetry-ext", + "body": { + "uploadTime": "UNIX_TIME_MILLIS", + "items": [], + "protoLogs": [ + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\",\"execution_time_ms\":1},\"cli_test_event\":{\"name\":\"VALUE1\"}}}}", + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\",\"execution_time_ms\":1},\"cli_test_event\":{\"name\":\"VALUE2\"}}}}" + ] + } +} +{ + "headers": { + "Authorization": [ + "Bearer [DATABRICKS_TOKEN]" + ] + }, + "method": "POST", + "path": "/telemetry-ext", + "body": { + "uploadTime": "UNIX_TIME_MILLIS", + "items": [], + "protoLogs": [ + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\",\"execution_time_ms\":1},\"cli_test_event\":{\"name\":\"VALUE1\"}}}}", + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\",\"execution_time_ms\":1},\"cli_test_event\":{\"name\":\"VALUE2\"}}}}" + ] + } +} diff --git a/acceptance/telemetry/partial-success-debug-logs/output.txt b/acceptance/telemetry/partial-success-debug-logs/output.txt new file mode 100644 index 000000000..7f74b1318 --- /dev/null +++ b/acceptance/telemetry/partial-success-debug-logs/output.txt @@ -0,0 +1,53 @@ + +>>> [CLI] selftest send-telemetry --debug +HH:MM:SS Info: start pid=PID version=[DEV_VERSION] args="[CLI], selftest, send-telemetry, --debug" +HH:MM:SS Info: completed execution pid=PID exit_code=0 +HH:MM:SS Debug: POST /telemetry-ext +> { +> "items": null, +> "protoLogs": [ +> "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"... (232 more bytes)", +> "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"... (232 more bytes)" +> ], +> "uploadTime": "UNIX_TIME_MILLIS" +> } +< HTTP/1.1 200 OK +< { +< "errors": null, +< "numProtoSuccess": 1 +< } pid=PID sdk=true +HH:MM:SS Debug: Attempt 1 was a partial success. Number of logs uploaded: 1 out of 2 + pid=PID +HH:MM:SS Debug: POST /telemetry-ext +> { +> "items": null, +> "protoLogs": [ +> "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"... (232 more bytes)", +> "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"... (232 more bytes)" +> ], +> "uploadTime": "UNIX_TIME_MILLIS" +> } +< HTTP/1.1 200 OK +< { +< "errors": null, +< "numProtoSuccess": 1 +< } pid=PID sdk=true +HH:MM:SS Debug: Attempt 2 was a partial success. Number of logs uploaded: 1 out of 2 + pid=PID +HH:MM:SS Debug: POST /telemetry-ext +> { +> "items": null, +> "protoLogs": [ +> "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"... (232 more bytes)", +> "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"... (232 more bytes)" +> ], +> "uploadTime": "UNIX_TIME_MILLIS" +> } +< HTTP/1.1 200 OK +< { +< "errors": null, +< "numProtoSuccess": 1 +< } pid=PID sdk=true +HH:MM:SS Debug: Attempt 3 was a partial success. Number of logs uploaded: 1 out of 2 + pid=PID +HH:MM:SS Debug: failed to upload telemetry: failed to upload telemetry logs after three attempts pid=PID diff --git a/acceptance/telemetry/partial-success-debug-logs/script b/acceptance/telemetry/partial-success-debug-logs/script new file mode 100644 index 000000000..7fe85f0cd --- /dev/null +++ b/acceptance/telemetry/partial-success-debug-logs/script @@ -0,0 +1 @@ +trace $CLI selftest send-telemetry --debug diff --git a/acceptance/telemetry/partial-success-debug-logs/test.toml b/acceptance/telemetry/partial-success-debug-logs/test.toml new file mode 100644 index 000000000..c37b01569 --- /dev/null +++ b/acceptance/telemetry/partial-success-debug-logs/test.toml @@ -0,0 +1,17 @@ +[[Server]] +Pattern = "POST /telemetry-ext" +Response.Body = ''' +{ + "errors": [], + "numProtoSuccess": 1 +} +''' + + +[[Repls]] +Old = "(?:[01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]" +New = "HH:MM:SS" + +[[Repls]] +Old = "pid=[0-9]+" +New = "pid=PID" diff --git a/acceptance/telemetry/partial-success/out.requests.txt b/acceptance/telemetry/partial-success/out.requests.txt new file mode 100644 index 000000000..69633b190 --- /dev/null +++ b/acceptance/telemetry/partial-success/out.requests.txt @@ -0,0 +1,51 @@ +{ + "headers": { + "Authorization": [ + "Bearer [DATABRICKS_TOKEN]" + ] + }, + "method": "POST", + "path": "/telemetry-ext", + "body": { + "uploadTime": "UNIX_TIME_MILLIS", + "items": [], + "protoLogs": [ + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\"},\"cli_test_event\":{\"name\":\"VALUE1\"}}}}", + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\"},\"cli_test_event\":{\"name\":\"VALUE2\"}}}}" + ] + } +} +{ + "headers": { + "Authorization": [ + "Bearer [DATABRICKS_TOKEN]" + ] + }, + "method": "POST", + "path": "/telemetry-ext", + "body": { + "uploadTime": "UNIX_TIME_MILLIS", + "items": [], + "protoLogs": [ + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\"},\"cli_test_event\":{\"name\":\"VALUE1\"}}}}", + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\"},\"cli_test_event\":{\"name\":\"VALUE2\"}}}}" + ] + } +} +{ + "headers": { + "Authorization": [ + "Bearer [DATABRICKS_TOKEN]" + ] + }, + "method": "POST", + "path": "/telemetry-ext", + "body": { + "uploadTime": "UNIX_TIME_MILLIS", + "items": [], + "protoLogs": [ + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\"},\"cli_test_event\":{\"name\":\"VALUE1\"}}}}", + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\"},\"cli_test_event\":{\"name\":\"VALUE2\"}}}}" + ] + } +} diff --git a/acceptance/telemetry/partial-success/output.txt b/acceptance/telemetry/partial-success/output.txt new file mode 100644 index 000000000..78d1c02c4 --- /dev/null +++ b/acceptance/telemetry/partial-success/output.txt @@ -0,0 +1,2 @@ + +>>> [CLI] selftest send-telemetry diff --git a/acceptance/telemetry/partial-success/script b/acceptance/telemetry/partial-success/script new file mode 100644 index 000000000..a5bda47cf --- /dev/null +++ b/acceptance/telemetry/partial-success/script @@ -0,0 +1 @@ +trace $CLI selftest send-telemetry diff --git a/acceptance/telemetry/partial-success/test.toml b/acceptance/telemetry/partial-success/test.toml new file mode 100644 index 000000000..4e2bd9fab --- /dev/null +++ b/acceptance/telemetry/partial-success/test.toml @@ -0,0 +1,8 @@ +[[Server]] +Pattern = "POST /telemetry-ext" +Response.Body = ''' +{ + "errors": [], + "numProtoSuccess": 1 +} +''' diff --git a/acceptance/telemetry/skipped/output.txt b/acceptance/telemetry/skipped/output.txt new file mode 100644 index 000000000..78d1c02c4 --- /dev/null +++ b/acceptance/telemetry/skipped/output.txt @@ -0,0 +1,2 @@ + +>>> [CLI] selftest send-telemetry diff --git a/acceptance/telemetry/skipped/script b/acceptance/telemetry/skipped/script new file mode 100644 index 000000000..31f5dfb43 --- /dev/null +++ b/acceptance/telemetry/skipped/script @@ -0,0 +1,3 @@ +export DATABRICKS_CLI_DISABLE_TELEMETRY="true" + +trace $CLI selftest send-telemetry diff --git a/acceptance/telemetry/success/out.requests.txt b/acceptance/telemetry/success/out.requests.txt new file mode 100644 index 000000000..6fb834be7 --- /dev/null +++ b/acceptance/telemetry/success/out.requests.txt @@ -0,0 +1,17 @@ +{ + "headers": { + "Authorization": [ + "Bearer [DATABRICKS_TOKEN]" + ] + }, + "method": "POST", + "path": "/telemetry-ext", + "body": { + "uploadTime": "UNIX_TIME_MILLIS", + "items": [], + "protoLogs": [ + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\"},\"cli_test_event\":{\"name\":\"VALUE1\"}}}}", + "{\"frontend_log_event_id\":\"[UUID]\",\"entry\":{\"databricks_cli_log\":{\"execution_context\":{\"cmd_exec_id\":\"[UUID]\",\"version\":\"[DEV_VERSION]\",\"command\":\"selftest_send-telemetry\",\"operating_system\":\"[OS]\"},\"cli_test_event\":{\"name\":\"VALUE2\"}}}}" + ] + } +} diff --git a/acceptance/telemetry/success/output.txt b/acceptance/telemetry/success/output.txt new file mode 100644 index 000000000..78d1c02c4 --- /dev/null +++ b/acceptance/telemetry/success/output.txt @@ -0,0 +1,2 @@ + +>>> [CLI] selftest send-telemetry diff --git a/acceptance/telemetry/success/script b/acceptance/telemetry/success/script new file mode 100644 index 000000000..a5bda47cf --- /dev/null +++ b/acceptance/telemetry/success/script @@ -0,0 +1 @@ +trace $CLI selftest send-telemetry diff --git a/acceptance/telemetry/test.toml b/acceptance/telemetry/test.toml new file mode 100644 index 000000000..a7f8adeb3 --- /dev/null +++ b/acceptance/telemetry/test.toml @@ -0,0 +1,26 @@ +IncludeRequestHeaders = ["Authorization"] +RecordRequests = true + +Local = true +Cloud = false + +[[Repls]] +Old = '17\d{11}' +New = '"UNIX_TIME_MILLIS"' + +[[Repls]] +Old = 'darwin|linux|windows' +New = '[OS]' + +[[Repls]] +Old = 'execution_time_ms\\\":\d{1,5},' +New = 'execution_time_ms\":\"SMALL_INT\",' + +[[Server]] +Pattern = "POST /telemetry-ext" +Response.Body = ''' +{ + "errors": [], + "numProtoSuccess": 2 +} +''' diff --git a/cmd/root/root.go b/cmd/root/root.go index 04815f48b..7b858b12d 100644 --- a/cmd/root/root.go +++ b/cmd/root/root.go @@ -6,13 +6,18 @@ import ( "fmt" "log/slog" "os" + "runtime" "runtime/debug" "strings" + "time" "github.com/databricks/cli/internal/build" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/dbr" + "github.com/databricks/cli/libs/env" "github.com/databricks/cli/libs/log" + "github.com/databricks/cli/libs/telemetry" + "github.com/databricks/cli/libs/telemetry/protos" "github.com/spf13/cobra" ) @@ -74,9 +79,6 @@ func New(ctx context.Context) *cobra.Command { // get the context back ctx = cmd.Context() - // Detect if the CLI is running on DBR and store this on the context. - ctx = dbr.DetectRuntime(ctx) - // Configure our user agent with the command that's about to be executed. ctx = withCommandInUserAgent(ctx, cmd) ctx = withCommandExecIdInUserAgent(ctx) @@ -124,6 +126,14 @@ Stack Trace: %s`, version, r, string(trace)) }() + // Configure a telemetry logger and store it in the context. + ctx = telemetry.WithNewLogger(ctx) + + // Detect if the CLI is running on DBR and store this on the context. + ctx = dbr.DetectRuntime(ctx) + + startTime := time.Now() + // Run the command cmd, err = cmd.ExecuteContextC(ctx) if err != nil && !errors.Is(err, ErrAlreadyPrinted) { @@ -151,5 +161,38 @@ Stack Trace: } } + exitCode := 0 + if err != nil { + exitCode = 1 + } + + uploadTelemetry(cmd.Context(), commandString(cmd), startTime, exitCode) return err } + +func uploadTelemetry(ctx context.Context, cmdStr string, startTime time.Time, exitCode int) { + // Return early if there are no logs to upload. + if !telemetry.HasLogs(ctx) { + return + } + + // Telemetry is disabled. We don't upload logs. + if os.Getenv(telemetry.DisableEnvVar) != "" { + return + } + + telemetry.SetExecutionContext(ctx, protos.ExecutionContext{ + CmdExecID: cmdExecId, + Version: build.GetInfo().Version, + Command: cmdStr, + OperatingSystem: runtime.GOOS, + DbrVersion: env.Get(ctx, dbr.EnvVarName), + ExecutionTimeMs: time.Since(startTime).Milliseconds(), + ExitCode: int64(exitCode), + }) + + err := telemetry.Upload(ctx, ConfigUsed(ctx)) + if err != nil { + log.Debugf(ctx, "failed to upload telemetry: %v", err) + } +} diff --git a/cmd/root/user_agent_command_exec_id.go b/cmd/root/user_agent_command_exec_id.go index 3bf32b703..e3416983d 100644 --- a/cmd/root/user_agent_command_exec_id.go +++ b/cmd/root/user_agent_command_exec_id.go @@ -7,8 +7,10 @@ import ( "github.com/google/uuid" ) +var cmdExecId = uuid.New().String() + func withCommandExecIdInUserAgent(ctx context.Context) context.Context { // A UUID that will allow us to correlate multiple API requests made by // the same CLI invocation. - return useragent.InContext(ctx, "cmd-exec-id", uuid.New().String()) + return useragent.InContext(ctx, "cmd-exec-id", cmdExecId) } diff --git a/cmd/selftest/selftest.go b/cmd/selftest/selftest.go index 7d8cfcb76..b3e8541d4 100644 --- a/cmd/selftest/selftest.go +++ b/cmd/selftest/selftest.go @@ -11,6 +11,7 @@ func New() *cobra.Command { Hidden: true, } + cmd.AddCommand(newSendTelemetry()) cmd.AddCommand(newPanic()) return cmd } diff --git a/cmd/selftest/send_telemetry.go b/cmd/selftest/send_telemetry.go new file mode 100644 index 000000000..b091058e7 --- /dev/null +++ b/cmd/selftest/send_telemetry.go @@ -0,0 +1,29 @@ +package selftest + +import ( + "github.com/databricks/cli/cmd/root" + "github.com/databricks/cli/libs/telemetry" + "github.com/databricks/cli/libs/telemetry/protos" + "github.com/spf13/cobra" +) + +func newSendTelemetry() *cobra.Command { + cmd := &cobra.Command{ + Use: "send-telemetry", + Short: "log some test telemetry events", + PreRunE: root.MustWorkspaceClient, + } + + cmd.RunE = func(cmd *cobra.Command, args []string) error { + for _, v := range []string{"VALUE1", "VALUE2"} { + telemetry.Log(cmd.Context(), protos.DatabricksCliLog{ + CliTestEvent: &protos.CliTestEvent{ + Name: protos.DummyCliEnum(v), + }, + }) + } + return nil + } + + return cmd +} diff --git a/libs/dbr/detect.go b/libs/dbr/detect.go index d8b4dfe20..12b83dc8c 100644 --- a/libs/dbr/detect.go +++ b/libs/dbr/detect.go @@ -11,6 +11,8 @@ import ( // Dereference [os.Stat] to allow mocking in tests. var statFunc = os.Stat +const EnvVarName = "DATABRICKS_RUNTIME_VERSION" + // detect returns true if the current process is running on a Databricks Runtime. // Its return value is meant to be cached in the context. func detect(ctx context.Context) bool { @@ -21,7 +23,7 @@ func detect(ctx context.Context) bool { } // Databricks Runtime always has the DATABRICKS_RUNTIME_VERSION environment variable set. - if value, ok := env.Lookup(ctx, "DATABRICKS_RUNTIME_VERSION"); !ok || value == "" { + if value, ok := env.Lookup(ctx, EnvVarName); !ok || value == "" { return false } diff --git a/libs/telemetry/logger.go b/libs/telemetry/logger.go index b93ab94dd..5cc702f89 100644 --- a/libs/telemetry/logger.go +++ b/libs/telemetry/logger.go @@ -16,22 +16,22 @@ import ( "github.com/google/uuid" ) +// Environment variable to disable telemetry. If this is set to any value, telemetry +// will be disabled. +const DisableEnvVar = "DATABRICKS_CLI_DISABLE_TELEMETRY" + func Log(ctx context.Context, event protos.DatabricksCliLog) { fromContext(ctx).log(event) } -func GetLogs(ctx context.Context) []protos.FrontendLog { - return fromContext(ctx).getLogs() +func SetExecutionContext(ctx context.Context, ec protos.ExecutionContext) { + fromContext(ctx).setExecutionContext(ec) } func HasLogs(ctx context.Context) bool { return len(fromContext(ctx).getLogs()) > 0 } -func SetExecutionContext(ctx context.Context, ec protos.ExecutionContext) { - fromContext(ctx).setExecutionContext(ec) -} - type logger struct { logs []protos.FrontendLog } @@ -63,7 +63,7 @@ func (l *logger) setExecutionContext(ec protos.ExecutionContext) { func Upload(ctx context.Context, cfg *config.Config) error { l := fromContext(ctx) if len(l.logs) == 0 { - return nil + return fmt.Errorf("no logs to upload") } protoLogs := make([]string, len(l.logs))