Learning the Kusto Query Language (KQL) with Application Insights Logs

Application Insights is a powerful Azure feature for monitoring web applications. It collects a wide range of useful data that you can analyse either in real-time or by executing queries against logs at a later point in time.

The logs that are stored by Application Insights enable you to see how your web requests are performing, what kind of load your application is under, what errors have occurred, as well as any custom metrics that you may have enriched the logs with.

The Kusto Query Language (KQL) is used across various Azure cloud resource types, including Application Insights, to allow logs and other big data sets to be queried in an efficient manner.

In this article, I am going to focus on using KQL queries within Application Insights Logs to help you understand the behaviour of your application. I will walk you through some practical examples and cover the most common query operators that you’ll need to become familiar with to start using KQL effectively.

What is KQL?

The KQL I am referring to stands for Kusto Query Language.

Note that this is different to Kibana Query Language, a simple query language for filtering Elasticsearch results.

KQL is a feature-rich query language powered by the Kusto Engine that allows you to filter, sort, and aggregate data. It was built for the cloud and designed to play well with large data sets, allowing them to be analysed efficiently.

KQL is the primary tool used to query Application Insights Logs, however, it’s useful to know that KQL is not limited to this purpose. KQL can be used to query data from other Azure platforms, including Azure Log Analytics and Microsoft Defender for Cloud.

By the end of this article, you will be comfortable with writing basic KQL queries to retrieve data from Application Insights Logs and you can use this same knowledge to extract useful information from other Azure platforms too.

Demo data access

Microsoft helpfully offers demo platforms that can help you to learn KQL in a safe environment.

You can click here to access the Application Insights demo data in the Azure portal where you’ll see a user interface that is similar to the screenshot below.

Application Insights Logs user interface
Application Insights Logs user interface

The Application Insights Logs user interface is fairly intuitive. There is the main query text area where you can enter query text and a ‘Run’ button above this that allows you to execute your queries. Query results are displayed below the query text area after you execute a query. On the left, there is a collection of tables and you can expand each table node to view the columns contained within each table.

I recommend that you use the demo portal to follow along as you read the remainder of this article. You can copy and paste the queries found in the following sections into the query text area and run them to see the results.

All you need is a free Microsoft account to access the demo portal and start working on your KQL skills.

Queries

Let’s get stuck into some queries. I’ll be covering the query operators that I believe will be most helpful for getting started with analysing Application Insights Logs. I’ll be focusing mostly on the ‘requests’ table as it tends to be the most frequently queried table for Application Insights.

To start with, KQL queries can be as simple as the following (enter this into the query text area in Azure and press the ‘Run’ button).

requests

The above query will return all records from the ‘requests’ table (limited to 30,000 by default).

Note that by default the query results are shown for the ‘Last 24 hours’ as indicated by the ‘Time range’ control located to the right of the ‘Run’ button within the user interface. You can change this period by either changing the selected value of the ‘Time range’ control or by setting a custom date/time range within your query (we’ll see an example of this shortly).

Let’s look at some more practical examples next.

Filtering

When you are running queries on any data, one of the most common things you’ll want to do is filter the results based on some criteria. KQL provides the where operator for this purpose.

Example filter query

Here’s a nice first example of a basic request filtering query that you can use and build upon.

requests
| where name has "reports/index"
and timestamp between (datetime(2022-02-08) .. datetime(2022-02-09))

Let’s break down the structure of this query to make it easier to understand the other queries we’re going to cover.

The pipe (|) character is used to pipe the ‘requests’ table output to the where operator.

In the where condition, the name of the column to filter on (name in this example) is specified, followed by the has operator and the value to look for.

Note that the has operator looks for a specific word, so it offers better performance compared to the contains operator which can be used if a substring match is required (e.g. contains "reports/in"). Also, the has operator is case-insensitive. You can use has_cs instead for case-sensitive filtering (the _cs postfix applies to several other operators too).

Conditions can be chained together using the and/or keywords. In this case, another condition has been added to filter on the ‘timestamp’ column for requests that are between the specified datetime values.

Round brackets are used to surround the datetime values. As we’re using the between keyword, the values are separated by .. characters.

Note that you may need to adjust the datetime values to more recent ones in order to get some results from the demo portal.

Since a specific date/time range has been specified in the query you’ll notice that the ‘Time range’ control above the query text area in Azure now says ‘Set in query’. Quite a clever little addition I think you’ll agree!

More conditions

Now that we understand how a query is put together, let’s look at a couple more filtering examples.

requests
| where timestamp >= ago(1hr)
and (client_City == "London" or client_City == "Paris")

In the above example, the ago function is used to restrict the results to requests that have been made in the last hour and where the client city is either London or Paris.

You can use a combination of and/or operators and round brackets to build up highly complex conditions like you would with a traditional SQL query.

Filtering on multiple columns

Here’s an example of how to search for data across multiple columns.

requests
| where * hassuffix "index"

The above query is filtering on records where the value of any column in the ‘requests’ table ends with the text ‘index’.

The usage of the asterisk (*) character to search all columns of a table allows the where operator to mimic the behaviour of the search operator, which we’ll cover next.

Searching

If you don’t need to do any conditional checks and only need to search your tables for specific text or patterns of text, you can use the search operator instead of where.

requests
| search "reports"

The above query will search for the text ‘reports’ in every column of every row of the ‘requests’ table. Although it’s not the most efficient option, the search operator offers a quick and simple way of finding text.

To make the search case-sensitive we can update the query slightly using the kind modifier, as follows.

