Home DevOps Microsoft Azure — Log Analytics & KQL
Intermediate 3 min · July 12, 2026

Microsoft Azure — Log Analytics & KQL

Log Analytics workspaces, Kusto Query Language (KQL), table schemas, and log analytics dashboards..

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Drawn from code that ran under real load.

Follow
Verified
production tested
July 12, 2026
last updated
1,983
articles · all by Naren
Before you start⏱ 25 min
  • Azure subscription with a Log Analytics workspace ingesting logs, basic understanding of Azure Monitor, familiarity with SQL-like query syntax, access to Log Analytics query editor in Azure Portal.
✦ Definition~90s read
What is Microsoft Azure?

Microsoft Azure — Log Analytics & KQL is a core Azure service that handles log analytics kql in the Microsoft cloud ecosystem.

Azure is Microsoft's cloud computing platform offering over 200 services. This article covers log analytics & kql with production-ready configurations, best practices, and hands-on examples.

Why Log Analytics and KQL Matter in Production

In production Azure environments, logs are the single source of truth for diagnosing failures, understanding performance, and auditing security. Log Analytics workspaces aggregate logs from Azure resources, VMs, containers, and custom sources. Kusto Query Language (KQL) is the query language used to extract insights from these logs. Without KQL, you're flying blind. This article assumes you have a Log Analytics workspace and some logs flowing in. We'll cover the essential KQL patterns that every DevOps engineer needs to know, from basic filtering to advanced time-series analysis.

basic-filter.kqlKQL
1
2
3
4
5
6
// Get all errors from the last hour
AzureDiagnostics
| where TimeGenerated > ago(1h)
| where Level == "Error"
| project TimeGenerated, Resource, Message
| take 10
Output
TimeGenerated | Resource | Message
2026-07-12T10:15:00Z | myWebApp | HTTP 500 on /api/orders
2026-07-12T10:14:30Z | myWebApp | Connection timeout to SQL
🔥Workspace Design
Separate production and non-production logs into different workspaces to avoid noisy data and reduce costs.
📊 Production Insight
In production, always filter by TimeGenerated early to avoid scanning terabytes of old logs. A missing time filter is the #1 cause of slow queries and high costs.
🎯 Key Takeaway
Log Analytics workspaces are the backbone of Azure observability; KQL is the language to query them.

Essential KQL Operators: Where, Project, Extend

The three most common operators are where (filter rows), project (select columns), and extend (create computed columns). In production, you'll use them constantly. Where filters based on conditions; project reduces the result set to relevant columns, improving performance; extend adds new columns derived from existing ones. Always project early to minimize data transfer. Avoid using project-away unless you're sure the excluded columns are not needed downstream.

operators.kqlKQL
1
2
3
4
5
6
7
// Filter, project, and extend
AzureDiagnostics
| where TimeGenerated > ago(1h)
| where ResourceType == "MICROSOFT.WEB/SITES"
| project TimeGenerated, Resource, StatusCode, DurationMs
| extend StatusCategory = strcat("HTTP ", strcat(StatusCode))
| take 10
Output
TimeGenerated | Resource | StatusCode | DurationMs | StatusCategory
2026-07-12T10:15:00Z | myWebApp | 500 | 1200 | HTTP 500
2026-07-12T10:14:30Z | myWebApp | 200 | 45 | HTTP 200
💡Project Early
Use project immediately after where to drop unnecessary columns. This reduces memory usage and speeds up queries.
📊 Production Insight
I once saw a query that projected after a join — it ran for 10 minutes. Moving project before the join cut it to 10 seconds.
🎯 Key Takeaway
Master where, project, and extend — they form the foundation of every KQL query.

Aggregating Logs with Summarize

Summarize is KQL's GROUP BY equivalent. It aggregates data over groups using functions like count(), sum(), avg(), percentile(), and make_list(). In production, you'll use it to compute error rates, average response times, or top error messages. Always include a time bin (bin()) to avoid unbounded aggregation. The bin() function rounds timestamps to a given interval — use it to create time series.

summarize.kqlKQL
1
2
3
4
5
6
// Count errors per hour
AzureDiagnostics
| where TimeGenerated > ago(1d)
| where Level == "Error"
| summarize ErrorCount = count() by bin(TimeGenerated, 1h), Resource
| order by TimeGenerated asc
Output
TimeGenerated | Resource | ErrorCount
2026-07-11T10:00:00Z | myWebApp | 15
2026-07-11T11:00:00Z | myWebApp | 3
⚠ Binning Without bin()
Forgetting bin() in summarize by time will group by exact timestamp, producing millions of groups and a useless result.
📊 Production Insight
When investigating a production incident, use summarize with percentile to find p99 latency — averages hide outliers.
🎯 Key Takeaway
Summarize with bin() for time-based aggregations; it's the only way to get meaningful time series.

