Skip to content

Commit 5583c3f

Browse files
jongioCopilot
andcommitted
feat: comprehensive telemetry audit - add command-specific usage attributes
- Add telemetry to auth, config, env, hooks, templates, pipeline, monitor, show, infra commands - Add 16 new telemetry field constants for command-specific attributes - Fix user identity tracking with Anonymous account type fallback - Fix flaky TestStateCacheManager_TTL timing issue - Add audit documentation: feature matrix, schema, privacy checklist, audit process - Add telemetry field contract tests and CI coverage check Resolves #1772 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 1902d5c commit 5583c3f

21 files changed

Lines changed: 1329 additions & 8 deletions

cli/azd/cmd/auth_login.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import (
2020
"github.com/azure/azure-dev/cli/azd/cmd/actions"
2121
"github.com/azure/azure-dev/cli/azd/internal"
2222
"github.com/azure/azure-dev/cli/azd/internal/runcontext"
23+
"github.com/azure/azure-dev/cli/azd/internal/tracing"
24+
"github.com/azure/azure-dev/cli/azd/internal/tracing/fields"
2325
"github.com/azure/azure-dev/cli/azd/pkg/account"
2426
"github.com/azure/azure-dev/cli/azd/pkg/auth"
2527
"github.com/azure/azure-dev/cli/azd/pkg/contracts"
@@ -307,6 +309,7 @@ func (la *loginAction) Run(ctx context.Context) (*actions.ActionResult, error) {
307309
}
308310

309311
if la.flags.onlyCheckStatus {
312+
tracing.SetUsageAttributes(fields.AuthMethodKey.String("check-status"))
310313
// In check status mode, we always print the final status to stdout.
311314
// We print any non-setup related errors to stderr.
312315
// We always return a zero exit code.
@@ -452,6 +455,11 @@ func runningOnCodespacesBrowser(ctx context.Context, commandRunner exec.CommandR
452455
}
453456

454457
func (la *loginAction) login(ctx context.Context) error {
458+
// Track hashed tenant ID if provided (before resolving from env vars)
459+
if la.flags.tenantID != "" {
460+
tracing.SetUsageAttributes(fields.StringHashed(fields.TenantIdKey, la.flags.tenantID))
461+
}
462+
455463
if la.flags.federatedTokenProvider == azurePipelinesProvider {
456464
if la.flags.clientID == "" {
457465
log.Printf("setting client id from environment variable %s", azurePipelinesClientIDEnvVarName)
@@ -465,6 +473,7 @@ func (la *loginAction) login(ctx context.Context) error {
465473
}
466474

467475
if la.flags.managedIdentity {
476+
tracing.SetUsageAttributes(fields.AuthMethodKey.String("managed-identity"))
468477
if _, err := la.authManager.LoginWithManagedIdentity(
469478
ctx, la.flags.clientID,
470479
); err != nil {
@@ -494,6 +503,7 @@ func (la *loginAction) login(ctx context.Context) error {
494503

495504
switch {
496505
case la.flags.clientSecret.ptr != nil:
506+
tracing.SetUsageAttributes(fields.AuthMethodKey.String("service-principal-secret"))
497507
if *la.flags.clientSecret.ptr == "" {
498508
v, err := la.console.Prompt(ctx, input.ConsoleOptions{
499509
Message: "Enter your client secret",
@@ -510,6 +520,7 @@ func (la *loginAction) login(ctx context.Context) error {
510520
return fmt.Errorf("logging in: %w", err)
511521
}
512522
case la.flags.clientCertificate != "":
523+
tracing.SetUsageAttributes(fields.AuthMethodKey.String("service-principal-certificate"))
513524
certFile, err := os.Open(la.flags.clientCertificate)
514525
if err != nil {
515526
return fmt.Errorf("reading certificate: %w", err)
@@ -527,12 +538,14 @@ func (la *loginAction) login(ctx context.Context) error {
527538
return fmt.Errorf("logging in: %w", err)
528539
}
529540
case la.flags.federatedTokenProvider == "github":
541+
tracing.SetUsageAttributes(fields.AuthMethodKey.String("federated-github"))
530542
if _, err := la.authManager.LoginWithGitHubFederatedTokenProvider(
531543
ctx, la.flags.tenantID, la.flags.clientID,
532544
); err != nil {
533545
return fmt.Errorf("logging in: %w", err)
534546
}
535547
case la.flags.federatedTokenProvider == azurePipelinesProvider:
548+
tracing.SetUsageAttributes(fields.AuthMethodKey.String("federated-azure-pipelines"))
536549
serviceConnectionID := os.Getenv(azurePipelinesServiceConnectionIDEnvVarName)
537550

538551
if serviceConnectionID == "" {
@@ -546,6 +559,7 @@ func (la *loginAction) login(ctx context.Context) error {
546559
return fmt.Errorf("logging in: %w", err)
547560
}
548561
case la.flags.federatedTokenProvider == "oidc": // generic oidc provider
562+
tracing.SetUsageAttributes(fields.AuthMethodKey.String("federated-oidc"))
549563
if _, err := la.authManager.LoginWithOidcFederatedTokenProvider(
550564
ctx, la.flags.tenantID, la.flags.clientID,
551565
); err != nil {
@@ -557,6 +571,7 @@ func (la *loginAction) login(ctx context.Context) error {
557571
}
558572

559573
if la.authManager.UseExternalAuth() {
574+
tracing.SetUsageAttributes(fields.AuthMethodKey.String("external"))
560575
// Request a token and assume the external auth system will prompt the user to log in.
561576
//
562577
// TODO(ellismg): We may want instead to call some explicit `/login` endpoint on the external auth system instead
@@ -581,6 +596,7 @@ func (la *loginAction) login(ctx context.Context) error {
581596
}
582597

583598
if useDevCode {
599+
tracing.SetUsageAttributes(fields.AuthMethodKey.String("device-code"))
584600
_, err = la.authManager.LoginWithDeviceCode(ctx, la.flags.tenantID, la.flags.scopes, claims,
585601
func(url string) error {
586602
if !la.flags.global.NoPrompt {
@@ -598,8 +614,10 @@ func (la *loginAction) login(ctx context.Context) error {
598614
}
599615

600616
if oneauth.Supported && !la.flags.browser {
617+
tracing.SetUsageAttributes(fields.AuthMethodKey.String("oneauth"))
601618
err = la.authManager.LoginWithOneAuth(ctx, la.flags.tenantID, la.flags.scopes)
602619
} else {
620+
tracing.SetUsageAttributes(fields.AuthMethodKey.String("browser"))
603621
_, err = la.authManager.LoginInteractive(ctx, la.flags.scopes, claims,
604622
&auth.LoginInteractiveOptions{
605623
TenantID: la.flags.tenantID,

cli/azd/cmd/auth_logout.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"io"
1010

1111
"github.com/azure/azure-dev/cli/azd/cmd/actions"
12+
"github.com/azure/azure-dev/cli/azd/internal/tracing"
13+
"github.com/azure/azure-dev/cli/azd/internal/tracing/fields"
1214
"github.com/azure/azure-dev/cli/azd/pkg/account"
1315
"github.com/azure/azure-dev/cli/azd/pkg/auth"
1416
"github.com/azure/azure-dev/cli/azd/pkg/input"
@@ -54,6 +56,8 @@ func newLogoutAction(
5456
}
5557

5658
func (la *logoutAction) Run(ctx context.Context) (*actions.ActionResult, error) {
59+
tracing.SetUsageAttributes(fields.AuthMethodKey.String("logout"))
60+
5761
if la.annotations[loginCmdParentAnnotation] == "" {
5862
fmt.Fprintln(
5963
la.console.Handles().Stderr,

cli/azd/cmd/auth_status.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import (
1414
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
1515
"github.com/azure/azure-dev/cli/azd/cmd/actions"
1616
"github.com/azure/azure-dev/cli/azd/internal"
17+
"github.com/azure/azure-dev/cli/azd/internal/tracing"
18+
"github.com/azure/azure-dev/cli/azd/internal/tracing/fields"
1719
"github.com/azure/azure-dev/cli/azd/internal/tracing/resource"
1820
"github.com/azure/azure-dev/cli/azd/pkg/auth"
1921
"github.com/azure/azure-dev/cli/azd/pkg/contracts"
@@ -71,6 +73,7 @@ func newAuthStatusAction(
7173
}
7274

7375
func (a *authStatusAction) Run(ctx context.Context) (*actions.ActionResult, error) {
76+
tracing.SetUsageAttributes(fields.AuthMethodKey.String("check-status"))
7477
loginMode, err := a.authManager.Mode()
7578
if err != nil {
7679
log.Printf("error: fetching auth mode: %v", err)

cli/azd/cmd/config.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import (
1717
"github.com/MakeNowJust/heredoc/v2"
1818
"github.com/azure/azure-dev/cli/azd/cmd/actions"
1919
"github.com/azure/azure-dev/cli/azd/internal"
20+
"github.com/azure/azure-dev/cli/azd/internal/tracing"
21+
"github.com/azure/azure-dev/cli/azd/internal/tracing/fields"
2022
"github.com/azure/azure-dev/cli/azd/pkg/alpha"
2123
"github.com/azure/azure-dev/cli/azd/pkg/config"
2224
"github.com/azure/azure-dev/cli/azd/pkg/input"
@@ -207,6 +209,7 @@ func newConfigShowAction(
207209

208210
// Executes the `azd config show` action
209211
func (a *configShowAction) Run(ctx context.Context) (*actions.ActionResult, error) {
212+
tracing.SetUsageAttributes(fields.ConfigOperationKey.String("show"))
210213
azdConfig, err := a.configManager.Load()
211214
if err != nil {
212215
return nil, err
@@ -276,6 +279,7 @@ func newConfigGetAction(
276279

277280
// Executes the `azd config get <path>` action
278281
func (a *configGetAction) Run(ctx context.Context) (*actions.ActionResult, error) {
282+
tracing.SetUsageAttributes(fields.ConfigOperationKey.String("get"))
279283
azdConfig, err := a.configManager.Load()
280284
if err != nil {
281285
return nil, err
@@ -317,6 +321,7 @@ func newConfigSetAction(configManager config.UserConfigManager, args []string) a
317321

318322
// Executes the `azd config set <path> <value>` action
319323
func (a *configSetAction) Run(ctx context.Context) (*actions.ActionResult, error) {
324+
tracing.SetUsageAttributes(fields.ConfigOperationKey.String("set"))
320325
azdConfig, err := a.configManager.Load()
321326
if err != nil {
322327
return nil, err
@@ -349,6 +354,7 @@ func newConfigUnsetAction(configManager config.UserConfigManager, args []string)
349354

350355
// Executes the `azd config unset <path>` action
351356
func (a *configUnsetAction) Run(ctx context.Context) (*actions.ActionResult, error) {
357+
tracing.SetUsageAttributes(fields.ConfigOperationKey.String("unset"))
352358
azdConfig, err := a.configManager.Load()
353359
if err != nil {
354360
return nil, err
@@ -399,6 +405,7 @@ func newConfigResetAction(
399405

400406
// Executes the `azd config reset` action
401407
func (a *configResetAction) Run(ctx context.Context) (*actions.ActionResult, error) {
408+
tracing.SetUsageAttributes(fields.ConfigOperationKey.String("reset"))
402409
a.console.MessageUxItem(ctx, &ux.MessageTitle{
403410
Title: "Reset configuration (azd config reset)",
404411
})
@@ -473,6 +480,7 @@ type configListAlphaAction struct {
473480
}
474481

475482
func (a *configListAlphaAction) Run(ctx context.Context) (*actions.ActionResult, error) {
483+
tracing.SetUsageAttributes(fields.ConfigOperationKey.String("list-alpha"))
476484
features, err := a.alphaFeaturesManager.ListFeatures()
477485
if err != nil {
478486
return nil, err
@@ -569,6 +577,7 @@ func newConfigOptionsAction(
569577
}
570578

571579
func (a *configOptionsAction) Run(ctx context.Context) (*actions.ActionResult, error) {
580+
tracing.SetUsageAttributes(fields.ConfigOperationKey.String("options"))
572581
options := config.GetAllConfigOptions()
573582

574583
// Load current config to show current values

cli/azd/cmd/env.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import (
1717
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
1818
"github.com/azure/azure-dev/cli/azd/cmd/actions"
1919
"github.com/azure/azure-dev/cli/azd/internal"
20+
"github.com/azure/azure-dev/cli/azd/internal/tracing"
21+
"github.com/azure/azure-dev/cli/azd/internal/tracing/fields"
2022
"github.com/azure/azure-dev/cli/azd/pkg/account"
2123
"github.com/azure/azure-dev/cli/azd/pkg/alpha"
2224
"github.com/azure/azure-dev/cli/azd/pkg/azapi"
@@ -213,6 +215,7 @@ func newEnvSetAction(
213215
}
214216

215217
func (e *envSetAction) Run(ctx context.Context) (*actions.ActionResult, error) {
218+
tracing.SetUsageAttributes(fields.EnvOperationKey.String("set"))
216219
// To track case conflicts
217220
dotEnv := e.env.Dotenv()
218221
keyValues := make(map[string]string)
@@ -364,6 +367,7 @@ type envSetSecretAction struct {
364367
}
365368

366369
func (e *envSetSecretAction) Run(ctx context.Context) (*actions.ActionResult, error) {
370+
tracing.SetUsageAttributes(fields.EnvOperationKey.String("set-secret"))
367371
if len(e.args) < 1 {
368372
return nil, &internal.ErrorWithSuggestion{
369373
Err: internal.ErrNoArgsProvided,
@@ -784,6 +788,7 @@ func newEnvSelectAction(
784788
}
785789

786790
func (e *envSelectAction) Run(ctx context.Context) (*actions.ActionResult, error) {
791+
tracing.SetUsageAttributes(fields.EnvOperationKey.String("select"))
787792
var environmentName string
788793

789794
// If no argument provided, prompt the user to select an environment
@@ -868,12 +873,15 @@ func newEnvListAction(
868873
}
869874

870875
func (e *envListAction) Run(ctx context.Context) (*actions.ActionResult, error) {
876+
tracing.SetUsageAttributes(fields.EnvOperationKey.String("list"))
871877
envs, err := e.envManager.List(ctx)
872878

873879
if err != nil {
874880
return nil, fmt.Errorf("listing environments: %w", err)
875881
}
876882

883+
tracing.SetUsageAttributes(fields.EnvCountKey.Int(len(envs)))
884+
877885
if e.formatter.Kind() == output.TableFormat {
878886
columns := []output.Column{
879887
{
@@ -967,6 +975,7 @@ func newEnvNewAction(
967975
}
968976

969977
func (en *envNewAction) Run(ctx context.Context) (*actions.ActionResult, error) {
978+
tracing.SetUsageAttributes(fields.EnvOperationKey.String("new"))
970979
environmentName := ""
971980
if len(en.args) >= 1 {
972981
environmentName = en.args[0]
@@ -1141,6 +1150,7 @@ func newEnvRefreshAction(
11411150
}
11421151

11431152
func (ef *envRefreshAction) Run(ctx context.Context) (*actions.ActionResult, error) {
1153+
tracing.SetUsageAttributes(fields.EnvOperationKey.String("refresh"))
11441154
// Command title
11451155
ef.console.MessageUxItem(ctx, &ux.MessageTitle{
11461156
Title: fmt.Sprintf("Refreshing environment %s (azd env refresh)", ef.env.Name()),
@@ -1301,6 +1311,7 @@ func newEnvGetValuesAction(
13011311
}
13021312

13031313
func (eg *envGetValuesAction) Run(ctx context.Context) (*actions.ActionResult, error) {
1314+
tracing.SetUsageAttributes(fields.EnvOperationKey.String("get-values"))
13041315
name, err := eg.azdCtx.GetDefaultEnvironmentName()
13051316
if err != nil {
13061317
return nil, err
@@ -1393,6 +1404,7 @@ func newEnvGetValueAction(
13931404
}
13941405

13951406
func (eg *envGetValueAction) Run(ctx context.Context) (*actions.ActionResult, error) {
1407+
tracing.SetUsageAttributes(fields.EnvOperationKey.String("get-value"))
13961408
if len(eg.args) < 1 {
13971409
return nil, &internal.ErrorWithSuggestion{
13981410
Err: internal.ErrNoKeyNameProvided,
@@ -1497,6 +1509,7 @@ func newEnvConfigGetAction(
14971509
}
14981510

14991511
func (a *envConfigGetAction) Run(ctx context.Context) (*actions.ActionResult, error) {
1512+
tracing.SetUsageAttributes(fields.EnvOperationKey.String("config-get"))
15001513
name, err := a.azdCtx.GetDefaultEnvironmentName()
15011514
if err != nil {
15021515
return nil, err
@@ -1595,6 +1608,7 @@ func newEnvConfigSetAction(
15951608
}
15961609

15971610
func (a *envConfigSetAction) Run(ctx context.Context) (*actions.ActionResult, error) {
1611+
tracing.SetUsageAttributes(fields.EnvOperationKey.String("config-set"))
15981612
name, err := a.azdCtx.GetDefaultEnvironmentName()
15991613
if err != nil {
16001614
return nil, err
@@ -1695,6 +1709,7 @@ func newEnvConfigUnsetAction(
16951709
}
16961710

16971711
func (a *envConfigUnsetAction) Run(ctx context.Context) (*actions.ActionResult, error) {
1712+
tracing.SetUsageAttributes(fields.EnvOperationKey.String("config-unset"))
16981713
name, err := a.azdCtx.GetDefaultEnvironmentName()
16991714
if err != nil {
17001715
return nil, err

cli/azd/cmd/env_remove.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212

1313
"github.com/azure/azure-dev/cli/azd/cmd/actions"
1414
"github.com/azure/azure-dev/cli/azd/internal"
15+
"github.com/azure/azure-dev/cli/azd/internal/tracing"
16+
"github.com/azure/azure-dev/cli/azd/internal/tracing/fields"
1517
"github.com/azure/azure-dev/cli/azd/pkg/environment"
1618
"github.com/azure/azure-dev/cli/azd/pkg/environment/azdcontext"
1719
"github.com/azure/azure-dev/cli/azd/pkg/input"
@@ -111,6 +113,7 @@ func newEnvRemoveAction(
111113
}
112114

113115
func (er *envRemoveAction) Run(ctx context.Context) (*actions.ActionResult, error) {
116+
tracing.SetUsageAttributes(fields.EnvOperationKey.String("remove"))
114117
// Command title
115118
er.console.MessageUxItem(ctx, &ux.MessageTitle{
116119
Title: "Remove an environment (azd env remove)",

cli/azd/cmd/hooks.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99

1010
"github.com/azure/azure-dev/cli/azd/cmd/actions"
1111
"github.com/azure/azure-dev/cli/azd/internal"
12+
"github.com/azure/azure-dev/cli/azd/internal/tracing"
13+
"github.com/azure/azure-dev/cli/azd/internal/tracing/fields"
1214
"github.com/azure/azure-dev/cli/azd/pkg/environment"
1315
"github.com/azure/azure-dev/cli/azd/pkg/exec"
1416
"github.com/azure/azure-dev/cli/azd/pkg/ext"
@@ -118,6 +120,15 @@ const (
118120
func (hra *hooksRunAction) Run(ctx context.Context) (*actions.ActionResult, error) {
119121
hookName := hra.args[0]
120122

123+
hookType := "project"
124+
if hra.flags.service != "" {
125+
hookType = "service"
126+
}
127+
tracing.SetUsageAttributes(
128+
fields.HooksNameKey.String(hookName),
129+
fields.HooksTypeKey.String(hookType),
130+
)
131+
121132
// Command title
122133
hra.console.MessageUxItem(ctx, &ux.MessageTitle{
123134
Title: "Running hooks (azd hooks run)",

cli/azd/cmd/infra_generate.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313

1414
"github.com/azure/azure-dev/cli/azd/cmd/actions"
1515
"github.com/azure/azure-dev/cli/azd/internal"
16+
"github.com/azure/azure-dev/cli/azd/internal/tracing"
17+
"github.com/azure/azure-dev/cli/azd/internal/tracing/fields"
1618
"github.com/azure/azure-dev/cli/azd/pkg/alpha"
1719
"github.com/azure/azure-dev/cli/azd/pkg/environment/azdcontext"
1820
"github.com/azure/azure-dev/cli/azd/pkg/input"
@@ -85,6 +87,11 @@ func newInfraGenerateAction(
8587
}
8688

8789
func (a *infraGenerateAction) Run(ctx context.Context) (*actions.ActionResult, error) {
90+
// Track infra provider from project configuration
91+
if a.projectConfig != nil && a.projectConfig.Infra.Provider != "" {
92+
tracing.SetUsageAttributes(fields.InfraProviderKey.String(string(a.projectConfig.Infra.Provider)))
93+
}
94+
8895
if a.calledAs == "synth" {
8996
fmt.Fprintln(
9097
a.console.Handles().Stderr,

cli/azd/cmd/monitor.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99

1010
"github.com/azure/azure-dev/cli/azd/cmd/actions"
1111
"github.com/azure/azure-dev/cli/azd/internal"
12+
"github.com/azure/azure-dev/cli/azd/internal/tracing"
13+
"github.com/azure/azure-dev/cli/azd/internal/tracing/fields"
1214
"github.com/azure/azure-dev/cli/azd/pkg/account"
1315
"github.com/azure/azure-dev/cli/azd/pkg/alpha"
1416
"github.com/azure/azure-dev/cli/azd/pkg/apphost"
@@ -100,6 +102,15 @@ func (m *monitorAction) Run(ctx context.Context) (*actions.ActionResult, error)
100102
m.flags.monitorOverview = true
101103
}
102104

105+
// Track which monitor type was selected
106+
monitorType := "overview"
107+
if m.flags.monitorLive {
108+
monitorType = "live"
109+
} else if m.flags.monitorLogs {
110+
monitorType = "logs"
111+
}
112+
tracing.SetUsageAttributes(fields.MonitorTypeKey.String(monitorType))
113+
103114
if m.env.GetSubscriptionId() == "" {
104115
return nil, &internal.ErrorWithSuggestion{
105116
Err: internal.ErrInfraNotProvisioned,

0 commit comments

Comments
 (0)