requests
| search kind=case_sensitive "reports"

Instead of searching all columns, you can restrict the search to a specific column by including the name of the column to search followed by a colon (:) character, as shown below.

requests
| search name:"reports"

You can also search for text in multiple tables, like so.

search in (requests, traces) "reports" or "home"

Note the use of the logical or operator in the above example. As per some of the where examples shown previously, you can build up more complex and/or conditions where required.

There are many other things you can do with search (and where) such as including wildcards in your search terms (e.g. "*reports*") and you can avail of several other operators such as startswith or endswith.

Check out the String operators page on the Microsoft Docs website which lists all of the available KQL string operators for reference. You’ll notice that on the left of the page there are links to additional pages that document other operators types such as numerical operators and logical operators.

Sorting and limiting

In addition to filtering results and searching for text, sorting and limiting results are common operations you’ll need to learn to use KQL effectively.

Here’s an example of how to sort and limit data.

requests
| 
sort by timestamp desc
| take 10

The above query uses the sort operator to sort requests by the ‘timestamp’ column in descending order i.e. the most recent request will appear at the top of the results.

Note that you can also use the order operator instead which is an alias providing the same behaviour as sort.

The take operator is used to limit the number of records returned by the query, in this case, the results are limited to 10 records. Limiting results is especially useful for speeding up query execution time so that you can check that your query is valid. You can then remove the limit in order to obtain the complete result set.

It’s important to know that the take operator does not guarantee that the same records will be returned each time you run a query. Therefore, it’s important to sort the data first if you’re using the take operator for anything other than query testing purposes.

Note that you can also use the limit operator instead which is an alias providing the same behaviour as take.

Another way of achieving the same results as the query further above is to use the top operator.

requests 
| top 10 by timestamp desc

The top operator conveniently allows you to specify the sort/order criteria and the direction to sort the values in.

As per the previous query, this query will return the top 10 records in order of the most recent timestamp values.

Counting

To count data using KQL, all you need to do is pipe your overall query to the count operator and it will return the total number of records that match your conditions.

requests 
| where resultCode == "404"
| count

The above query filters on requests with a 404 (Not Found) result code and then displays the total number of records, rather than a list of request records.

Queries like this can be useful in various scenarios. For example, if you want to track if specific types of errors, warnings, or other events are occurring on a regular basis.

Projecting

By default, when you execute queries, the full set of columns from the tables you are querying are returned in the result set.

It can be very useful to restrict the list of columns to just the ones that you are interested in.

Here’s an example of how to achieve this using the project operator.

requests
| where timestamp >= ago(1hr) 
| sort by timestamp
| project timestamp,
url,
resultCode,
duration

By specifying only a particular set of columns, we are making the query more efficient and there’s also no need to scroll back and forward so much on the user interface to see the information that we are truly interested in.

If you want to specify a list of columns that should not be included in the output, simply replace the project keyword with project-away.

Joining

Inevitably there will be situations where you need to join records from one table to another.

When analysing Application Insights Logs, a good example of this is when you are querying the ‘traces’ table and you need to know what the URL relating to the trace record is. Perhaps your web application uses a separate sub-domain for each of your tenants and you want to filter on the url column to identify traces for a specific tenant.

traces
| join requests on operation_Id 
| where operation_Name has "home/index" 
and url startswith "https://ch1-retailappy37ha6"

In the above query, the join operator is used to join the ‘requests’ table to the ‘traces’ table on the value of the ‘operation_Id’ column.

Note that the default join operation is ‘inner unique’. This behaviour can be customised as follows: join kind=inner requests on operation_Id

The where operator is used to filter the results based on the value of the ‘operation_Name’ column in the ‘traces’ table.

Another condition is added to check that the URL starts with the specified text.

Note that url is a column in the requests table.

Check out the join operator page on the Microsoft Docs website to learn more about joins.

Additional learning

There’s so much more that I could cover in this article. However, my aim has been to provide simple and practical examples of the most frequently used query operators to help you get started.

As per the order of the content in this article, I recommend that you start off with using the where operator to filter records from the ‘requests’ table. You can then experiment further by adding other operators to the first example query to build something more complex.

Some other operators that I would encourage you to check out next include the following.

And of course, there are many other operators on top of these that you’ll want to explore!

For a deep dive, check out the official Microsoft Docs where the Kusto Query Language is fully documented.

This Quick Reference page within the Microsoft Docs is a very useful resource when you are writing KQL queries.

Summary

In this article, I have focused on examples of common types of queries you will want to run when analysing your Application Insights Logs in Azure.

I started off by briefly explaining what KQL is and how to access a demo portal where you can start learning it.

I then walked through a number of examples of how to filter data, search, sort, project, and more.

Lastly, I provided links to useful resources where you can continue your journey to learn KQL in greater depth.


I hope you enjoyed this post! Comments are always welcome and I respond to all questions.

If you like my content and it helped you out, please check out the button below 🙂

Comments

Seamus

> Since a specific date/time range has been specified in the query you’ll notice that the ‘Time range’ control above the query text area in Azure now says ‘Set in query’. Quite a clever little addition I think you’ll agree!
This is great until you realize that even if you try to filter by birthdate of person filed in logs, it still changes to “Set in Query”, and tries to get all the terabytes of logs instead of getting logs for last 4hrs.
Weird.

July 29, 2023

Jonathan Crozier

Hey Seamus, that does sound weird if I understand you correctly. Is the behaviour you have described something you’re able to replicate in the demo portal?

August 2, 2023