Ronald Bosma
Ronald Bosma
Software Architect | Microsoft MVP
Feb 28, 2023 17 min read

Azure Workbook Tips & Tricks

thumbnail for this post

If you use Azure, you most likely use Application Insights for logging. You can use a dashboard to visualize your logging and gain better insights, but dashboards come with some limitations. For more flexibility Azure has workbooks.

In this blog post I’ll share some tips & tricks that I’ve gathered over the years. As a sample, we’ll create a workbook that shows information about requests sent to an API Management instance.

Construct a query

When you want to display data from Application Insights on a dashboard or workbook, you’ll need to create a query. Azure uses the Kusto Query Language for this.

Creating such a query can be daunting if you’re unfamiliar with the syntax. I always like to start by constructing an initial query through the transaction search screen of Application Insights.

For this, open your Application Insights instance in the portal and go to Transaction Search. At the top you’ll see ‘pills’ that you can use to filter the data. By default, it will show logging from the last 24 hours for all event types. In our case, we’re interested in requests, so unselect all event types except ‘Request’.

We can add extra filters by adding new pills. You can then select the property on which to filter and the value(s).

To filter on requests from our API Management instance, first select the ‘Service ID’ property and then the name of your API Management instance.

I also want to be able to filter on requests from specific APIs. Add another pill, select ‘API Name’ as the property and select the APIs on which to filter.

The result should look similar to the image below.

Transaction Search

By clicking on ‘View in Logs’ you’ll go to the Logs screen where the query we’ve just constructed is loaded. It should look like the query below.

union isfuzzy = true requests
| where timestamp > datetime("2026-10-18T13:05:55.344Z") and timestamp < datetime("2026-10-19T13:05:55.344Z")
| where customDimensions["Service ID"] in("apim-monitorais-frc-u7eei")
| where customDimensions["API Name"] in("user-ratings-api", "movies-api")
| order by timestamp desc
| take 100

The query has the filters specified in the Transaction Search screen as where clauses. It’s also sorting the results on the timestamp and only showing the first 100 records.

We’ll optimize this query in a bit, but first have a look at the left of the Logs screen. You can find a list of tables you can query on and the columns of each table. Here’s part of the requests table for example.

Requests Table Properties

The timestamp property is a property we can directly use in our queries because it’s a column of the requests table. The Service ID and API Name are not default columns in the requests table because they are specific to API Management. These are stored in the customDimensions property and can be accessed with the syntax customDimensions["Service ID"] and customDimensions["API Name"].

When you specify your own custom properties to log from your application, you’ll find them in this customDimensions property.

Create reusable query

We’ll be reusing the same query in different parts of our workbook, so we’re going to make a function. First, we’ll clean up the previously generated query to look like this:

requests
| where customDimensions["Service ID"] == "apim-monitorais-frc-u7eei"
| extend subscription = tostring(customDimensions["Subscription Name"])
    , api = tostring(customDimensions["API Name"])

We’ve made the following changes:

  • The union isfuzzy=true part is useful when querying multiple event types. Because we’re only querying requests we’ve removed it.
  • The query screen provides a ‘Time range’ pill that can be used to specify a time range to filter on. We will provide a similar filter in our workbook. So, the where clause on timestamp is removed.
  • We’ll only query on a single API Management instance, so the ‘in’ filter has become an ‘equals’.
  • We’ll be adding the filter on API name in our workbook. So, it’s removed for now.
  • We extend the query results with the following properties from the custom dimensions array. We also convert them to strings, so they’re easier to work with.
    • Subscription Name: the name of the subscription that was used to call an API. This will make it possible to identify who performed a request.
    • API Name: this is the name of the API that was called.
  • Sorting is up to the queries using the function, so | order by timestamp desc can be removed.
  • Lastly, we can remove the | take 100 to show more results.

To save the query as a function, choose ‘Save > Save as function’ and give it a name like ‘ApimRequests’.

When the function is saved, you can use it in a query like this:

Query Results

Because we’ve extended the results in the function with api, we don’t need to use the customDimensions["API Name"] syntax in our query.

Create Workbook

Now that we have our query, we can start creating our workbook. Open your Application Insights instance and go to Workbooks. Azure already provides several workbooks that you can use and customize, but we’ll start from scratch. Click on Empty (A completely empty workbook). A new empty workbook opens.

When you click on Add, you’ll see that you can add different items to the workbook. We’ll focus on parameters and queries in this workbook.

Add Items Menu

Parameters

The first thing we’ll do is add a couple of parameters. These will allow us to filter on the data that will be displayed.

Click on ‘Add > Add parameters’ to add a parameters section to the top of the workbook.

Time Range Parameter