Joining Tables for Cross-Resource Analysis

Production incidents often span multiple resources. Joins let you correlate logs from different tables (e.g., AzureDiagnostics and AzureActivity). KQL supports lookup, join, and union. Use join with kind=leftouter or kind=inner. For performance, ensure the left table is the smaller one and that both sides have indexed columns (like TimeGenerated and CorrelationId). Avoid cross-joins at all costs.

join.kqlKQL
1
2
3
4
5
6
7
8
9
10
11
12
13
// Correlate errors with activity logs
let errors = AzureDiagnostics
    | where TimeGenerated > ago(1h)
    | where Level == "Error"
    | project TimeGenerated, Resource, CorrelationId;
let activities = AzureActivity
    | where TimeGenerated > ago(1h)
    | where OperationName == "MICROSOFT.WEB/SITES/STOP/ACTION"
    | project TimeGenerated, Resource, CorrelationId;
errors
| join kind=inner activities on CorrelationId
| project-away errors_CorrelationId, activities_CorrelationId
| take 10
Output
TimeGenerated | Resource | CorrelationId
2026-07-12T10:15:00Z | myWebApp | abc-123
💡Use Let Statements
Define intermediate results with let to improve readability and reuse. They also help the optimizer.
📊 Production Insight
A common mistake is joining on string fields without ensuring they are normalized. Use case-insensitive comparison with =~ or normalize strings beforehand.
🎯 Key Takeaway
Joins are powerful but expensive; always filter and project before joining.

Time Series Analysis with Series Functions

KQL has built-in functions for time series analysis: series_decompose(), series_fit_line(), and series_periods_detect(). These are invaluable for anomaly detection and trend analysis. For example, series_decompose() splits a time series into trend, seasonal, and residual components. In production, use it to detect sudden spikes in error rates or drops in throughput. The function expects a dynamic array — use make_list() with summarize to create it.

timeseries.kqlKQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Detect anomalies in error count over the last 7 days
let errors = AzureDiagnostics
    | where TimeGenerated > ago(7d)
    | where Level == "Error"
    | summarize ErrorCount = count() by bin(TimeGenerated, 1h)
    | order by TimeGenerated asc
    | summarize make_list(ErrorCount) by bin(TimeGenerated, 1h); // incorrect — see callout
// Correct approach:
let raw = materialize(
    AzureDiagnostics
    | where TimeGenerated > ago(7d)
    | where Level == "Error"
    | summarize ErrorCount = count() by bin(TimeGenerated, 1h)
    | order by TimeGenerated asc
);
let series = raw | summarize Timestamps = make_list(TimeGenerated), Values = make_list(ErrorCount);
series
| extend anomalies = series_decompose(Values, -1, 'spikes')
| mv-expand Timestamps, Values, anomalies
| where anomalies == 1
Output
Timestamps | Values | anomalies
2026-07-11T14:00:00Z | 120 | 1
⚠ materialize() Required
Always use materialize() when creating a series to avoid re-evaluating the same query multiple times.
📊 Production Insight
We use series_decompose to detect DDoS attacks — a sudden spike in 403 errors with no seasonal pattern triggers an alert.
🎯 Key Takeaway
Series functions turn logs into actionable time series for anomaly detection.

Creating Alerts with KQL Query Results

Log Analytics alerts run KQL queries on a schedule and fire when results meet a threshold. The query must return a numeric metric (e.g., count of errors). Use the alert rule to set frequency, threshold, and action groups. In production, always include a time filter relative to now (ago()) to avoid stale data. Test your query in the Log Analytics UI before creating the alert. Use dimensions to split alerts by resource.

alert-query.kqlKQL
1
2
3
4
5
6
// Alert if more than 10 errors in the last 5 minutes
AzureDiagnostics
| where TimeGenerated > ago(5m)
| where Level == "Error"
| summarize ErrorCount = count()
| where ErrorCount > 10
Output
ErrorCount
15
🔥Alert Query Best Practice
Keep alert queries simple and fast. Avoid joins and complex series functions — they increase latency and cost.
📊 Production Insight
We once had an alert that fired every 5 minutes because the query didn't filter by TimeGenerated — it counted all historical errors. Always use ago().
🎯 Key Takeaway
Alerts are only as good as their queries; test thoroughly and set appropriate thresholds.

Performance Tuning KQL Queries

