Querying Logs with KQL
Complete the full lesson to earn 25 points
Work through each section, then tap “Mark as Complete” on the last one.
✦ Skip the page breaks and see fewer ads — read each lesson on a single page with Pro
Mastering KQL: The Language of Azure Monitor
Introduction: Why KQL Matters
In the modern cloud ecosystem, the ability to observe your infrastructure is just as important as the ability to deploy it. Azure Monitor serves as the central nervous system for your cloud environment, collecting vast amounts of telemetry data from virtual machines, databases, web applications, and network components. However, raw data is useless without a way to extract meaningful insights. This is where Kusto Query Language (KQL) comes in.
KQL is a powerful, read-only query language designed to process massive datasets with high performance. Unlike SQL, which is optimized for transactional databases and relational joins, KQL is built specifically for log analytics and time-series data. If you are responsible for maintaining Azure resources, learning KQL is not just an optional skill—it is the primary tool you will use to troubleshoot outages, identify performance bottlenecks, and satisfy audit requirements. Whether you are investigating a failed login attempt or calculating the average memory usage of a cluster over the last week, KQL provides the syntax to ask the right questions and get immediate answers.
Understanding the KQL Data Model
Before writing your first query, it is essential to understand how data is organized within Azure Monitor Logs. Data is stored in tables, and each table contains columns with specific data types. When you query these tables, you are essentially filtering, transforming, and aggregating rows of data points.
Every KQL query starts with a table name or a data source. From there, you use a pipe character (|) to pass the output of one command to the next. This "pipelining" approach makes KQL highly readable and allows you to build complex logic incrementally. You start with a broad dataset and refine it step-by-step until you have the exact answer you need.
The Anatomy of a Query
A typical KQL query follows a logical flow:
- Source: Identify the table (e.g.,
AzureActivity,Heartbeat,AppRequests). - Filter: Narrow down the scope using
whereclauses (e.g., time range, status codes, resource IDs). - Project/Extend: Choose specific columns to view or create new calculated columns.
- Aggregate: Use functions like
summarizeto group data and perform calculations (e.g.,count,avg,sum). - Sort/Limit: Organize the final output for readability.
Callout: KQL vs. SQL While both languages allow you to query data, they serve different purposes. SQL is designed for ACID-compliant transactional systems where data integrity and complex relationships are paramount. KQL is designed for "Big Data" scenarios where you are scanning millions of log entries sequentially. In SQL, you write a single monolithic statement with nested subqueries. In KQL, you build a pipeline of operations, which makes troubleshooting your own queries much easier because you can run the query up to any pipe to see the intermediate results.
Getting Started: The Basic Operators
To be effective with Azure Monitor, you need to master the fundamental operators. Let’s look at the most common ones you will use on a daily basis.
1. The where Operator
The where operator is your primary tool for filtering. It acts as a gatekeeper, removing any records that do not meet your specified criteria. You can use standard comparison operators like ==, !=, >, <, and contains.
// Get all failed login attempts in the last 24 hours
SigninLogs
| where TimeGenerated > ago(24h)
| where ResultType != 0
In this example, ago(24h) is a dynamic time function that ensures your query always looks at the most recent data. ResultType != 0 filters out successful logins, leaving you with only the errors.
2. The project and extend Operators
The project operator allows you to select only the columns you want to see. This is useful for cleaning up the output and focusing on relevant fields. The extend operator, on the other hand, allows you to create new columns based on existing ones.
// Calculate the duration in seconds and rename the column
AppRequests
| extend DurationInSeconds = DurationMs / 1000
| project TimeGenerated, Name, DurationInSeconds, Success
3. The summarize Operator
The summarize operator is the heart of KQL for reporting. It allows you to group data by one or more columns and calculate statistics for those groups.
// Count the number of requests per application over the last hour
AppRequests
| where TimeGenerated > ago(1h)
| summarize RequestCount = count() by AppRoleName
Tip: Always include a
where TimeGeneratedfilter in your queries. Azure Monitor logs can contain petabytes of data, and searching without a time constraint is inefficient and can lead to query timeouts or performance degradation.
Advanced Data Manipulation
As you move beyond basic filtering, you will need to handle more complex data structures and time-based analysis.
Binning Time
When looking at trends, you rarely want to see a raw count of every event. Instead, you want to see trends over time intervals. The bin() function is perfect for this.
// Visualize the number of errors per 5-minute interval
AzureActivity
| where TimeGenerated > ago(6h)
| where ActivityStatusValue == 'Failed'
| summarize ErrorCount = count() by bin(TimeGenerated, 5m)
| render timechart
The render operator is a unique feature of KQL that allows you to output the data directly into a visual chart within the Azure portal, making it much easier to spot anomalies or patterns.
Sorting and Limiting
When dealing with large result sets, you should always limit your output to prevent excessive data transfer.
// Find the top 10 longest-running requests
AppRequests
| top 10 by DurationMs desc
Best Practices for Writing Efficient Queries
Writing queries that run quickly and consume fewer resources is a hallmark of an expert Azure administrator. Follow these guidelines to ensure your queries are optimized.
- Filter Early: The most important rule in KQL is to filter your data as early as possible. By placing your
whereclauses at the top of the query, you reduce the volume of data that the engine needs to process in subsequent steps. - Use Specific Columns: Avoid using
project *in your queries. Explicitly naming the columns you need reduces the amount of data processed and makes your query schema-resilient if the underlying table changes. - Avoid Case-Insensitive Searches if Possible: Using
containsis case-insensitive and requires more processing power. If you know the exact casing of your data, usehasor==instead. - Use
hasfor Full Tokens: Thehasoperator searches for full tokens. For example,Name has "Admin"will match "Admin" or "System Admin", but not "Administrator". This is significantly faster thancontainsbecause it leverages an index.
Common Pitfalls to Avoid
- Over-querying: Do not pull all columns if you only need three. It wastes bandwidth and memory.
- Ignoring Time Intervals: As mentioned, always scope your query to a specific time range. Running a query over the "last 30 days" by default is a common mistake that leads to slow dashboard performance.
- Complex Joins: While
joinis supported in KQL, it is an expensive operation. Try to uselookupif you are joining a small dimension table to a large fact table.lookupis optimized for this specific scenario.
Warning: Be cautious when using the
joinoperator on very large tables. If both sides of the join are massive, the query may exceed the memory limits of the compute node and fail. Always filter both sides of a join before performing the operation.
Practical Examples: Troubleshooting Scenarios
Scenario 1: Investigating VM Performance
Suppose you are alerted that a specific Virtual Machine is under heavy CPU load. You can use KQL to inspect the InsightsMetrics table to confirm the trend.
InsightsMetrics
| where TimeGenerated > ago(1h)
| where Namespace == 'Processor' and Name == 'UtilizationPercentage'
| where Computer == 'Production-VM-01'
| summarize AvgCPU = avg(Val) by bin(TimeGenerated, 1m)
| render timechart
This query filters for CPU utilization data for a specific machine, averages it out per minute, and displays a time chart. This allows you to see exactly when the spike occurred and how long it lasted.
Scenario 2: Analyzing Security Logs
Security logs can be noisy. To find potential brute-force attempts, you need to look for high counts of failed logins from unique IP addresses.
SigninLogs
| where TimeGenerated > ago(24h)
| where ResultType != 0
| summarize FailedCount = count() by IPAddress, UserPrincipalName
| where FailedCount > 10
| sort by FailedCount desc
This query identifies users or IP addresses that have failed to log in more than 10 times in the last day, allowing you to prioritize your investigation.
Quick Reference: Comparison Table
| Operator | Purpose | Example |
|---|---|---|
where |
Filter rows based on criteria | where Status == 200 |
summarize |
Aggregate data (count, sum, avg) | summarize count() by Category |
project |
Select specific columns | project TimeGenerated, Message |
extend |
Create calculated columns | extend Total = Price * Qty |
join |
Combine two tables | `TableA |
render |
Visualize the result | render barchart |
take / limit |
Return a sample of rows | take 100 |
Understanding Table Schemas
In Azure Monitor, you don't just query random data; you query specific tables defined by the diagnostic settings of your resources. It is vital to understand the schema of these tables.
- AzureActivity: Contains information about control plane events in Azure (e.g., who stopped a VM, who changed a resource group).
- Heartbeat: Used for health monitoring. If a VM stops sending heartbeats, it is likely offline.
- AppRequests: Contains incoming requests to your web applications, including latency and success/failure status.
- SecurityEvent: Logs from Windows security events, such as login attempts or process creation.
To inspect the schema of a table, you can simply type the table name in the query window and run it. The output will show you the first 10 rows, allowing you to see the column names and data types.
// Inspect the first 10 rows of the AppRequests table
AppRequests
| take 10
Advanced Techniques: Working with Dynamic Data
Modern logs often contain data in JSON format. KQL handles this natively with dynamic columns. If you have a column named Properties that contains a JSON object, you can access nested fields using dot notation.
// Extracting a field from a JSON property column
AppRequests
| extend ClientOS = tostring(Properties.os)
| summarize RequestCount = count() by ClientOS
The tostring() function is used to cast the dynamic JSON value into a string, which is necessary for grouping or filtering. This ability to parse semi-structured data on the fly is one of the most powerful aspects of KQL.
Using Functions and Plugins
KQL supports various plugins that extend its functionality, such as autocluster for pattern recognition or series_decompose for anomaly detection.
// Detect anomalies in request volume
let min_t = datetime(2023-01-01);
let max_t = datetime(2023-01-07);
AppRequests
| where TimeGenerated between(min_t .. max_t)
| make-series RequestCount=count() on TimeGenerated from min_t to max_t step 1h
| extend Anomaly = series_decompose_anomalies(RequestCount)
| render timechart
This query uses time-series decomposition to automatically flag points in your data that deviate from the expected pattern. This is significantly more effective than manual threshold-based alerting, which often leads to "alert fatigue."
Managing Performance and Costs
In large organizations, KQL queries can incur costs based on the amount of data scanned. To manage this:
- Use Table Partitioning: While you don't explicitly manage this, Azure does it for you. Keep your time ranges as tight as possible to hit fewer partitions.
- Monitor Query Usage: Use the
LAQueryLogstable (if enabled) to see which queries are consuming the most resources. - Use Saved Queries: If you find yourself writing the same complex query repeatedly, save it as a "Query" in the Azure portal. This promotes consistency and saves time.
Callout: The Power of
letStatements Theletstatement is your best friend for complex queries. It allows you to define variables or even entire sub-queries that can be reused later in the same statement. This makes your code modular and much easier to read.let TimeRange = 1h; let ErrorThreshold = 50; AppRequests | where TimeGenerated > ago(TimeRange) | where Status > ErrorThresholdBy defining variables at the top, you can easily tweak your query without hunting through the code to change hard-coded values.
Common Questions and Troubleshooting
Why is my query returning no results?
First, check your time range. It is the most common reason for empty results. Next, ensure that the data is actually flowing into the table you are querying. You can check the Usage table to see how much data is being ingested into specific tables.
How do I handle missing values?
Sometimes a column might have null values. Use the isnull() or isnotnull() operators to filter these out. You can also use coalesce() to provide a default value if a field is empty.
Can I join data from different workspaces?
Yes, using the workspace() function. You can perform cross-workspace queries if you have the necessary permissions. This is essential for companies with multiple Azure subscriptions or regions.
workspace("MyWorkspace").AppRequests
| join (workspace("OtherWorkspace").AzureActivity) on $left.AppId == $right.ResourceId
Building Dashboards with KQL
Once you have perfected your queries, the next step is to surface them. Azure Monitor Workbooks are the standard way to present KQL insights. You can create interactive reports where users can select parameters like "Time Range" or "Environment" from dropdown menus, and the KQL queries will update dynamically.
To turn a query into a parameterized one, use the {} syntax:
AppRequests
| where TimeGenerated > ago({TimeRange})
| where AppRoleName == '{SelectedApp}'
When you save this as a Workbook, the Azure portal automatically generates dropdown menus for the {TimeRange} and {SelectedApp} placeholders. This is how you empower your team—by giving them tools that allow them to explore data without needing to know KQL themselves.
Industry Standards and Best Practices
When working in a professional environment, follow these standards for your KQL codebase:
- Comment Your Code: Use
//to explain the intent of complex sections. Future you will thank you during an incident response. - Standardize Naming: Use clear, descriptive names for aliases created with
extend. - Version Control: If you have highly complex analytical queries, store them in a Git repository. You can treat your monitoring logic as "Monitoring as Code."
- Audit Access: Ensure that only authorized personnel have access to query logs, as they may contain sensitive information like user IDs or IP addresses.
- Alerting Integration: Your KQL queries are the foundation of your alerting strategy. Ensure your queries are stable and test them against historical data before turning them into production alerts.
Summary: Key Takeaways
Mastering KQL is a journey that moves from basic filtering to building complex, predictive analytical models. As you continue your work in Azure, remember these core principles:
- Pipelining is King: Always use the pipe operator to build your logic step-by-step. It makes your queries readable and easy to debug.
- Filter Early and Often: Always start with
TimeGeneratedand specific filters to minimize the amount of data processed, which keeps your queries fast and inexpensive. - Leverage Native Visualizations: Don't just look at tables; use the
renderoperator to identify trends and anomalies visually. - Understand Your Schema: Use the
take 10command to explore table structures before you start writing complex logic. - Use
letStatements: Keep your queries clean and modular by defining variables and sub-queries at the top of your code. - Focus on Performance: Avoid
project *, unnecessary joins, and case-insensitive string operations when performance is critical. - Think in Time-Series: Most Azure data is time-based. Master the
bin()function and time-series operators to unlock the full potential of your logs.
By applying these practices, you will transition from simply "looking at logs" to actively "gaining insights" from your cloud environment. KQL is a skill that scales with your career, and the more you practice, the more intuitive the language will become. Start by converting your manual troubleshooting steps into saved queries, and soon you will be building automated dashboards that provide instant visibility into the health of your infrastructure.
Continue the course
Enjoying the courses?
Everything stays free. Pro shows fewer ads, doubles your daily points limit so you progress twice as fast, and lets you read each lesson on one page.
- ✓ Fewer advertisements
- ✓ 2× daily points limit
- ✓ Distraction-free lessons