We’ll want to filter on a specific time range, so click on the ‘Add Parameter’ button. The New Parameter screen opens to the side. Enter the parameter name ‘Time’, select ‘Time range picker’ as the parameter type and make it required.

Add Parameter Time

Click Save to add the parameter.

Subscription Parameter (drop down from logs)

When calling API Management, we need to use a subscription for authentication. I want to be able filter on this subscription to see who performed which requests.

Click on the ‘Add Parameter’ button. Enter the parameter name ‘Subscription’, select ‘Drop down’ as the parameter type, check the ‘Allow multiple selections’ box and select ‘Query’ as the source of the data.

Enter the following query. As you can see, we’re using the function that we’ve created earlier.

ApimRequests
| distinct subscription
| sort by subscription asc

If you want to filter the results in your parameter based on the selected time in the Time parameter, select Time in the Time Range drop down.

To test the query, click the Run Query button.

You might have to select a time range in the Time parameter first for the query to work.

The New Parameter screen should look like this.

Add Parameter Subscription

NOTE: if you scroll down in the New Parameter window, you’ll see how you can use this parameter in a query.

Click Save to add the parameter.

Api Parameter (drop down from logs)

As mentioned before, we also want to filter on the API that was called. As an extra requirement, I want to filter the list of APIs based on the selected subscription(s) from Subscription parameter.

Click on the ‘Add Parameter’ button. Enter the parameter name ‘Api’, select ‘Drop down’ as the parameter type, check the ‘Allow multiple selections’ box and select ‘Query’ as the source of the data.

Enter the following query and select Time as the Time Range.

let subscriptionFilter = dynamic([{Subscription}]);

ApimRequests
| where array_length(subscriptionFilter) == 0 or subscription in (subscriptionFilter)
| distinct api
| sort by api asc

As you can see, the query is a bit more complicated.

  • The let subscriptionFilter = dynamic([{Subscription}]); line will create an array of selected subscriptions based on the Subscription parameter. If no subscription is selected, the array is empty.
  • The filter | where array_length(subscriptionFilter) == 0 or subscription in (subscriptionFilter) will show all APIs if no subscription is selected or APIs with requests that have a matching subscription if one or more are selected in the Subscription filter.

The New Parameter screen should look like this.

Add Parameter Api

Run the query to test if it works. Click Save to add the parameter.

Success parameter (drop down from JSON)

You can also use a static list to populate a drop down filter. We’ll add another parameter to filter on successful and/or failed requests.

Click on the ‘Add Parameter’ button. Enter the parameter name ‘Success’, select ‘Drop down’ as the parameter type. The ‘Allow multiple selections’ checkbox can be left unchecked.

Select ‘JSON’ as the source of the data. In the JSON Input we need to add an array of values and labels. See the example below.

[
    { "value": "true", "label": "yes"},
    { "value": "false", "label": "no"}
]

We’ll see the items ‘yes’ and ‘no’ in the drop down list. These correspond to true and false respectively.

The New Parameter screen should look like this.

Add Parameter Success

Click Save to add the parameter.

Now that we’ve added our parameters, click the ‘Done Editing’ button in the ‘Editing parameters item’ section. The result should look something like this.

Workbook Parameters

Table

The next step is to add a table that shows the requests. Choose ‘Add > Add query’.

To filter the results in the table based on the selected time range of the previously created Time parameter, we can select the Time parameter in the Time Range drop down. Set the Visualization to Grid to display the results as a table.

Grid Time Range and Visualization

Now add the following query.

let subscriptionFilter = dynamic([{Subscription}]);
let apiFilter = dynamic([{Api}]);
let successFilter = '{Success}';

ApimRequests
| where array_length(subscriptionFilter) == 0 or subscription in (subscriptionFilter)
| where array_length(apiFilter) == 0 or api in (apiFilter)
| where isempty(successFilter) or success == tobool(successFilter)
| project status = iif(success==true, "success", "error")
    , timestamp
    , subscription
    , api
    , name
    , resultCode
    , duration
    , details = itemId
    , transaction = itemId
    , operation_Id
| order by timestamp desc

The subscription and api filters are like the one in the query of the Api parameter.

The Success parameter was not multiselect, so let successFilter = '{Success}'; will be empty if nothing is selected, true if yes is selected and false if no is selected. With the filter | where isempty(successFilter) or success == tobool(successFilter) we either show all requests or the requests that were (un)successful.

With the project keyword we specify a list of columns to display in the table. The status column uses iif to display success when the request succeeded and error otherwise. We’ll use this to display an icon further on. The itemId is displayed twice in both the details and transaction column. We’ll use these to create links to the request details.

The query is executed when you click the Run Query button. You can filter the data by changing the values of the parameters. For example, select ‘no’ in the Success parameter to show all failed requests.