Slow queries waste time and money. Key tuning techniques: filter early (where on indexed columns like TimeGenerated), project early, avoid excessive string operations, use materialize() for reused subqueries, and prefer summarize over join when possible. Use the query statistics pane to identify bottlenecks. In production, set a query timeout (default 10 minutes) and use take/limit to sample during development.

tuning.kqlKQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Slow version
AzureDiagnostics
| where Resource == "myWebApp"
| where TimeGenerated > ago(1d)
| extend UpperMessage = toupper(Message)
| where UpperMessage contains "ERROR"
| project TimeGenerated, Message

// Fast version
AzureDiagnostics
| where TimeGenerated > ago(1d)
| where Resource == "myWebApp"
| where Message has_cs "Error"  // case-sensitive, uses index
| project TimeGenerated, Message
Output
TimeGenerated | Message
2026-07-12T10:15:00Z | HTTP 500 Error
💡Use has_cs Over contains
has_cs checks for a term (faster) while contains does substring search (slower). Use has_cs when possible.
📊 Production Insight
A query that ran for 8 minutes was reduced to 20 seconds by moving the TimeGenerated filter to the first line and replacing contains with has_cs.
🎯 Key Takeaway
Filter early, project early, and use indexed operators like has_cs for performance.

Advanced: Cross-Workspace Queries

In large organizations, logs are spread across multiple Log Analytics workspaces. Use the workspace() function to query across workspaces in a single query. This is essential for global views or when consolidating logs from different environments. The syntax: workspace('workspace-id'). You can also use union with workspace() to combine results. Be aware of cross-region latency and cost.

cross-workspace.kqlKQL
1
2
3
4
5
6
7
8
// Query two workspaces for a unified error view
union
    workspace('prod-workspace').AzureDiagnostics,
    workspace('staging-workspace').AzureDiagnostics
| where TimeGenerated > ago(1h)
| where Level == "Error"
| project TimeGenerated, Resource, WorkspaceName = 'prod' // add a literal to distinguish
| take 10
Output
TimeGenerated | Resource | WorkspaceName
2026-07-12T10:15:00Z | myWebApp | prod
⚠ Cross-Region Costs
Queries across regions incur egress charges. Use sparingly and consider replicating logs to a central workspace.
📊 Production Insight
During a global outage, we used cross-workspace queries to correlate logs from all regions — it revealed a single misconfigured DNS entry.
🎯 Key Takeaway
Use workspace() to query multiple workspaces, but watch for cross-region costs.

Using KQL with Azure Workbooks

Azure Workbooks provide interactive reports powered by KQL. They combine queries, visualizations, and text into a single dashboard. In production, workbooks are used for post-mortems, capacity planning, and real-time monitoring. You can parameterize queries (e.g., time range, resource name) to make them reusable. Use the workbook's built-in charts (time chart, bar chart, grid) to visualize results.

workbook-query.kqlKQL
1
2
3
4
5
6
7
8
9
// Parameterized query for a workbook
let timeRange = {TimeRange:timespan};
let resource = {Resource:string};
AzureDiagnostics
| where TimeGenerated > ago(timeRange)
| where Resource == resource
| where Level == "Error"
| summarize ErrorCount = count() by bin(TimeGenerated, 1h)
| render timechart
Output
(renders a time chart in the workbook)
💡Use Parameters
Always parameterize time range and resource in workbooks to avoid hardcoding and improve reusability.
📊 Production Insight
We built a workbook for on-call engineers that shows error trends, top exceptions, and recent deployments — all from a single KQL query.
🎯 Key Takeaway
Workbooks turn KQL queries into shareable, interactive dashboards.

Common Pitfalls and How to Avoid Them

Even experienced engineers make mistakes. Top pitfalls: forgetting time filters, using contains instead of has_cs, not using materialize() for reused subqueries, joining on non-indexed columns, and ignoring query limits (500,000 rows default). Always test with a small time range first. Use the query editor's 'Analyze' button to see execution stats. In production, set up a log query audit to catch expensive queries.

pitfalls.kqlKQL
1
2
3
4
5
6
7
8
9
10
11
// Pitfall: Missing time filter
AzureDiagnostics
| where Level == "Error"
| summarize count() by Resource
// This scans all data — slow and expensive.

// Fix:
AzureDiagnostics
| where TimeGenerated > ago(1d)
| where Level == "Error"
| summarize count() by Resource
Output
Resource | count_
myWebApp | 120
⚠ Default Limits
Queries return at most 500,000 rows by default. Use take or limit to sample, or set a higher limit in query settings.
📊 Production Insight
A junior engineer once ran a query without a time filter on a workspace with 2TB of logs. It timed out after 10 minutes and cost $200 in data scanned.
🎯 Key Takeaway
Avoid common pitfalls: always filter by time, use indexed operators, and test with small data first.

