Skip to content
CAMPUX Cloud Bootcamp
Field notes · Observability
Azure Monitor KQL Examples

Azure Monitor KQL query examples you can copy, and understand.

By Captain O9 min read

Most KQL pages are reference dumps — every operator, no context. This is the opposite: a short set of queries that do the things you need in Azure Monitor, each with one plain line explaining what it does and why.

New to cloud? CAMPUX is a free, build-first course. Start here →

KQL reads top-to-bottom as a pipeline — you start from a table and pipe it through filters and aggregations with |; master five operators (where, summarize, project, order by, render) and you can write most Azure Monitor queries. That is the whole trick, and once it clicks the language stops feeling like SQL you half-remember and starts feeling like a Unix pipe. Below are real, correct queries for the common tasks. Treat the table and column names as the parts you swap; the shape stays the same.

How to think in pipelines

A KQL query is a table name followed by a series of steps, each separated by a pipe. The first line names the data — AzureActivity, Heartbeat, AppRequests — and every line after it takes the rows the previous line produced and does one thing to them. Filter, then group, then sort, then draw. The engine runs the steps in the exact order you wrote them, so the order is not cosmetic: it decides both what you get back and how fast you get it.

That is the mental model competitors skip. They hand you a wall of operators and leave you to guess the sequence. In practice almost every query you write is some subset of the same five moves, in the same order: narrow the rows with where, collapse them with summarize, pick the columns with project, sort with order by or top, and optionally render a chart. Learn that spine and the rest is detail.

OperatorWhat it doesExample snippet
whereKeeps only the rows that match a condition. Your primary filter — put the time filter first.| where TimeGenerated > ago(1h)
summarizeAggregates rows into groups — counts, sums, averages, one row per group.| summarize count() by Computer
projectChooses which columns to keep, in what order, and can rename them.| project TimeGenerated, Caller
extendAdds a new calculated column without dropping the existing ones.| extend Cpu = round(CounterValue,1)
order by / topSorts rows; top N by sorts and keeps only the first N.| top 5 by Count desc
renderDraws the result as a chart — timechart, barchart, piechart.| render timechart

If you are completely new to the language, read KQL for beginners first — it walks the pipeline idea from zero. This note assumes you have the concept and want working queries.

Filtering: time and level

Nearly every query starts by narrowing to a time window and a severity. This one pulls Azure control-plane events from the last day that came back as errors.

AzureActivity
| where TimeGenerated > ago(24h)
| where Level == "Error"
| project TimeGenerated, OperationNameValue, Caller, ResourceGroup

Does Last 24 hours of failed subscription-level operations — what was done, by whom, and where. ago(24h) is a rolling window; use 7d, 30m, or 90d to widen or tighten it.

Counting and grouping

summarize collapses many rows into a few. The classic shape is count() by some column, which answers "how many of each?"

AzureActivity
| where TimeGenerated > ago(7d)
| summarize Events = count() by OperationNameValue
| order by Events desc

Does Ranks the past week's activity by operation, busiest first. Naming the aggregate (Events =) beats the default count_ column name when you sort or chart it.

A time chart

To see a trend rather than a total, group by a time bucket with bin() and hand the result to render. bin(TimeGenerated, 1h) rounds each row down to the hour, so you get one point per hour.

AppRequests
| where TimeGenerated > ago(24h)
| summarize Requests = count() by bin(TimeGenerated, 1h)
| render timechart

Does Plots request volume per hour for the last day from Application Insights. Swap 1h for 5m to zoom in, or add , ResultCode after the bin to get one line per status code.

Top-N errors

When something is on fire you want the worst offenders, not everything. top N by sorts and truncates in one step.

AppExceptions
| where TimeGenerated > ago(24h)
| summarize Count = count() by ProblemId
| top 5 by Count desc

Does The five most frequent exception signatures in the last day. ProblemId groups exceptions by type and location, so this is your "what is breaking most" query.

Selecting and renaming columns

Raw tables are wide. project keeps only the columns you care about and gives them readable names, which matters when you export a result or paste it into a ticket.

AzureActivity
| where TimeGenerated > ago(1h)
| project Time = TimeGenerated, Operation = OperationNameValue,
          Caller, Status = ActivityStatusValue

Does A tidy four-column view of the last hour with friendlier headers. project also sets column order; use project-away instead when you want everything except a couple of columns.

Adding a calculated field

extend is project's cousin: it adds a column instead of replacing the set. Here it reaches into a dynamic (JSON) column to pull a value out and then groups by it.

SigninLogs
| where TimeGenerated > ago(24h)
| extend City = tostring(LocationDetails.city)
| summarize Signins = count() by City
| order by Signins desc

Does Counts the last day's sign-ins by city. LocationDetails is a nested object; tostring(LocationDetails.city) extracts one field and extend makes it a real column you can group on.

Joining two tables

Sometimes the answer lives across two tables — a request and the exception it threw. join matches rows on a shared key. Application Insights ties telemetry together with OperationId.

