Azure Developer CLI

August 12, 2026 · View on GitHub

How azd collects, exports, and transmits telemetry data.

Note

This is the public telemetry documentation. A Microsoft-internal companion set of docs (data pipeline, Kusto/Power BI reporting, runnable queries) is maintained separately for internal maintainers.

Overview

azd emits OpenTelemetry spans for every command execution. Telemetry flows through a local pipeline:

  1. Instrumentation — CLI, VS Code extension, and extensions emit OTel spans
  2. Export — Spans are converted to Application Insights envelopes and queued to disk
  3. Upload — A background process transmits envelopes to Application Insights

Microsoft-internal dashboards, data pipelines, and reporting infrastructure are documented separately for internal maintainers.

Data Flow

flowchart TB
    subgraph Instrumentation ["Instrumentation"]
        CLI["azd CLI<br/>(Go + OpenTelemetry)"]
        VSC["VS Code Extension<br/>(@microsoft/vscode-azext-utils)"]
        EXT["Extensions<br/>(structured error reporting)"]
    end

    subgraph Export ["CLI Export Pipeline"]
        MW["Command Middleware<br/>cli/azd/cmd/middleware/telemetry.go"]
        OTel["OTel TracerProvider"]
        AIExp["App Insights Exporter<br/>SpanToEnvelope()"]
        DiskQ["Disk Queue<br/>~/.azd/telemetry/*.trn"]
        Upload["azd telemetry upload<br/>(background / deferred)"]
    end

    subgraph Ingestion ["Azure Monitor"]
        AppInsights["Application Insights"]
    end

    CLI --> MW --> OTel --> AIExp --> DiskQ --> Upload --> AppInsights
    VSC -->|VS Code telemetry framework| AppInsights
    EXT -->|structured errors via host| MW

CLI Telemetry Pipeline (Detail)

1. Initialization

File: cli/azd/internal/telemetry/telemetry.go

When azd starts, the telemetry subsystem:

  1. Checks AZURE_DEV_COLLECT_TELEMETRY — if set to "no", telemetry is disabled entirely
  2. In Cloud Shell, shows a first-run consent notice (creates ~/.azd/first-run marker)
  3. Creates a StorageQueue backed by the filesystem at ~/.azd/telemetry/
  4. Initializes the App Insights Exporter — a custom OTel SpanExporter that converts spans to Application Insights envelopes
  5. Optionally adds:
    • Stdout trace exporter (via --trace-log-file)
    • OTLP HTTP exporter (via --trace-log-url)
  6. Creates an OTel TracerProvider with the configured exporters
  7. Registers the provider globally via otel.SetTracerProvider(tp)

2. Command Execution → Span Creation

File: cli/azd/cmd/middleware/telemetry.go

Every azd command is wrapped by the telemetry middleware:

user runs `azd deploy`
  → middleware.Run()
    → tracing.Start(ctx, "cmd.deploy")  // creates OTel span
    → records attributes:
        cmd.entry, cmd.flags, cmd.args.count,
        platform.type, installed extensions (id@version)
    → runs the actual command action
    → on completion:
        adds usage attributes (from baggage)
        adds perf.interact_time
        maps errors via cmd.MapError()
    → span.End()

For extension commands, the event name switches to ext.run and records ext.id, ext.version.

3. Span Export → Disk Queue

File: cli/azd/internal/telemetry/storage_exporter.go

The custom exporter:

  1. Receives completed OTel spans
  2. Converts each to an Application Insights Envelope with RequestData (via SpanToEnvelope())
  3. Serializes as NDJSON
  4. Enqueues to the disk queue (~/.azd/telemetry/YYYYMMDDThhmmss_retry_random.trn)
  5. Retries enqueue up to 3 times on failure

4. Disk Queue → Upload

Files: cli/azd/internal/telemetry/storage.go, uploader.go

The disk queue is a FIFO queue implemented as timestamped files:

  • Peek() picks the oldest ready item (not older than itemFileMaxTimeKept)
  • Cleanup() removes stale .tmp files and expired items

Upload happens via azd telemetry upload (triggered as a background subprocess):

  1. Acquires upload.lock (file lock)
  2. Loops: Peek → Transmit → Remove
  3. Retries up to maxRetryCount=3 with backoff
  4. Handles partial success and Retry-After headers from App Insights ingestion

5. App Insights Envelope Format

File: cli/azd/internal/telemetry/appinsights-exporter/span_to_envelope.go

Each span becomes a contracts.Envelope containing RequestData:

Envelope FieldSource
IKeyInstrumentation key from connection string
Tags[ai.application.ver]service.version resource attribute
Tags[ai.user.*]UserAccountId, UserAuthUserId, UserId, SessionId
PropertiesString/bool span attributes
MeasurementsInt64/float64 span attributes
NameSpan name (e.g., cmd.deploy)
DurationSpan duration (App Insights format)
ResponseCodeSpan status / result code
SuccessSpan status == OK

Slice attributes are JSON-serialized into Properties.

VS Code Extension Telemetry

Files: ext/vscode/src/telemetry/

The VS Code extension uses a separate telemetry path from the CLI:

flowchart LR
    VSExt["VS Code Extension"] --> VSFw["VS Code Telemetry Framework<br/>(@microsoft/vscode-azext-utils)"]
    VSFw --> AppInsights["Application Insights"]

Key differences from CLI:

AspectCLIVS Code Extension
FrameworkGo + OpenTelemetryTypeScript + vscode-azext-utils
ExportCustom App Insights exporter + disk queueVS Code telemetry framework (direct)
Opt-outAZURE_DEV_COLLECT_TELEMETRY=notelemetry.telemetryLevel=off in VS Code settings
Eventscmd.*, ext.*, mcp.*, etc.azure-dev.* (activate, deactivate, tasks, surveys)

Extension events (ext/vscode/src/telemetry/telemetryId.ts):

  • Lifecycle: azure-dev.activate, azure-dev.deactivate
  • CLI command tasks: deploy, provision, up, down, init, login, restore, package
  • Environment/extension actions
  • Survey tracking: azure-dev.survey-check, azure-dev.survey-prompt-response
  • Activity statistics: tracks totalActiveDays via VS Code Memento storage

Extension Framework Telemetry

File: cli/azd/cmd/middleware/telemetry.go (host side)

Extensions run as separate processes and report back to the azd host:

flowchart LR
    Ext["Extension Process"] -->|structured error| Host["azd Host"]
    Host -->|maps to span attributes| MW["Telemetry Middleware"]
    MW --> Span["OTel Span<br/>(event: ext.run)"]
  • Extension commands emit ext.run events with ext.id and ext.version
  • Extensions can report structured errors back to the host via ExtensionService.ReportError
  • Error result codes follow conventions:
    • Service errors: ext.service.<service>.<statusCode>
    • Validation: ext.validation.*
    • Auth: ext.auth.*
    • Dependency: ext.dependency.*
  • Extension lifecycle events: ext.install, ext.update, ext.promote
  • Extensions published to the official registry can report usage events via TelemetryService.ReportUsage after going through a privacy review. Each event becomes an ext.usage span sharing the command's trace, carrying the extension's identity plus the caller's event name and attributes. Core namespaces every caller attribute under ext. and bounds its size, but does not enumerate the values. Only extensions whose configured source matches the verified official azd registry name, type, and normalized URL are recorded — see ADR-001.

Opt-Out

SurfaceMechanism
CLISet AZURE_DEV_COLLECT_TELEMETRY=no
VS CodeSet telemetry.telemetryLevel to off in VS Code settings
Cloud ShellFirst-run notice shown; opt-out instructions provided

PII Protection

  • Hashed fields: project.template.id, project.template.version, project.name, env.name are SHA-256 hashed (case-insensitive) before emission
  • Data classifications are annotated on every field:
    • PublicPersonalData
    • SystemMetadata
    • CallstackOrException
    • CustomerContent
    • EndUserPseudonymizedInformation
    • OrganizationalIdentifiableInformation
  • Privacy review required for: new telemetry fields, classification changes, any unhashed PII

Key Files

cli/azd/
├── cmd/middleware/telemetry.go           # Command-level span middleware
├── internal/
│   ├── telemetry/
│   │   ├── telemetry.go                  # Pipeline init, env vars, consent
│   │   ├── storage.go                    # Disk queue (FIFO)
│   │   ├── storage_exporter.go           # OTel exporter → disk queue
│   │   ├── uploader.go                   # Queue → App Insights upload
│   │   ├── notice.go                     # First-run consent notice
│   │   └── appinsights-exporter/
│   │       ├── span_to_envelope.go       # Span → App Insights envelope
│   │       ├── transmitter.go            # HTTP POST to ingestion
│   │       ├── endpoint_config.go        # Connection string parsing
│   │       └── transmit_payload.go       # NDJSON serialization
│   └── tracing/
│       ├── tracing.go                    # Global tracer
│       ├── attributes.go                 # Global/usage baggage
│       ├── events/events.go              # All event name constants
│       └── fields/
│           ├── fields.go                 # All field keys + classifications
│           └── key.go                    # SHA-256 hashing helpers
ext/vscode/src/telemetry/
├── telemetryId.ts                        # Extension event IDs
└── activityStatisticsService.ts          # Active days tracking

See Also