You can configure more settings in the Advanced Settings tab. I’ve set the chart title to ‘Requests’. I also like to check the following options:

  • Show open external query button when not editing: this will add a button above the table on the right that opens the executed query in a new screen.
  • Show refresh icon when not editing: this will add a button above the table on the right to refresh the results.
  • Show Export to CSV button when not editing: this will add a button above the table on the right to export the results to a CSV.
  • Show filter field above grid or tiles: this will show a filter input field above the table. It can be used to further filter the results as shown below.

Table Filter Field

Status Icon

To make it easier to differentiate successful from failed requests, we can use icons in our table.

Follow these steps:

  • Click on the ‘Column Settings’ button in the ‘Settings’ tab and select the status column
  • Select Icon in the ‘Column renderer’ drop down

Status Column Settings

Choose ‘Save and Close’ to see the results.

The status column will display a green/red icon depending on the value:

Icon in Grid

See the Rendering options page for more supported icon values.

Duration Formatting

By default, the duration is displayed in milliseconds. Making it difficult to quickly see how long a request took. We can configure a different renderer and custom number formatting to improve readability.

Follow these steps:

  • Open the column settings again and select the duration column
  • Select ‘Bar (underneath)’ in the ‘Column renderer’ drop down
  • Check the ‘Custom number formatting’ checkbox
  • Select Milliseconds as the unit

Duration Column Settings

Choose ‘Save and Close’ to see the results.

Now, the duration column will display the value in milliseconds or seconds, depending on the value. It also renders a bar to indicate its relative value in relation to the slowest request.

Duration in Grid

Request Details in Context Pane

To show more information about a request, we can change the details column to show a link that opens the request details to the side.

Follow these steps:

  • Open the column settings again and select the details column
  • Select Link in the ‘Column renderer’ drop down
  • Enter ‘11ch’ as the ‘Custom Column Width’
  • Select ‘Request Details’ in the ‘View to open’ drop down
  • Enter ‘details’ as the ‘Link label’
  • Check the ‘Open link in Context pane’ box
  • Select Workbook in the ‘Resource Id comes from’ drop down

Table Column Details

Choose ‘Save and Close’ to see the results.

When you click on a details link, a context pane opens to the right showing the request properties. See the example below.

Request Details

End-to-end Transaction Details

To show the end-to-end transaction details of a request, we can change the transaction column to show a link that opens the end-to-end-transaction details.

Follow these steps:

  • Open the column settings again and select the transaction column
  • Select Link in the ‘Column renderer’ drop down
  • Enter ‘15ch’ as the ‘Custom Column Width’
  • Select ‘Request Details’ in the ‘View to open’ drop down
  • Enter ‘transaction’ as the ‘Link label’
  • Keep the ‘Open link in Context pane’ unchecked
  • Select Workbook in the ‘Resource Id comes from’ drop down

Table Column Transaction

Choose ‘Save and Close’ to see the results.

When you click on a transaction link, the end-to-end transaction screen is opened. See the example below.

End-to-end Transaction Details

Hide Operation ID

The query needs to include operation_Id so we can use it later to load dependencies for the selected request. However, I don’t want to display this column in the table.

Follow these steps:

  • Open the column settings again and select the operation_Id column
  • Select Hidden in the ‘Column renderer’ drop down

Operation ID Column Transaction

Choose ‘Save and Close’ to see the results.

Column Labels

The current column headings look fairly technical because some are using camelCasing. We can improve these by configuring column labels.

Follow these steps:

  • Open the column settings again
  • Click on the Labels tab
  • Specify the labels for the columns

Column Labels

Choose ‘Save and Close’ to see the results.

Number of Requests Graph

Besides tables you can also use other visualizations to display your query results. A graph that shows the number of requests over time gives a quick impression of the traffic and when errors occur. See the example below.

Number of Requests Chart

Start by adding another query to the workbook with ‘Add > Add query’. Select Time as the Time Range and Bar chart as the Visualization.

Add the following query:

let subscriptionFilter = dynamic([{Subscription}]);
let apiFilter = dynamic([{Api}]);
let successFilter = '{Success}';

ApimRequests
| where array_length(subscriptionFilter) == 0 or subscription in (subscriptionFilter)
| where array_length(apiFilter) == 0 or api in (apiFilter)
| where isempty(successFilter) or success == tobool(successFilter)
| make-series numberOfRequests=count() default=0 on timestamp from todatetime("{Time:startISO}") to todatetime("{Time:endISO}") step {Time:grain} by success

The filters are the same as in the table query. The query then uses make-series instead of summarize with bin, because a summarize only returns time slots that have data. With make-series, the graph always shows the entire selected time period, even if there are no requests at the start or end of the period.