AppRequests
| where TimeGenerated > ago(1h)
| where Success == false
| join kind=inner (
    AppExceptions
    | where TimeGenerated > ago(1h)
  ) on OperationId
| project TimeGenerated, RequestName = Name, ProblemId, OperationId

Does Pairs each failed request in the last hour with the exception raised in the same operation. kind=inner keeps only rows that match on both sides; filter both tables by time so neither scans more than it must.

A heartbeat / availability check

The Heartbeat table gets a ping from every monitored machine. Compare each machine's last ping to now and you have a rough "is it alive" report.

Heartbeat
| where TimeGenerated > ago(1h)
| summarize LastSeen = max(TimeGenerated) by Computer
| extend Status = iff(LastSeen < ago(5m), "MISSING", "OK")
| order by LastSeen asc

Does Lists each machine with the time it last checked in and flags anything silent for over five minutes. This is the bones of a "VM went dark" alert rule.

The order of your lines is not cosmetic — it decides both what you get back and how fast you get it.

The mistakes that trip people up

Three catch nearly everyone at the start, and none of them throw an obvious error — they just give you the wrong answer or a slow one.

Filter time first. Put where TimeGenerated > ago(...) as the very first step. Log Analytics partitions data by time, so an early time filter lets the engine skip whole chunks it never has to read. The same query with the time filter last can be many times slower and cost more.

summarize throws away columns. After a summarize the only columns that survive are the ones you aggregated and the ones in the by clause — everything else is gone. If you want a column in the output, aggregate it, group by it, or project it before the summarize, not after.

== is case-sensitive. Level == "error" matches nothing if the data stores "Error". Use =~ for a case-insensitive match, or has for a whole-word search that is both faster and case-insensitive on free text.

How to build a query without memorizing anything

Open Logs in the portal, type a table name alone, and run it. You will see the columns. Add one line at a time: a where, run it, a summarize, run it again. The result updates at each step, so you are never writing blind. Autocomplete offers the column names and inline docs explain each operator. Copy a query from this page, change the table, and watch what happens. That loop, add a line then run then read, is how people who look fluent actually work.

Where these queries run

All of the above run unchanged in the Logs blade of the Azure portal, in a Log Analytics workspace, and in Microsoft Sentinel, because they share the same engine and the same tables. Application Insights resources expose the App* tables (AppRequests, AppExceptions, AppDependencies) when workspace-based, which is the default now. The one thing to watch is that a table only exists if something is sending data to it. No VMs reporting means an empty Heartbeat, no App Insights means no AppRequests. Any single query here is only as current as the tables and columns behind it, so run it against your own workspace before you trust the output; the pipeline shape and the operators hold steady, while the exact column values are yours to confirm.

If you are deciding how to lay out the workspace these tables live in, the Log Analytics workspace design note covers the trade-offs, and the wider Azure Monitor, App Insights and Log Analytics piece explains how the pieces fit together before you query them.

Questions people also ask

How do I write a KQL query in Azure Monitor?

Open Logs in the Azure portal or your Log Analytics workspace, then start with a table name and pipe it through operators with the vertical bar. A minimal query is a table on its own line; a useful one adds a where filter on TimeGenerated, then a summarize to aggregate, then order by or render. The query runs top to bottom, so each line transforms the rows the previous line produced.

What is KQL used for in Azure?

KQL, the Kusto Query Language, is the read-only query language for log and telemetry data across Azure Monitor, Log Analytics, Application Insights, Microsoft Sentinel, and Azure Data Explorer. You use it to search logs, count and chart events over time, investigate errors and security signals, and power alert rules and workbooks. It is built for large time-series datasets rather than for updating records the way SQL does.

What are the most common KQL operators?

Five cover most queries: where filters rows, summarize aggregates them into counts and groups, project selects and renames columns, order by (or top) sorts, and render turns the result into a chart. extend adds a calculated column and join combines two tables. Learn those and you can write the large majority of Azure Monitor queries without looking anything up.

How do I filter by time in KQL?

Filter on the TimeGenerated column with the ago() function, for example where TimeGenerated > ago(24h) for the last day, ago(7d) for the last week, or ago(30m) for the last half hour. Put the time filter as early as possible in the query, ideally the first where, because it lets the engine skip data it does not need to scan and makes the query dramatically faster.

Is KQL hard to learn?

No. KQL is one of the friendlier query languages to start with because it reads as a straight top-to-bottom pipeline and the portal gives you autocomplete and inline docs. You can write real, useful queries within an hour by copying working examples and changing the table and column names. The depth comes later with joins, parsing, and time-series functions, but the basics are quick.

Further reading — the Microsoft docs
Your next class · free
You've read the idea. Class 29 — KQL Essentials is where you build it, hands-on — no account needed.Start Class 29 →
Captain O
Founder & instructor · CAMPUX Cloud Engineering Bootcamp
Back to all field notes →