Azure Log Analytics and KQL Queries
Log Analytics is Azure’s managed log analysis service — a hosted query engine built on the same technology as Azure Data Explorer. You send logs there from dozens of Azure services, and then you query them using KQL: a readable, pipe-based language that lets you go from raw log data to actionable insight in seconds. If you invest in learning KQL, almost every Azure monitoring and security task becomes faster.
Log Analytics workspaces
A Log Analytics workspace is the storage and query container for your log data. Think of it as a purpose-built database for operational data. Every piece of log data in Azure Monitor lives in a workspace, organized into tables.
Key workspace decisions:
- Region — Workspaces are regional. Data ingested into a workspace stays in that region, which matters for compliance.
- Retention — Default interactive retention is 30 days (free within the workspace cost). You can extend to 730 days. Beyond that, data can be archived at a lower cost tier.
- Workspace consolidation — Most organizations benefit from a small number of centralized workspaces (one per environment or one per region) rather than one workspace per resource. Cross-resource queries are easier in a single workspace.
# Create a Log Analytics workspace
az monitor log-analytics workspace create \
--resource-group myRG \
--workspace-name myWorkspace \
--location eastus \
--sku PerGB2018 \
--retention-time 90
# Show workspace details including Customer ID (used in agent config)
az monitor log-analytics workspace show \
--resource-group myRG \
--workspace-name myWorkspace \
--query "{name:name, customerId:customerId, retentionInDays:retentionInDays}" \
--output tableKQL fundamentals
KQL uses a pipeline model. You start with a table, then apply a series of operators separated by the pipe character (|). Each operator receives the output of the previous one and transforms it.
The most frequently used operators are:
where— Filter rows based on a conditionproject— Select specific columns (like SQL SELECT)summarize— Aggregate data (like SQL GROUP BY)order by— Sort resultslimit/take— Limit number of rows returnedextend— Add computed columnsjoin— Join two tablesrender— Visualize results as a chart
KQL query walkthrough: investigating a spike in HTTP errors
Let’s walk through a real investigation scenario. Your alerting system fires at 14:23 — HTTP 5xx errors on your App Service exceeded 20 per minute. Here is the sequence of KQL queries you would run.
Step 1: Confirm the spike and its timing.
// Confirm when errors started and how many there were
AppRequests
| where TimeGenerated > ago(2h)
| where ResultCode startswith "5"
| summarize ErrorCount = count() by bin(TimeGenerated, 1m)
| render timechartStep 2: Break down by URL to find the affected endpoint.
// Which endpoints are generating 5xx errors?
AppRequests
| where TimeGenerated between (datetime(2026-03-19 14:20:00) .. datetime(2026-03-19 14:35:00))
| where ResultCode startswith "5"
| summarize ErrorCount = count(), AvgDuration = avg(DurationMs) by Url, ResultCode
| order by ErrorCount desc
| limit 20Step 3: Find correlated exceptions.
// Find exceptions that happened at the same time as the error spike
AppExceptions
| where TimeGenerated between (datetime(2026-03-19 14:20:00) .. datetime(2026-03-19 14:35:00))
| summarize ExceptionCount = count() by ExceptionType, OuterMessage
| order by ExceptionCount desc
| limit 10Step 4: Trace the operation end-to-end.
// Get the full trace for a specific failed operation
let failedOpId = "abc123def456";
union AppRequests, AppDependencies, AppExceptions
| where OperationId == failedOpId
| project TimeGenerated, ItemType = itemType, Name, DurationMs, Success, ResultCode, OuterMessage
| order by TimeGenerated ascThe union operator lets you query multiple tables at once. union AppRequests, AppDependencies, AppExceptions combines all three into a single result set, which is perfect for reconstructing a request’s full journey.
Key tables to know
| Table Name | What it Contains | Source |
|---|---|---|
| AppRequests | HTTP requests processed by the application | Application Insights |
| AppDependencies | Outbound calls (SQL, HTTP, queue, Redis) | Application Insights |
| AppExceptions | Unhandled exceptions and logged errors | Application Insights |
| AppTraces | Custom trace and log messages | Application Insights |
| AzureActivity | Azure control-plane operations (ARM) | Azure Activity Log |
| AzureDiagnostics | Diagnostic logs from multiple resource types | Diagnostic settings |
| ContainerLog | Stdout/stderr from Kubernetes pods | Container Insights |
| Perf | Performance counters from VMs and servers | Azure Monitor Agent |
| SecurityEvent | Windows Security event log | Azure Monitor Agent |
| Syslog | Linux syslog messages | Azure Monitor Agent |
Useful KQL patterns
These patterns come up frequently in real monitoring work.
// Pattern 1: Error rate as a percentage over time
AppRequests
| where TimeGenerated > ago(1h)
| summarize
Total = count(),
Errors = countif(Success == false)
by bin(TimeGenerated, 5m)
| extend ErrorRate = round(100.0 * Errors / Total, 2)
| render timechart
// Pattern 2: P95 and P99 response time (percentiles)
AppRequests
| where TimeGenerated > ago(1h)
| where Name == "GET /api/orders"
| summarize
P50 = percentile(DurationMs, 50),
P95 = percentile(DurationMs, 95),
P99 = percentile(DurationMs, 99)
by bin(TimeGenerated, 5m)
| render timechart
// Pattern 3: Top slow dependencies
AppDependencies
| where TimeGenerated > ago(1h)
| where Success == false or DurationMs > 2000
| summarize
CallCount = count(),
AvgDuration = avg(DurationMs),
FailCount = countif(Success == false)
by Target, DependencyType
| order by FailCount desc, AvgDuration desc
// Pattern 4: Users affected by errors
AppRequests
| where TimeGenerated > ago(1h)
| where Success == false
| summarize
ErrorCount = count(),
AffectedUsers = dcount(UserId)
by bin(TimeGenerated, 10m)
| render timechartSaved queries and query packs
You can save KQL queries in the Log Analytics workspace for reuse by your team. Saved queries appear in the query browser alongside Microsoft’s built-in example queries. For larger teams, query packs let you package and share a collection of queries across workspaces — useful for standardizing the queries your on-call team uses during incidents.
# Create a query pack
az monitor log-analytics query-pack create \
--resource-group myRG \
--query-pack-name "OnCallQueryPack" \
--location eastus
# Add a query to the pack (via REST since CLI has limited query-pack support)
# Typically done through the portal or ARM templateMicrosoft provides hundreds of pre-built queries in the Log Analytics portal under the “Queries” tab. These cover common scenarios like VM performance, container logs, security events, and network diagnostics. Browse these before writing queries from scratch.
Common mistakes
- Querying without a time filter. Log Analytics will scan all data in the table if you do not include a
where TimeGenerated > ago(Xh)clause. This is slow and potentially expensive. Always start with a time filter. - Using
AzureDiagnosticsas a default table. Many resources previously sent all their logs to the catch-allAzureDiagnosticstable. Newer resources use resource-specific tables (likeAzureFirewallApplicationRuleorStorageBlobLogs) which are faster and easier to query. Check which table your resource uses before writing queries. - Over-collecting logs. Ingesting every debug-level log entry from every service into a workspace is expensive and creates noise. Be deliberate: collect Warning and Error levels broadly, and enable Debug/Verbose only for specific troubleshooting windows.
- Not indexing on custom columns. KQL queries on custom string columns in
AzureDiagnosticscan be slow because the column schema is dynamic. Where possible, use resource-specific tables or Application Insights custom properties with clear, consistent names.
Summary
- Log Analytics workspaces store all Azure Monitor log data and are queried using KQL.
- KQL uses a pipeline model — data flows through operators like
where,summarize,project, andrender. - Always include a time filter (
where TimeGenerated > ago(Xh)) at the start of every query to keep queries fast and cost-efficient. - Key tables include AppRequests, AppDependencies, AppExceptions, AzureActivity, ContainerLog, and Perf.
- Saved queries and query packs help teams standardize on-call investigation procedures.
Frequently asked questions
What is KQL?
KQL (Kusto Query Language) is the query language used in Azure Log Analytics, Azure Data Explorer, and Microsoft Sentinel. It uses a pipe-based syntax where each operator transforms the result of the previous one.
How do I find which table to query?
In the Log Analytics workspace portal, the schema browser on the left lists all available tables. Tables are grouped by solution — AzureDiagnostics, AppRequests, ContainerLog, SecurityEvent, etc.
Is there a cost to running KQL queries?
Querying data that is within the interactive retention period (default 30 days) has no query cost. Querying archived data (beyond 30 days) incurs a search job cost. Log ingestion itself is the primary cost driver.