Debugging Production Systems in Azure
Debugging a production system is not like debugging on your laptop. You cannot set breakpoints, step through code, or add console logs and redeploy in thirty seconds. Production debugging requires working with the evidence that was already collected — traces, logs, metrics — and using Azure’s built-in tools to fill in the gaps. This page walks through the full toolkit available and the systematic approach that turns a confusing production problem into an understood and resolved one.
The production debugging mindset
Production debugging is hypothesis-driven. You start with the alert or complaint that triggered the investigation, form a hypothesis about the likely cause, find data that confirms or refutes it, and iterate. The key discipline is not to start remediation until you understand the root cause — a fix based on a wrong hypothesis often masks the real problem or creates new ones.
The data sources available to you, in roughly decreasing order of precision:
- Distributed traces — The exact path and timing of the failing request across all services
- Structured application logs — What the application was doing and what it recorded
- Exception details — The specific error, stack trace, and state at the point of failure
- Dependency calls — Which downstream services were called and whether they succeeded
- Infrastructure metrics — CPU, memory, disk, network at the time of the failure
- Resource events and changes — Deployments, configuration changes, platform events
Step 1: Establish the timeline and scope
Before investigating the cause, establish when the problem started and how widespread it is. This narrows your search space dramatically.
// Establish the timeline: when did errors increase?
AppRequests
| where TimeGenerated > ago(6h)
| summarize
TotalRequests = count(),
Errors = countif(Success == false),
AvgLatency = avg(DurationMs)
by bin(TimeGenerated, 2m)
| extend ErrorRate = round(100.0 * Errors / TotalRequests, 2)
| render timechart
// Determine scope: which services are affected?
AppRequests
| where TimeGenerated > ago(1h)
| where Success == false
| summarize ErrorCount = count() by ServiceName = cloud_RoleName
| order by ErrorCount desc
// Determine user impact: how many users are experiencing errors?
AppRequests
| where TimeGenerated between (datetime(2026-03-19 14:20:00) .. datetime(2026-03-19 15:00:00))
| summarize
TotalUsers = dcount(UserId),
AffectedUsers = dcountif(UserId, Success == false)
by bin(TimeGenerated, 10m)
| extend ImpactPercent = round(100.0 * AffectedUsers / TotalUsers, 1)Step 2: Trace a specific failure end-to-end
Once you know the time range and affected endpoints, pick a specific failing request and trace it completely. Find its operation ID and use it to pull the full execution context.
// Find a specific failing request and get its operation ID
AppRequests
| where TimeGenerated between (datetime(2026-03-19 14:25:00) .. datetime(2026-03-19 14:35:00))
| where Success == false
| where Name == "POST /api/orders"
| project TimeGenerated, OperationId = operation_Id, DurationMs, ResultCode, UserId
| order by TimeGenerated desc
| limit 5
// Once you have an operation ID, trace the full journey
// Replace 'abc123' with the actual operation ID
let targetOp = "abc123def456789";
union AppRequests, AppDependencies, AppExceptions, AppTraces
| where OperationId == targetOp
| project
TimeGenerated,
ItemType = itemType,
ServiceName = cloud_RoleName,
Name,
DurationMs,
Success,
ResultCode,
ExceptionType,
Message,
OuterMessage
| order by TimeGenerated ascStep 3: Diagnose dependency failures
Many production failures trace back to a downstream dependency — a database timing out, a third-party API returning errors, a message queue becoming unavailable. Application Insights dependency tracking makes this easy to detect.
// Find failing dependencies in the incident window
AppDependencies
| where TimeGenerated between (datetime(2026-03-19 14:20:00) .. datetime(2026-03-19 15:00:00))
| where Success == false
| summarize
FailureCount = count(),
AvgDuration = avg(DurationMs),
MaxDuration = max(DurationMs),
SampleMessage = any(Data)
by Target, Type, Name
| order by FailureCount desc
// Compare dependency performance before and during the incident
let before = AppDependencies
| where TimeGenerated between (datetime(2026-03-19 13:00:00) .. datetime(2026-03-19 14:20:00))
| where Success == true
| summarize BaselineP95 = percentile(DurationMs, 95) by Target, Type;
let during = AppDependencies
| where TimeGenerated between (datetime(2026-03-19 14:20:00) .. datetime(2026-03-19 15:00:00))
| summarize
IncidentP95 = percentile(DurationMs, 95),
FailureRate = round(100.0 * countif(Success == false) / count(), 1)
by Target, Type;
before
| join during on Target, Type
| extend LatencyIncrease = round((IncidentP95 - BaselineP95) / BaselineP95 * 100, 0)
| order by LatencyIncrease descAzure App Service diagnostics tools
For App Service applications, Azure provides a suite of built-in diagnostics beyond Log Analytics. These tools are especially useful when the application is behaving unexpectedly at the host level — memory pressure, process crashes, file system issues.
# View recent application crash logs
az webapp log download \
--resource-group myRG \
--name myapp \
--log-file application-logs.zip
# Enable detailed error logging temporarily
az webapp config set \
--resource-group myRG \
--name myapp \
--detailed-error-messages true \
--failed-request-tracing true
# Get current application settings for configuration verification
az webapp config appsettings list \
--resource-group myRG \
--name myapp \
--output table
# Check if the application is restarting unexpectedly
az monitor activity-log list \
--resource-group myRG \
--resource-provider Microsoft.Web \
--start-time "2026-03-19T14:00:00Z" \
--end-time "2026-03-19T16:00:00Z" \
--query "[?contains(operationName.value, 'restart')]" \
--output table
# Stream live logs during debugging
az webapp log tail \
--resource-group myRG \
--name myapp \
--provider httpThe “Diagnose and solve problems” blade in the Azure portal runs automated diagnostics for App Service and AKS. Before writing queries, check this blade — it often identifies the root cause (memory spike, CPU spike, failing dependency, certificate expiry) in under 60 seconds.
Snapshot Debugger: capturing state at the point of failure
When an exception occurs in production, the Snapshot Debugger can capture a “mini dump” of the application state — including local variable values, the call stack, and parameter values — at the exact moment the exception was thrown. This is invaluable for NullReferenceException, unexpected null values, or any bug where the “what were the values?” question is critical.
# Enable Snapshot Debugger on App Service
az webapp config appsettings set \
--resource-group myRG \
--name myapp \
--settings \
APPLICATIONINSIGHTS_CONNECTION_STRING="<connection-string>" \
SNAPSHOTDEBUGGER_EXTENSION_VERSION="disabled"
# Install the Snapshot Debugger extension
az webapp extension add \
--resource-group myRG \
--name myapp \
--extension Microsoft.ApplicationInsights.SnapshotDebuggerAfter enabling, navigate to Application Insights → Failures → select an exception → “Open Debug Snapshot” to view the captured state.
Correlating across multiple data sources
The most powerful debugging sessions combine data from multiple sources — application logs, infrastructure metrics, database logs, and platform events — in the same query window.
// Correlate App Service resource metrics with application errors
// This shows whether errors correlate with infrastructure stress
let errors = AppRequests
| where TimeGenerated > ago(2h)
| where Success == false
| summarize ErrorCount = count() by bin(TimeGenerated, 2m);
let cpuMetrics = AzureMetrics
| where TimeGenerated > ago(2h)
| where ResourceProvider == "MICROSOFT.WEB"
| where MetricName == "CpuPercentage"
| summarize MaxCPU = max(Maximum) by bin(TimeGenerated, 2m);
errors
| join cpuMetrics on TimeGenerated
| project TimeGenerated, ErrorCount, MaxCPU
| render timechart
// Correlate errors with recent deployments (Activity Log)
let incidentStart = datetime(2026-03-19 14:20:00);
AzureActivity
| where TimeGenerated between ((incidentStart - 2h) .. incidentStart)
| where OperationNameValue has "write"
| where ActivityStatusValue == "Success"
| where ResourceProviderValue == "MICROSOFT.WEB"
| project TimeGenerated,
OperationName = OperationNameValue,
Resource = _ResourceId,
InitiatedBy = Caller
| order by TimeGenerated descStep 4: Verifying the fix is working
After deploying a fix, do not close the incident immediately. Verify the fix is actually working using the same metrics and logs that identified the problem.
// Verify error rate is returning to baseline after fix deployment
AppRequests
| where TimeGenerated > ago(2h)
| summarize
Total = count(),
Errors = countif(Success == false),
AvgLatency = avg(DurationMs)
by bin(TimeGenerated, 2m)
| extend ErrorRate = round(100.0 * Errors / Total, 2)
| extend Phase = iff(TimeGenerated < datetime(2026-03-19 15:00:00), "incident", "post-fix")
| render timechart
// Confirm the specific error pattern is gone
AppExceptions
| where TimeGenerated > ago(30m)
| where ExceptionType == "System.Data.SqlClient.SqlException"
| count
// Expected: 0 (or dramatically reduced from the incident count)Common mistakes
- Fixing symptoms instead of root causes. Restarting a crashing application service removes the symptom (the crash) but not the cause (the memory leak or the dependency failure causing it). Every fix should be preceded by a clear statement of the root cause. If you cannot state the root cause, you are not ready to fix.
- Changing multiple things at once during an incident. When an engineer restarts the application, increases the memory limit, and deploys a config change all at once, there is no way to know which change (if any) resolved the issue. Make one change at a time during incidents. This feels slower but produces reliable post-incident knowledge.
- Not preserving query history during the investigation. Browser tabs close, sessions expire, and KQL query history disappears. Keep a running document during the incident with key findings: “at 14:23, 45% of orders were failing with SqlException timeout. At 14:31, we identified the database connection pool was exhausted. At 14:45, after increasing the pool size from 50 to 200 connections, errors returned to baseline.”
- Treating the monitoring tools themselves as untrusted. Occasionally engineers doubt whether Application Insights is “actually showing the right data.” In most cases, the data is accurate. If you think the monitoring is wrong, verify with direct log inspection (
az webapp log tail, kubectl logs) rather than ignoring the monitoring signal entirely.
Summary
- Production debugging is hypothesis-driven: form a hypothesis, find data that confirms or refutes it, and iterate — do not fix before you understand.
- Start by establishing the timeline and scope (when did it start, which services, how many users), then trace a specific failing request end-to-end.
- Dependency failure comparison (before vs. during) reveals whether the problem is in your code or a downstream service.
- Snapshot Debugger captures local variable values at the point of exception for bugs where variable state is the critical question.
- Verify fixes with the same metrics and logs used to identify the problem; one change at a time during active incidents.
Frequently asked questions
Can I use a debugger on a production Azure app without stopping it?
Yes. Azure App Service supports remote debugging via Visual Studio, and Application Insights Snapshot Debugger captures call stacks and local variable values when exceptions occur — all without pausing or restarting the application.
What is the Diagnose and Solve Problems blade?
The "Diagnose and solve problems" blade is a built-in Azure portal diagnostic assistant available for App Service, AKS, and other services. It runs automated checks for common issues — misconfiguration, memory pressure, disk space, dependency failures — and surfaces relevant metrics and logs without you writing any queries.
How do I debug a problem that only happens occasionally?
For intermittent issues, combine Application Insights smart detection (which alerts on statistical anomalies), Snapshot Debugger (which captures state on exception), and log-based queries filtered by specific error patterns. Correlation IDs linking all requests to the same user session are also invaluable for intermittent user-specific bugs.