-
Notifications
You must be signed in to change notification settings - Fork 330
feat(telemetry): emit infra.provider on provision, up, and down #9091
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f609bfa
c915e6e
4c183f3
36fe158
433d5ea
55c4ec2
37f7272
2385413
ca72fc4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,8 @@ import ( | |
| "os" | ||
| "path/filepath" | ||
|
|
||
| "github.com/azure/azure-dev/cli/azd/internal/tracing" | ||
| "github.com/azure/azure-dev/cli/azd/internal/tracing/fields" | ||
| "github.com/azure/azure-dev/cli/azd/pkg/alpha" | ||
| "github.com/azure/azure-dev/cli/azd/pkg/azapi" | ||
| "github.com/azure/azure-dev/cli/azd/pkg/azsdk/storage" | ||
|
|
@@ -538,3 +540,89 @@ func (m *Manager) newProvider(ctx context.Context) (Provider, error) { | |
|
|
||
| return provider, nil | ||
| } | ||
|
|
||
| const ( | ||
| // InfraProviderMixed is the infra.provider telemetry value recorded when a project's | ||
| // provisioning layers do not all resolve to the same IaC provider. | ||
| InfraProviderMixed = "mixed" | ||
|
|
||
| // InfraProviderCustom is the infra.provider telemetry value recorded for a provider that is | ||
| // not one of the built-in kinds (for example an extension-registered provider). The raw name | ||
| // is never emitted because it may embed user-chosen project or organization identifiers. | ||
|
hemarina marked this conversation as resolved.
|
||
| InfraProviderCustom = "custom" | ||
| ) | ||
|
|
||
| // RecordInfraProviderUsage records the resolved IaC provider for a provisioning command | ||
| // (provision / up / down) as the infra.provider usage attribute on the ambient command span. | ||
| // When every layer resolves to the same provider it records that provider (for example | ||
| // "bicep", "terraform", "arm"); non-built-in (extension) providers are bucketed to | ||
| // InfraProviderCustom ("custom") so raw user-chosen names are never emitted. When layers | ||
| // resolve to different providers it records InfraProviderMixed ("mixed"). Layers that leave | ||
| // the provider unspecified resolve through the manager's default provider. It is a no-op when | ||
| // no provider can be resolved. | ||
| // | ||
| // Callers must invoke this once per command, before provider work begins, so the attribute is | ||
| // present on success, failure, and preview spans alike. The value is computed deterministically | ||
| // from configuration (rather than racing concurrent per-layer resolution). | ||
| func (m *Manager) RecordInfraProviderUsage(layers []Options) { | ||
| // The default provider is resolved lazily and at most once per call: every unspecified layer | ||
| // resolves to the same default, so caching keeps the value deterministic and avoids repeating | ||
| // resolver work (which may do I/O) per layer. | ||
| var cachedDefault ProviderKind | ||
| defaultResolved := false | ||
| defaultFailed := false | ||
|
|
||
| seen := map[ProviderKind]struct{}{} | ||
| for _, layer := range layers { | ||
| kind := layer.Provider | ||
| if kind == NotSpecified { | ||
| if m.defaultProvider == nil || defaultFailed { | ||
| continue | ||
| } | ||
|
|
||
| if !defaultResolved { | ||
| resolved, err := m.defaultProvider() | ||
| if err != nil { | ||
| defaultFailed = true | ||
| continue | ||
| } | ||
|
|
||
| cachedDefault = resolved | ||
| defaultResolved = true | ||
| } | ||
|
|
||
| kind = cachedDefault | ||
| } | ||
|
|
||
| if kind == NotSpecified { | ||
| continue | ||
| } | ||
|
|
||
| seen[kind] = struct{}{} | ||
| } | ||
|
|
||
| // Distinct resolved kinds are collected first so that two different custom providers still | ||
| // record "mixed" (bucketing only the single-provider result, not before counting). | ||
| switch len(seen) { | ||
| case 0: | ||
| return | ||
| case 1: | ||
| for kind := range seen { | ||
| tracing.SetUsageAttributes(fields.InfraProviderKey.String(infraProviderTelemetryValue(kind))) | ||
| } | ||
| default: | ||
| tracing.SetUsageAttributes(fields.InfraProviderKey.String(InfraProviderMixed)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit / lowest priority — genuinely just curious, not blocking: is there a reason we prefer collapsing to a single Built-in provider names are a fixed enum (only I fully expect almost nobody to actually mix two providers across layers, so this really is a nit — just want to understand the choice. |
||
| } | ||
| } | ||
|
|
||
| // infraProviderTelemetryValue maps a provider kind to a value that is safe to emit raw. Built-in | ||
| // kinds are returned verbatim; any other (custom / extension-registered) provider is bucketed to | ||
| // InfraProviderCustom so a user-chosen provider name is never sent as telemetry. | ||
| func infraProviderTelemetryValue(kind ProviderKind) string { | ||
| switch kind { | ||
| case Bicep, Terraform, Arm, Pulumi: | ||
| return string(kind) | ||
| default: | ||
| return InfraProviderCustom | ||
| } | ||
|
hemarina marked this conversation as resolved.
|
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.