The make-series operator is configured as follows:

  • from and to use the {Time:startISO} and {Time:endISO} properties of the Time parameter, so the series covers the selected time range.
  • step uses {Time:grain}. This way, the granularity is automatically adjusted depending on the time range you’ve selected in the Time parameter.
  • default=0 makes sure time slots without requests get the value 0 instead of null.
  • by success splits the results, so you get a separate series for successful and failed requests.

To configure the color and labels, follow these steps:

  • Click on the ‘Chart Settings’ button in the ‘Settings’ tab
  • Open the ‘Series Settings’ tab
  • Specify a display label and color for each series

Number of Requests Chart Settings

Choose ‘Save and Close’ to see the results.

I usually display a graph like this above a table. You can move it up by choosing ‘Move > Move up’.

Time Brushing

The graph supports time brushing, which means you can select a time period in the graph and all data in the workbook will be filtered accordingly.

Time Brushing

To configure time brushing, you first enable it on the graph:

  1. Open the ‘Advanced Settings’ of the graph.
  2. Check ‘Enable time range brushing’.
  3. Use Time as the parameter name. This is the same name as the time range parameter at the top of the workbook, which all queries use to filter on time.
  4. Check ‘Only export the parameter when a range is brushed’.

Enable Time Brushing

Next, edit the Time parameter:

  1. Go to ‘Advanced Settings’.
  2. Check ‘Treat this parameter as a global’.

Time Global Parameter

With this configuration, the time range selected in the drop-down is used by default. When you select a different time range in the graph, the Time parameter is updated and all data is filtered using the brushed range instead.

Master-detail Table

Tables and other items provide the option to select data. We can use that selection as a filter in other items.

As an example, we’ll create a master-detail table. When a request in the master table is selected, all dependencies that have the same operation id will be displayed in the details table. See the example below.

Master-detail Table

Export Selection from Master Table

To start, Edit the current Requests table. Go to Advanced Settings and check the ‘When items are selected, export parameters’ box.

Click on Add parameter. Enter operation_Id as the field to export. Enter SelectedOperationId as the parameter name.

Export Parameter Settings

Choose Save. It should look like this.

Exported Parameter

To reduce the amount of scrolling, I’ve changed the table size from medium to small in the settings.

Choose Done Editing on the editing query item.

Add Detail Table

Choose ‘Add > Add query’ to add another table. Select Time as the Time Range and Grid as Visualization.

Add the following query.

dependencies
| where operation_Id == '{SelectedOperationId}'
| project status = iif(success==true, "success", "error")
    , timestamp
    , target
    , name
    , type
    , resultCode
    , duration
    , details = itemId
    , transaction = itemId
| order by timestamp desc

This query looks similar to the query for requests. A few columns are different and it only filters on the operation_Id column using the exported parameter of the master table.

You can customize the styling of the columns again, similar to the master table. I also like to add a chart title in which I display the selected value. You can update the chart title in the Advanced Settings with: Dependencies of operation: {SelectedOperationId}.

Show Detail Table on Selection

If you don’t select an item in the master table. You’ll see the message below in place of the details table.

Master-detail Table No Selected Item

If you don’t like this, you can make the detail table hide when no row is selected in the master table.

Open the Advanced Settings and check the ‘Make this item conditionally visible’. Choose Add Condition. Enter SelectedOperationId as the Parameter name and select ‘is not equal to’ in the Comparison drop down. Leave the Parameter value input empty.

Visibility Condition

With this the details table is only shown when the SelectedOperationId has a value, which it will have if a row is selected in the master table.

Save Workbook

Once you’re done editing the workbook you can choose ‘Done Editing’. Then click the Save button. Enter a title in the Save As window and select the correct subscription, resource group & location. Choose Apply and the workbook is saved.

Note that parameter selections are also saved. Meaning, if you’re filtering on for example success == no and choose Save, this selection will be saved. Whenever you or another user opens the workbook, this selection will be preselected.

Share Workbook

To share the workbook with different parameters preselected, configure the parameters and click the Share button: Share this workbook button.

Then, in the share workbook screen:

  • Specify whether people who open the workbook from the link can edit it or only view it.
  • Check the ‘Open with selected parameter values’ checkbox.
  • Specify which parameter values should be preselected.
  • Copy the link to share.

Share workbook screen

I use this for parameter configurations that I frequently use. For example all failed API Management requests in the last 24 hours.

Conclusion

As you can see, there’s a lot you can do with workbooks, and we’ve only scratched the surface. See these links for more information.

Here are links to the exported workbook and shared function:

In my blog post Deploy Azure Workbook and App Insights Function I show you how to deploy this workbook using Bicep and the Azure CLI.