Distributed Tracing in Azure
When a single user request touches ten microservices before returning a response, a traditional log file tells you almost nothing useful. Distributed tracing changes this by attaching a unique trace ID to every request and propagating it through every service, every queue message, and every downstream call — giving you a complete, end-to-end picture of exactly what happened, in order, with timing at every step.
How distributed tracing works
Distributed tracing builds on a simple idea: every request gets a globally unique ID (the trace ID), and every unit of work within that request gets a locally unique ID (the span ID). When service A calls service B, it passes both IDs in the request headers. Service B creates a new span with its own span ID, but records service A’s span ID as its parent. The result is a tree structure — the trace — that shows exactly how the request flowed through the system.
The standard headers used for this propagation are defined in the W3C Trace Context specification:
traceparent— Contains the version, trace ID, parent span ID, and trace flags. Example:00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01tracestate— Vendor-specific data that travels alongside the trace. Application Insights uses this to pass the operation name.
Application Insights reads these headers on inbound requests and injects them on outbound calls automatically when you use the SDK, so in many cases trace propagation works without any manual code.
Spans, requests, and dependencies
In Application Insights terminology:
- Request — An inbound operation received by a service (HTTP, gRPC, queue message consumption). This becomes the root span for that service’s contribution to the trace.
- Dependency — An outbound call made by a service (HTTP call, SQL query, Redis command, Service Bus message send). Each dependency call becomes a child span.
- Exception — An error recorded within a span.
- Trace / Log — Custom messages recorded within the context of a span.
All four types are linked by the operation ID (the trace ID). This means you can query all of them together using the operation ID as a join key.
End-to-end transaction diagnostics: a real example
Scenario: A user reports that checkout is slow. The request ID from the browser’s network tab is 4bf92f3577b34da6a3ce929d0e0e4736. Here is how you investigate using KQL.
// Step 1: Find the root request and its duration
AppRequests
| where OperationId == "4bf92f3577b34da6a3ce929d0e0e4736"
| project TimeGenerated, Name, DurationMs, ResultCode, Success,
RoleName = cloud_RoleName
// Step 2: See all spans (requests + dependencies) in the trace, ordered by time
union AppRequests, AppDependencies
| where OperationId == "4bf92f3577b34da6a3ce929d0e0e4736"
| project TimeGenerated,
SpanType = itemType,
Name,
Target,
DurationMs,
Success,
ResultCode,
RoleName = cloud_RoleName,
ParentId = operation_ParentId
| order by TimeGenerated asc
// Step 3: Find the slowest spans in this trace
union AppRequests, AppDependencies
| where OperationId == "4bf92f3577b34da6a3ce929d0e0e4736"
| project Name, DurationMs, SpanType = itemType, RoleName = cloud_RoleName
| order by DurationMs desc
| limit 10
// Step 4: Check for exceptions in this trace
AppExceptions
| where OperationId == "4bf92f3577b34da6a3ce929d0e0e4736"
| project TimeGenerated, ExceptionType, OuterMessage, InnermostMessage, RoleName = cloud_RoleNameSampling: keeping costs under control
On a high-traffic application, storing every trace for every request is prohibitively expensive. Application Insights uses sampling to retain a statistically representative subset of traces. There are three sampling modes:
| Sampling Type | How it Works | Best For |
|---|---|---|
| Adaptive sampling | Automatically adjusts the sampling rate to target 5 telemetry items/second per SDK instance | Most applications; default behavior |
| Fixed-rate sampling | Retains a configured percentage (e.g., 10%) of requests consistently | When you need predictable volume; coordinated with backend services |
| Ingestion sampling | Azure drops data at ingestion before it enters the workspace | Last resort; data is discarded, not just unsampled |
Importantly, sampling is trace-aware: all spans belonging to the same trace are either kept or dropped together. You will never see half a trace in your data — if any span from a trace is sampled in, all related spans are sampled in.
Errors are never sampled out by adaptive sampling. Application Insights always retains all telemetry for requests that result in exceptions or error status codes, even when sampling is active. This ensures you can always investigate failures.
Enabling cross-service tracing
For tracing to work across service boundaries, each service must use a compatible SDK that reads and propagates the W3C traceparent header. Application Insights SDKs do this automatically for HTTP calls. For messaging systems like Azure Service Bus and Event Hubs, the SDK auto-instruments message send and receive operations.
# Verify that an Application Insights connection string is configured
az webapp config appsettings list \
--resource-group myRG \
--name myapp \
--query "[?name=='APPLICATIONINSIGHTS_CONNECTION_STRING']" \
--output table
# Set the connection string if missing
az webapp config appsettings set \
--resource-group myRG \
--name myapp \
--settings APPLICATIONINSIGHTS_CONNECTION_STRING="InstrumentationKey=<key>;IngestionEndpoint=https://eastus-8.in.applicationinsights.azure.com/"
# For .NET apps, also set the role name so traces show which service they came from
az webapp config appsettings set \
--resource-group myRG \
--name myapp \
--settings APPLICATIONINSIGHTS_ROLE_NAME="OrderService"OpenTelemetry integration
For new applications, Microsoft recommends using the Azure Monitor OpenTelemetry distro instead of the legacy Application Insights SDK. OpenTelemetry is a vendor-neutral standard, which means your instrumentation code is not tied to Azure. If you ever need to send traces to a different backend, you change the exporter configuration — not the instrumentation code.
# Install the Azure Monitor OpenTelemetry distro for .NET
dotnet add package Azure.Monitor.OpenTelemetry.AspNetCore
# For Node.js
npm install @azure/monitor-opentelemetry
# For Python
pip install azure-monitor-opentelemetryThe Azure Monitor distro automatically instruments HTTP clients, SQL, Redis, Azure SDK calls, and more — the same auto-instrumentation as the legacy SDK, but through the standardized OpenTelemetry pipeline.
Common mistakes
- Not setting the cloud role name. By default, Application Insights uses the host name as the role name. In containerized or serverless environments, host names are ephemeral and meaningless. Always set
APPLICATIONINSIGHTS_ROLE_NAMEto a human-readable service name so traces clearly show which service each span came from. - Using the legacy AI-Request-Id header instead of W3C traceparent. Older Application Insights SDK versions used a proprietary header format. If any service in your chain uses the old format and others use W3C, trace correlation breaks at that boundary. Upgrade all services to the same SDK version family.
- Not propagating context through async message queues. HTTP calls propagate context automatically. But if your service reads from a queue and does not extract the trace context from the message properties, the trace chain is broken. Use the Application Insights SDK’s queue-aware instrumentation or manually extract and set the parent context.
- Sampling rate mismatch between services. If service A samples at 10% and service B (which it calls) samples at 50%, you will see orphaned spans in service B with no parent in service A. Coordinate sampling rates across all services in a trace or use head-based sampling where the first service makes the sampling decision for the entire trace.
Summary
- Distributed tracing links all spans (requests, dependency calls, exceptions) from a single user request using a shared operation ID propagated in W3C traceparent headers.
- Application Insights automatically instruments HTTP and Azure SDK calls; custom spans can be added for any other work that needs tracing.
- Sampling keeps costs manageable: adaptive sampling adjusts automatically, and all spans for a single trace are kept or dropped together.
- Always set the cloud role name so the Transaction Diagnostics view shows meaningful service names rather than host names.
- For new applications, use the Azure Monitor OpenTelemetry distro for vendor-neutral instrumentation.
Frequently asked questions
What is a trace in distributed tracing?
A trace represents the complete journey of a single request through your system. It is made up of spans — individual units of work at each service boundary. A trace for an e-commerce checkout might include spans for the API gateway, order service, inventory service, payment service, and database calls.
Does Application Insights support OpenTelemetry?
Yes. Application Insights supports the OpenTelemetry SDK for .NET, Java, Node.js, and Python. Microsoft recommends using the OpenTelemetry-based Azure Monitor distro for new applications, as it is the strategic direction going forward.
What is W3C Trace Context?
W3C Trace Context is the HTTP header standard (traceparent and tracestate) for propagating trace correlation across service boundaries. Application Insights uses this standard by default in modern SDK versions.