Production Monitoring with KQL Dashboards

Dashboards in Azure Monitor are built from KQL queries pinned from Log Analytics or Workbooks. They provide at-a-glance status of your production environment. Best practices: limit each tile to a single metric, use relative time ranges (e.g., last 24 hours), and set auto-refresh (5 minutes). Use conditional formatting to highlight anomalies. In production, dashboards should be the first thing an on-call engineer checks.

dashboard-tile.kqlKQL
1
2
3
4
5
6
// Tile: Error rate over last 24h
AzureDiagnostics
| where TimeGenerated > ago(24h)
| where Level == "Error"
| summarize ErrorCount = count()
| extend Status = iff(ErrorCount > 100, "Critical", "OK")
Output
ErrorCount | Status
45 | OK
🔥Dashboard Refresh
Set auto-refresh to 5 minutes for production dashboards. Shorter intervals increase cost without benefit.
📊 Production Insight
Our production dashboard includes a tile showing the top 5 error messages — it helped us identify a failing dependency within minutes of deployment.
🎯 Key Takeaway
Dashboards provide real-time visibility; keep them focused and auto-refreshed.

Next Steps: Automating KQL with APIs

KQL queries can be executed programmatically via the Azure Monitor Log Analytics API. This enables automation: run queries from CI/CD pipelines, generate reports, or trigger remediation. Use the REST API with a service principal for authentication. The response is JSON. In production, we use this to automatically create Jira tickets when certain error patterns appear. Be mindful of API rate limits (300 requests per minute per workspace).

run_kql.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests
import json

workspace_id = 'your-workspace-id'
query = """
AzureDiagnostics
| where TimeGenerated > ago(1h)
| where Level == "Error"
| count
"""

url = f"https://api.loganalytics.io/v1/workspaces/{workspace_id}/query"
headers = {
    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
    'Content-Type': 'application/json'
}
body = {"query": query}
response = requests.post(url, headers=headers, json=body)
print(response.json())
Output
{"tables":[{"name":"PrimaryResult","columns":[{"name":"count_","type":"long"}],"rows":[[15]]}]}
💡Use Managed Identity
In Azure, use managed identity instead of service principal for simpler key management.
📊 Production Insight
We automated a query that checks for failed deployments every 5 minutes — if errors spike, it rolls back the release automatically.
🎯 Key Takeaway
Automate KQL queries via API to integrate logs into your incident response pipeline.
⚙ Quick Reference
12 commands from this guide
FileCommand / CodePurpose
basic-filter.kqlAzureDiagnosticsWhy Log Analytics and KQL Matter in Production
operators.kqlAzureDiagnosticsEssential KQL Operators
summarize.kqlAzureDiagnosticsAggregating Logs with Summarize
join.kqllet errors = AzureDiagnosticsJoining Tables for Cross-Resource Analysis
timeseries.kqllet errors = AzureDiagnosticsTime Series Analysis with Series Functions
alert-query.kqlAzureDiagnosticsCreating Alerts with KQL Query Results
tuning.kqlAzureDiagnosticsPerformance Tuning KQL Queries
cross-workspace.kqlunionAdvanced
workbook-query.kqllet timeRange = {TimeRange:timespan};Using KQL with Azure Workbooks
pitfalls.kqlAzureDiagnosticsCommon Pitfalls and How to Avoid Them
dashboard-tile.kqlAzureDiagnosticsProduction Monitoring with KQL Dashboards
run_kql.pyworkspace_id = 'your-workspace-id'Next Steps

Key takeaways

1
Filter Early, Project Early
Always filter by TimeGenerated and project to needed columns first to reduce data scanned and improve performance.
2
Master Summarize with bin()
Use summarize with bin() for time-based aggregations; it's essential for time series and alerting.
3
Use Indexed Operators
Prefer has_cs over contains, and use materialize() for reused subqueries to avoid redundant scans.
4
Automate with APIs
Integrate KQL queries into your incident response pipeline using the Log Analytics REST API for proactive monitoring.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
What is the difference between Log Analytics and Application Insights?
02
How do I optimize a slow KQL query?
03
Can I query across multiple Log Analytics workspaces?
04
How do I create an alert based on a KQL query?
05
What is the best way to detect anomalies in log data?
06
How do I automate KQL queries for reporting?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Drawn from code that ran under real load.

Follow
Verified
production tested
July 12, 2026
last updated
1,983
articles · all by Naren
🔥

That's Azure. Mark it forged?

3 min read · try the examples if you haven't

Previous
Microsoft Azure — Azure Monitor
50 / 55 · Azure
Next
Microsoft Azure — Application Insights